feat: sync vehicle monitoring and accumulated platform updates
Add monitoring metrics, numeric sorting, report-time display, vehicle number labels, and map layout fixes. Include accumulated mileage, hydrogen, OneOS authentication, open platform, ingestion, deployment, and UI updates. Exclude temporary files, generated reports, and credentials. Validate frontend 632 tests and production build, API packages, vehicle-map tests, and release installer tests. Gateway suite has one pre-existing date-sensitive identity throttling test failure reproduced on the parent commit.
This commit is contained in:
@@ -14,6 +14,7 @@ import (
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -28,10 +29,13 @@ type change struct {
|
||||
Total string `json:"latest_total_mileage_km"`
|
||||
LastTime string `json:"latest_event_time"`
|
||||
Daily string `json:"daily_mileage_km"`
|
||||
Status string `json:"quality_status"`
|
||||
Selected int `json:"is_selected"`
|
||||
} `json:"before"`
|
||||
Baseline struct {
|
||||
Total string `json:"total"`
|
||||
Time string `json:"time"`
|
||||
Source string `json:"source_key"`
|
||||
Total string `json:"total"`
|
||||
Time string `json:"time"`
|
||||
} `json:"baseline"`
|
||||
Daily string `json:"daily"`
|
||||
Status string `json:"status"`
|
||||
@@ -42,6 +46,7 @@ func main() {
|
||||
input := flag.String("input", "", "reviewed JSON plan")
|
||||
apply := flag.Bool("apply", false, "apply plan and projections in one transaction")
|
||||
manifest := flag.String("backup-manifest", "", "verified backup manifest required for apply")
|
||||
selectedOnly := flag.Bool("selected-projections-only", false, "preserve final results when only unselected candidates change")
|
||||
flag.Parse()
|
||||
data, err := os.ReadFile(*input)
|
||||
must(err)
|
||||
@@ -67,24 +72,44 @@ func main() {
|
||||
affected := map[string]change{}
|
||||
for _, c := range changes {
|
||||
b := c.Before
|
||||
total, stamp, found, err := stats.LookupLatestSourceBaselineBefore(ctx, tx, b.VIN, b.Date, envelope.Protocol(b.Protocol), b.Source)
|
||||
baselineSource := b.Source
|
||||
if c.Baseline.Source != "" && c.Baseline.Source != b.Source {
|
||||
// Historical IP keys and current platform keys may describe the
|
||||
// same terminal. Verify that identity against the registry before
|
||||
// accepting an explicitly reviewed alias boundary.
|
||||
currentCanonical, err := canonicalRepairSource(ctx, tx, b.Protocol, b.Source)
|
||||
must(err)
|
||||
baselineCanonical, err := canonicalRepairSource(ctx, tx, b.Protocol, c.Baseline.Source)
|
||||
must(err)
|
||||
if currentCanonical != baselineCanonical {
|
||||
panic("repair cannot mix independent source counters")
|
||||
}
|
||||
baselineSource = c.Baseline.Source
|
||||
}
|
||||
total, stamp, found, err := stats.LookupLatestSourceBaselineBefore(ctx, tx, b.VIN, b.Date, envelope.Protocol(b.Protocol), baselineSource)
|
||||
must(err)
|
||||
expected, err := strconv.ParseFloat(c.Baseline.Total, 64)
|
||||
must(err)
|
||||
if !found || math.Abs(total-expected) > 0.0001 || stamp.Format("2006-01-02 15:04:05") != c.Baseline.Time {
|
||||
panic(fmt.Sprintf("baseline changed: %s %s", b.VIN, b.Date))
|
||||
}
|
||||
oldStatus := b.Status
|
||||
if oldStatus == "" {
|
||||
oldStatus = stats.QualityOK
|
||||
}
|
||||
result, err := tx.ExecContext(ctx, `UPDATE vehicle_daily_mileage_source SET first_total_mileage_km=?,first_event_time=?,daily_mileage_km=?,quality_status=?,quality_reason=?
|
||||
WHERE vin=? AND stat_date=? AND protocol=? AND source_key=?
|
||||
AND first_total_mileage_km <=> ? AND first_event_time <=> ? AND latest_total_mileage_km=?
|
||||
AND latest_event_time=? AND daily_mileage_km=? AND quality_status='OK'`, c.Baseline.Total, c.Baseline.Time, c.Daily, c.Status, c.Reason, b.VIN, b.Date, b.Protocol, b.Source, b.First, b.FirstTime, b.Total, b.LastTime, b.Daily)
|
||||
AND latest_event_time=? AND daily_mileage_km=? AND quality_status=?`, c.Baseline.Total, c.Baseline.Time, c.Daily, c.Status, c.Reason, b.VIN, b.Date, b.Protocol, b.Source, b.First, b.FirstTime, b.Total, b.LastTime, b.Daily, oldStatus)
|
||||
must(err)
|
||||
n, err := result.RowsAffected()
|
||||
must(err)
|
||||
if n != 1 {
|
||||
panic(fmt.Sprintf("concurrent change: %s %s %s", b.VIN, b.Date, b.Source))
|
||||
}
|
||||
affected[b.Date+"|"+b.VIN+"|"+b.Protocol] = c
|
||||
if !*selectedOnly || b.Selected == 1 {
|
||||
affected[b.Date+"|"+b.VIN+"|"+b.Protocol] = c
|
||||
}
|
||||
}
|
||||
keys := make([]string, 0, len(affected))
|
||||
for key := range affected {
|
||||
@@ -98,6 +123,43 @@ func main() {
|
||||
must(tx.Commit())
|
||||
must(json.NewEncoder(os.Stdout).Encode(map[string]any{"updatedSources": len(changes), "reprojectedDays": len(keys), "committed": true}))
|
||||
}
|
||||
|
||||
func canonicalRepairSource(ctx context.Context, db stats.Queryer, protocol, key string) (string, error) {
|
||||
if protocol != "JT808" {
|
||||
return "", fmt.Errorf("source alias repair is only supported for JT808")
|
||||
}
|
||||
if strings.Contains(key, "@PLATFORM:") || strings.HasSuffix(key, "@DIRECT") {
|
||||
return key, nil
|
||||
}
|
||||
at := strings.LastIndex(key, "@")
|
||||
if at < 0 || !strings.HasPrefix(key, protocol+":") {
|
||||
return "", fmt.Errorf("unsupported legacy source key")
|
||||
}
|
||||
rows, err := db.QueryContext(ctx, `SELECT COALESCE(source_code,''),source_kind
|
||||
FROM vehicle_data_source WHERE protocol=? AND source_ip=?`, protocol, key[at+1:])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer rows.Close()
|
||||
if !rows.Next() {
|
||||
return "", fmt.Errorf("source registry is missing the legacy IP")
|
||||
}
|
||||
var code, kind string
|
||||
if err := rows.Scan(&code, &kind); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if kind == "PLATFORM" && strings.TrimSpace(code) != "" {
|
||||
return key[:at] + "@PLATFORM:" + strings.TrimSpace(code), nil
|
||||
}
|
||||
if kind == "DIRECT" {
|
||||
return key[:at] + "@DIRECT", nil
|
||||
}
|
||||
return "", fmt.Errorf("legacy source has no verified platform or direct-terminal identity")
|
||||
}
|
||||
|
||||
func must(err error) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/DATA-DOG/go-sqlmock"
|
||||
)
|
||||
|
||||
func TestCanonicalRepairSourceRequiresVerifiedRegistryIdentity(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name, key, code, kind, want string
|
||||
missing, wantError bool
|
||||
}{
|
||||
{name: "platform", key: "JT808:41456413939@117.132.196.192", code: "guangan_beidou", kind: "PLATFORM", want: "JT808:41456413939@PLATFORM:guangan_beidou"},
|
||||
{name: "direct", key: "JT808:41456413939@117.132.196.192", kind: "DIRECT", want: "JT808:41456413939@DIRECT"},
|
||||
{name: "unknown", key: "JT808:41456413939@117.132.196.192", kind: "UNKNOWN", wantError: true},
|
||||
{name: "missing", key: "JT808:41456413939@117.132.196.192", missing: true, wantError: true},
|
||||
{name: "platform-without-code", key: "JT808:41456413939@117.132.196.192", kind: "PLATFORM", wantError: true},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
db, mock, err := sqlmock.New()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
rows := sqlmock.NewRows([]string{"code", "kind"})
|
||||
if !tc.missing {
|
||||
rows.AddRow(tc.code, tc.kind)
|
||||
}
|
||||
mock.ExpectQuery("SELECT COALESCE").WithArgs("JT808", "117.132.196.192").WillReturnRows(rows)
|
||||
got, err := canonicalRepairSource(context.Background(), db, "JT808", tc.key)
|
||||
if (err != nil) != tc.wantError || got != tc.want {
|
||||
t.Fatalf("got=%q err=%v want=%q wantError=%v", got, err, tc.want, tc.wantError)
|
||||
}
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCanonicalRepairSourceKeepsIndependentCounters(t *testing.T) {
|
||||
for _, key := range []string{"JT808:terminal-a@PLATFORM:g7s", "JT808:terminal-b@PLATFORM:g7s", "JT808:terminal-a@PLATFORM:dongfang_beidou"} {
|
||||
got, err := canonicalRepairSource(context.Background(), nil, "JT808", key)
|
||||
if err != nil || got != key {
|
||||
t.Fatalf("independent identity changed: %s %v", got, err)
|
||||
}
|
||||
}
|
||||
if _, err := canonicalRepairSource(context.Background(), nil, "GB32960", "GB32960:vin@PLATFORM:g7s"); err == nil {
|
||||
t.Fatal("alias repair must not silently change other protocol rules")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user