diff --git a/vehicle-data-platform/apps/api/internal/platform/production_store.go b/vehicle-data-platform/apps/api/internal/platform/production_store.go index a5c39e58..d4c7cec4 100644 --- a/vehicle-data-platform/apps/api/internal/platform/production_store.go +++ b/vehicle-data-platform/apps/api/internal/platform/production_store.go @@ -50,7 +50,11 @@ func (s *ProductionStore) DashboardSummary(ctx context.Context) (DashboardSummar if err := s.db.QueryRowContext(ctx, `SELECT COUNT(*) FROM jt808_registration r LEFT JOIN vehicle_identity_binding b ON b.phone = r.phone WHERE r.vin = '' OR r.vin = 'unknown' OR r.vin IS NULL`).Scan(&summary.IssueVehicles); err != nil { return DashboardSummary{}, err } - summary.FrameToday = summary.ActiveToday + frameToday, err := s.frameToday(ctx, time.Now()) + if err != nil { + return DashboardSummary{}, err + } + summary.FrameToday = frameToday protocols, err := s.protocolStats(ctx) if err != nil { return DashboardSummary{}, err @@ -65,6 +69,18 @@ func (s *ProductionStore) DashboardSummary(ctx context.Context) (DashboardSummar return summary, nil } +func (s *ProductionStore) frameToday(ctx context.Context, now time.Time) (int, error) { + if s.tdengine == nil { + return 0, nil + } + built := buildTodayRawFrameCountSQL(s.tdDatabase, now) + total := 0 + if err := s.tdengine.QueryRowContext(ctx, built.Text).Scan(&total); err != nil { + return 0, err + } + return total, nil +} + func (s *ProductionStore) Vehicles(ctx context.Context, query url.Values) (Page[VehicleRow], error) { built := buildVehicleListSQL(query) total := 0 diff --git a/vehicle-data-platform/apps/api/internal/platform/query_builders_test.go b/vehicle-data-platform/apps/api/internal/platform/query_builders_test.go index 5f009f66..2157b24a 100644 --- a/vehicle-data-platform/apps/api/internal/platform/query_builders_test.go +++ b/vehicle-data-platform/apps/api/internal/platform/query_builders_test.go @@ -4,6 +4,7 @@ import ( "net/url" "strings" "testing" + "time" ) func TestBuildVehicleListSQL(t *testing.T) { @@ -120,3 +121,13 @@ func TestBuildHistoryLocationSQL(t *testing.T) { t.Fatalf("SQL should keep stable pagination order: %s", built.Text) } } + +func TestBuildTodayRawFrameCountSQL(t *testing.T) { + now := time.Date(2026, 7, 3, 22, 30, 0, 0, time.FixedZone("Asia/Shanghai", 8*3600)) + built := buildTodayRawFrameCountSQL("lingniu_vehicle_ts", now) + for _, want := range []string{"SELECT COUNT(*)", "lingniu_vehicle_ts.raw_frames", "ts >= '2026-07-02 16:00:00'"} { + if !strings.Contains(built.Text, want) { + t.Fatalf("SQL missing %q: %s", want, built.Text) + } + } +} diff --git a/vehicle-data-platform/apps/api/internal/platform/tdengine_queries.go b/vehicle-data-platform/apps/api/internal/platform/tdengine_queries.go index 8727c85c..0eec668f 100644 --- a/vehicle-data-platform/apps/api/internal/platform/tdengine_queries.go +++ b/vehicle-data-platform/apps/api/internal/platform/tdengine_queries.go @@ -83,6 +83,13 @@ func buildHistoryLocationSQL(database string, query map[string]string) SQLQuery return SQLQuery{Text: text, Args: args, CountText: countText} } +func buildTodayRawFrameCountSQL(database string, now time.Time) SQLQuery { + start := shanghaiDayStartUTC(now) + return SQLQuery{ + Text: `SELECT COUNT(*) FROM ` + qualifyTDengine(database, "raw_frames") + ` WHERE ts >= '` + quoteTDengine(start) + `'`, + } +} + func qualifyTDengine(database, table string) string { if strings.TrimSpace(database) == "" { return table @@ -136,3 +143,10 @@ func normalizeTDengineTime(value string) string { } return value } + +func shanghaiDayStartUTC(now time.Time) string { + shanghai := time.FixedZone("Asia/Shanghai", 8*3600) + local := now.In(shanghai) + start := time.Date(local.Year(), local.Month(), local.Day(), 0, 0, 0, 0, shanghai) + return start.UTC().Format("2006-01-02 15:04:05") +}