fix(stats): calculate mileage without previous-day baseline
This commit is contained in:
@@ -67,6 +67,69 @@ func TestDailySourceLastBuildsCandidateKeysBySourceIP(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAggregateFromDailySourceUsesOlderHistoricalBaseline(t *testing.T) {
|
||||
current := dailySourceLast{
|
||||
VIN: "LMRKH9AC2R1004087",
|
||||
SourceKey: normalizedSourceKey("YUTONG_MQTT", "", "LMRKH9AC2R1004087", "mqtt://yutong/ytforward/shln/3"),
|
||||
DeviceID: "LMRKH9AC2R1004087",
|
||||
SourceEndpoint: "mqtt://yutong/ytforward/shln/3",
|
||||
FirstTS: time.Date(2026, 7, 8, 8, 0, 0, 0, time.FixedZone("Asia/Shanghai", 8*3600)),
|
||||
TS: time.Date(2026, 7, 8, 17, 0, 0, 0, time.FixedZone("Asia/Shanghai", 8*3600)),
|
||||
FirstTotalKM: 120778,
|
||||
TotalKM: 120788,
|
||||
RawSampleCount: 15,
|
||||
}
|
||||
previous := dailySourceLast{
|
||||
VIN: "LMRKH9AC2R1004087",
|
||||
SourceKey: current.SourceKey,
|
||||
DeviceID: "LMRKH9AC2R1004087",
|
||||
SourceEndpoint: "mqtt://yutong/ytforward/shln/3",
|
||||
TS: time.Date(2026, 7, 4, 23, 58, 0, 0, time.FixedZone("Asia/Shanghai", 8*3600)),
|
||||
TotalKM: 120672,
|
||||
}
|
||||
|
||||
agg := aggregateFromDailySource("2026-07-08", envelope.ProtocolYutongMQTT, current, previous, true)
|
||||
|
||||
if agg.FirstKM != 120672 || agg.LatestKM != 120788 {
|
||||
t.Fatalf("km range = %v -> %v", agg.FirstKM, agg.LatestKM)
|
||||
}
|
||||
if agg.FirstEventTime != previous.TS || agg.LatestEventTime != current.TS {
|
||||
t.Fatalf("event range = %v -> %v", agg.FirstEventTime, agg.LatestEventTime)
|
||||
}
|
||||
if agg.QualityStatus != stats.QualityOK || agg.QualityReason != "historical_source_baseline" {
|
||||
t.Fatalf("quality = %s/%s", agg.QualityStatus, agg.QualityReason)
|
||||
}
|
||||
if agg.Count != 15 {
|
||||
t.Fatalf("sample count = %d, want 15", agg.Count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAggregateFromDailySourceUsesCurrentFirstSampleWithoutHistory(t *testing.T) {
|
||||
current := dailySourceLast{
|
||||
VIN: "LMRKH9AC2R1004087",
|
||||
SourceKey: normalizedSourceKey("YUTONG_MQTT", "", "LMRKH9AC2R1004087", "mqtt://yutong/ytforward/shln/3"),
|
||||
DeviceID: "LMRKH9AC2R1004087",
|
||||
SourceEndpoint: "mqtt://yutong/ytforward/shln/3",
|
||||
FirstTS: time.Date(2026, 7, 8, 8, 0, 0, 0, time.FixedZone("Asia/Shanghai", 8*3600)),
|
||||
TS: time.Date(2026, 7, 8, 17, 0, 0, 0, time.FixedZone("Asia/Shanghai", 8*3600)),
|
||||
FirstTotalKM: 120778,
|
||||
TotalKM: 120788,
|
||||
RawSampleCount: 15,
|
||||
}
|
||||
|
||||
agg := aggregateFromDailySource("2026-07-08", envelope.ProtocolYutongMQTT, current, dailySourceLast{}, false)
|
||||
|
||||
if agg.FirstKM != 120778 || agg.LatestKM != 120788 {
|
||||
t.Fatalf("km range = %v -> %v", agg.FirstKM, agg.LatestKM)
|
||||
}
|
||||
if agg.FirstEventTime != current.FirstTS || agg.LatestEventTime != current.TS {
|
||||
t.Fatalf("event range = %v -> %v", agg.FirstEventTime, agg.LatestEventTime)
|
||||
}
|
||||
if agg.QualityStatus != stats.QualityOK || agg.QualityReason != "current_day_first_sample" {
|
||||
t.Fatalf("quality = %s/%s", agg.QualityStatus, agg.QualityReason)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClearBackfillTargetMileageClearsExactKey(t *testing.T) {
|
||||
db, mock, err := sqlmock.New()
|
||||
if err != nil {
|
||||
@@ -219,9 +282,9 @@ func TestWriteAggregatesSkipsBlankSourceBeforeClearingTarget(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddSamplesKeepsScanCandidatesNoPreviousBaseline(t *testing.T) {
|
||||
func TestAddSamplesUsesCurrentDayFirstSampleBaseline(t *testing.T) {
|
||||
loc := time.FixedZone("Asia/Shanghai", 8*3600)
|
||||
samples, err := stats.SamplesFromEnvelope(envelope.FrameEnvelope{
|
||||
firstSamples, err := stats.SamplesFromEnvelope(envelope.FrameEnvelope{
|
||||
Protocol: envelope.ProtocolJT808,
|
||||
VIN: "LA9GG64L7PBAF4001",
|
||||
Phone: "13307765812",
|
||||
@@ -234,18 +297,38 @@ func TestAddSamplesKeepsScanCandidatesNoPreviousBaseline(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("SamplesFromEnvelope() error = %v", err)
|
||||
}
|
||||
secondSamples, err := stats.SamplesFromEnvelope(envelope.FrameEnvelope{
|
||||
Protocol: envelope.ProtocolJT808,
|
||||
VIN: "LA9GG64L7PBAF4001",
|
||||
Phone: "13307765812",
|
||||
SourceEndpoint: "115.231.168.135:20215",
|
||||
EventTimeMS: time.Date(2026, 7, 8, 13, 0, 0, 0, loc).UnixMilli(),
|
||||
Fields: map[string]any{
|
||||
"jt808.location.total_mileage_km": 4129.9,
|
||||
},
|
||||
}, loc)
|
||||
if err != nil {
|
||||
t.Fatalf("SamplesFromEnvelope() error = %v", err)
|
||||
}
|
||||
aggregates := map[string]*metricAgg{}
|
||||
addSamples(aggregates, samples)
|
||||
addSamples(aggregates, firstSamples)
|
||||
addSamples(aggregates, secondSamples)
|
||||
if len(aggregates) != 1 {
|
||||
t.Fatalf("aggregate count = %d, want 1", len(aggregates))
|
||||
}
|
||||
for _, agg := range aggregates {
|
||||
if agg.QualityStatus != stats.QualityNoPreviousBaseline {
|
||||
t.Fatalf("quality = %q, want %q", agg.QualityStatus, stats.QualityNoPreviousBaseline)
|
||||
if agg.QualityStatus != stats.QualityOK {
|
||||
t.Fatalf("quality = %q, want %q", agg.QualityStatus, stats.QualityOK)
|
||||
}
|
||||
if agg.QualityReason != "scan_backfill_missing_previous_source" {
|
||||
if agg.QualityReason != "current_day_first_sample" {
|
||||
t.Fatalf("quality reason = %q", agg.QualityReason)
|
||||
}
|
||||
if agg.FirstKM != 4123.9 || agg.LatestKM != 4129.9 {
|
||||
t.Fatalf("km range = %v -> %v", agg.FirstKM, agg.LatestKM)
|
||||
}
|
||||
if agg.FirstEventTime != time.Date(2026, 7, 8, 12, 0, 0, 0, loc) {
|
||||
t.Fatalf("first event time = %v", agg.FirstEventTime)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user