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:
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user