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:
lingniu
2026-10-26 18:05:59 +08:00
parent 8b32ae2988
commit d6b957dfc8
290 changed files with 28238 additions and 1647 deletions
@@ -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")
}
}