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
@@ -0,0 +1,88 @@
package main
import (
"context"
"database/sql"
"errors"
"fmt"
"log/slog"
"time"
"lingniu-vehicle-ingest/go/vehicle-gateway/internal/envelope"
)
var errNoUsableSourceBoundary = errors.New("source has no usable mileage boundary")
// FIRST/LAST are ordered by TDengine's storage timestamp, which represents
// receipt order. Keep the fast aggregate path, but independently verify both
// ends against extrema of the vehicle event timestamp and fetch the matching
// complete row whenever late delivery has moved either end.
type sourceBoundaryEvidence struct {
vin, phone, deviceID, endpoint string
firstTS, lastTS time.Time
firstParsed, firstRaw string
lastParsed, lastRaw string
count, minimumMS, maximumMS int64
}
func correctSourceEventBoundaries(ctx context.Context, db *sql.DB, cfg config, protocol envelope.Protocol, where string, includeFirst bool, e *sourceBoundaryEvidence) error {
_, firstUsable := mileageFromEvidence(protocol, e.vin, e.firstParsed, e.firstRaw, e.firstTS, cfg.Location)
_, lastUsable := mileageFromEvidence(protocol, e.vin, e.lastParsed, e.lastRaw, e.lastTS, cfg.Location)
if e.lastTS.UnixMilli() != e.maximumMS || !lastUsable {
stamp, parsed, raw, err := querySourceEventBoundary(ctx, db, cfg, protocol, where, *e, e.maximumMS, false)
if err != nil {
if errors.Is(err, sql.ErrNoRows) && !firstUsable && !lastUsable {
return errNoUsableSourceBoundary
}
return err
}
slog.Info("corrected last source event boundary", "vin", e.vin, "endpoint", e.endpoint, "storage_last", e.lastTS, "event_last", stamp)
e.lastTS, e.lastParsed, e.lastRaw = stamp, parsed, raw
}
if !includeFirst {
e.firstTS, e.firstParsed, e.firstRaw = e.lastTS, e.lastParsed, e.lastRaw
return nil
}
if e.firstTS.UnixMilli() != e.minimumMS || !firstUsable {
stamp, parsed, raw, err := querySourceEventBoundary(ctx, db, cfg, protocol, where, *e, e.minimumMS, true)
if err != nil {
return err
}
slog.Info("corrected first source event boundary", "vin", e.vin, "endpoint", e.endpoint, "storage_first", e.firstTS, "event_first", stamp)
e.firstTS, e.firstParsed, e.firstRaw = stamp, parsed, raw
}
return nil
}
func querySourceEventBoundary(ctx context.Context, db *sql.DB, cfg config, protocol envelope.Protocol, where string, e sourceBoundaryEvidence, eventMS int64, first bool) (time.Time, string, string, error) {
// An equal event timestamp is resolved consistently by newest storage ts.
// Never obtain the timestamp and payload through independent aggregates.
comparison, order := "<=", "DESC"
if first {
comparison, order = ">=", "ASC"
}
query := fmt.Sprintf(`SELECT event_time, parsed_json, raw_text
FROM %s.raw_frames
WHERE %s AND vin = '%s' AND phone = '%s' AND device_id = '%s'
AND source_endpoint = '%s' AND CAST(event_time AS BIGINT) %s %d
ORDER BY event_time %s, ts DESC`, ident(cfg.TDengineDatabase), where, quote(e.vin), quote(e.phone), quote(e.deviceID), quote(e.endpoint), comparison, eventMS, order)
rows, err := db.QueryContext(ctx, query)
if err != nil {
return time.Time{}, "", "", fmt.Errorf("resolve event-time boundary %s/%s/%d: %w", e.vin, e.endpoint, eventMS, err)
}
defer rows.Close()
for rows.Next() {
var stamp time.Time
var parsed, raw string
if err := rows.Scan(&stamp, &parsed, &raw); err != nil {
return time.Time{}, "", "", err
}
if _, ok := mileageFromEvidence(protocol, e.vin, parsed, raw, stamp, cfg.Location); ok {
return stamp, parsed, raw, nil
}
}
if err := rows.Err(); err != nil {
return time.Time{}, "", "", err
}
return time.Time{}, "", "", sql.ErrNoRows
}