fix(stats): reject sub-threshold gps drift

This commit is contained in:
lingniu
2026-07-20 01:27:16 +08:00
parent 2ef027c7c2
commit 275d453954
6 changed files with 72 additions and 4 deletions

View File

@@ -151,6 +151,18 @@ func TestWriterThrottlesHighFrequencyGPSMileageStateWrites(t *testing.T) {
}
}
func TestGPSMileageCandidateEligibleRejectsStationaryDrift(t *testing.T) {
if GPSMileageCandidateEligible(0.099999, 10) {
t.Fatal("sub-100-metre GPS drift must not become a daily-mileage candidate")
}
if GPSMileageCandidateEligible(0.2, 0) {
t.Fatal("distance without a usable segment must not become a candidate")
}
if !GPSMileageCandidateEligible(0.1, 1) {
t.Fatal("100 metres with a usable segment should become eligible")
}
}
func TestAccumulateJT808GPSMileagePersistsEstimateAndProjects(t *testing.T) {
db, mock, err := sqlmock.New()
if err != nil {
@@ -250,3 +262,47 @@ func TestAccumulateJT808GPSMileageNeedsUsableSegmentBeforeProjection(t *testing.
t.Fatalf("unmet SQL expectations: %v", err)
}
}
func TestAccumulateGPSMileageKeepsSubThresholdDistanceAsStateOnly(t *testing.T) {
db, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("sqlmock.New() error = %v", err)
}
defer db.Close()
start := time.Date(2026, 7, 20, 0, 14, 56, 0, time.FixedZone("Asia/Shanghai", 8*3600))
point := GPSMileagePoint{
VIN: "LMRKH9ACXR1004130",
StatDate: "2026-07-20",
Protocol: envelope.ProtocolYutongMQTT,
SourceKey: "YUTONG_MQTT:LMRKH9ACXR1004130@PLATFORM:yutong",
SourceIP: "mqtt",
EventID: "evt-small-movement",
EventTime: start.Add(time.Second),
Longitude: 114.435573,
Latitude: 30.645503,
}
mock.ExpectBegin()
mock.ExpectQuery("SELECT first_event_time, latest_event_time").
WillReturnRows(sqlmock.NewRows([]string{
"first_event_time", "latest_event_time", "latest_event_id",
"latest_longitude", "latest_latitude", "daily_mileage_km",
"point_count", "usable_segment_count", "bad_jump_count",
"long_gap_count", "out_of_order_count",
}).AddRow(start, start, "evt-before", 114.435572, 30.645503, 0, 1, 0, 0, 0, 0))
mock.ExpectExec("UPDATE vehicle_daily_gps_mileage_state").
WillReturnResult(sqlmock.NewResult(0, 1))
mock.ExpectCommit()
state, recovered, err := AccumulateGPSMileage(context.Background(), db, point)
if err != nil {
t.Fatalf("AccumulateGPSMileage() error = %v", err)
}
if recovered || state.DailyMileageKM <= 0 || state.DailyMileageKM >= gpsMinimumDailyDistanceKM {
t.Fatalf("sub-threshold state = %+v recovered=%v", state, recovered)
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Fatalf("unmet SQL expectations: %v", err)
}
}