功能:完善里程边界与氢耗流式统计

This commit is contained in:
lingniu
2026-09-02 14:05:04 +08:00
parent 4201878c35
commit 8759668d39
23 changed files with 1638 additions and 159 deletions
@@ -211,12 +211,10 @@ func TestUpsertSourceMileageUsesEventTimeBoundaries(t *testing.T) {
}
}
func TestUpsertSourceMileageAccumulatesOnlyContinuousPureHydrogenIntervals(t *testing.T) {
func TestUpsertSourceMileageAttributesDeltaToCurrentPureHydrogenMode(t *testing.T) {
for _, want := range []string{
"pure_hydrogen_mileage_km = pure_hydrogen_mileage_km + CASE",
"VALUES(latest_event_time) > latest_event_time",
"latest_pure_hydrogen_mode_known = 1",
"latest_pure_hydrogen_active = 1",
"VALUES(latest_pure_hydrogen_mode_known) = 1",
"VALUES(latest_pure_hydrogen_active) = 1",
"VALUES(latest_total_mileage_km) - latest_total_mileage_km",
@@ -281,6 +279,26 @@ func TestDailyMileageFromDayBoundaryUsesCurrentMinusPrevious(t *testing.T) {
}
}
func TestIsUsableDailyMileageBoundaryRejectsOlderOfflineGap(t *testing.T) {
loc := time.FixedZone("Asia/Shanghai", 8*3600)
for _, test := range []struct {
name string
baseline time.Time
want bool
}{
{name: "current day cache", baseline: time.Date(2026, 8, 4, 19, 1, 26, 0, loc), want: true},
{name: "previous natural day", baseline: time.Date(2026, 8, 3, 23, 59, 59, 0, loc), want: true},
{name: "older offline gap", baseline: time.Date(2026, 5, 21, 23, 13, 2, 0, loc), want: false},
{name: "future boundary", baseline: time.Date(2026, 8, 5, 0, 0, 0, 0, loc), want: false},
} {
t.Run(test.name, func(t *testing.T) {
if got := IsUsableDailyMileageBoundary("2026-08-04", test.baseline, loc); got != test.want {
t.Fatalf("IsUsableDailyMileageBoundary() = %v, want %v", got, test.want)
}
})
}
}
func TestUpsertSourceMileageSkipsBlankSourceIP(t *testing.T) {
exec := &recordingExec{}
sample := SourceMileageSample{
@@ -612,6 +630,15 @@ func TestProjectDailyMileageSelectsCandidateAndMarksSource(t *testing.T) {
"project": projectFinal,
"mark": markSelected,
} {
// The project statement also contains an independent day-end odometer
// subquery. Check the daily-mileage election order after its main source
// FROM clause, rather than mistaking the odometer fallback's freshness
// order for the elected daily-distance source order.
if queryName == "project" {
if index := strings.Index(query, "FROM vehicle_daily_mileage_source s\nLEFT JOIN"); index >= 0 {
query = query[index:]
}
}
identityScore := strings.Index(query, "SUM(identity_source.sample_count)")
freshness := strings.Index(query, "latest_event_time DESC")
rowSamples := strings.LastIndex(query, "sample_count DESC")
@@ -699,3 +726,16 @@ func expectProjectDailyMileageSQL(mock sqlmock.Sqlmock, vin string, statDate str
WithArgs(vin, statDate, string(protocol), vin, statDate, string(protocol)).
WillReturnResult(sqlmock.NewResult(0, 1))
}
func TestProjectDailyMileagePersistsIndependentDayEndTotal(t *testing.T) {
for _, want := range []string{
"day_end_total_mileage_km",
"total_candidate.quality_status = 'OK'",
"total_candidate.quality_reason, '') = 'gps_coordinate_accumulation'",
"day_end_total_mileage_km = VALUES(day_end_total_mileage_km)",
} {
if !strings.Contains(projectDailyMileageSQL, want) {
t.Fatalf("project SQL missing %q:\n%s", want, projectDailyMileageSQL)
}
}
}