feat(stats): project elected mileage source
This commit is contained in:
@@ -12,6 +12,7 @@ const (
|
|||||||
QualityOK = "OK"
|
QualityOK = "OK"
|
||||||
QualityNoPreviousBaseline = "NO_PREVIOUS_BASELINE"
|
QualityNoPreviousBaseline = "NO_PREVIOUS_BASELINE"
|
||||||
QualityInvalidDelta = "INVALID_DELTA"
|
QualityInvalidDelta = "INVALID_DELTA"
|
||||||
|
maxSelectedDailyMileageKM = 1000
|
||||||
)
|
)
|
||||||
|
|
||||||
type SourceMileageSample struct {
|
type SourceMileageSample struct {
|
||||||
@@ -99,6 +100,28 @@ func UpsertSourceMileage(ctx context.Context, exec Execer, sample SourceMileageS
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ProjectDailyMileage(ctx context.Context, exec Execer, vin string, statDate string, protocol envelope.Protocol) error {
|
||||||
|
if exec == nil {
|
||||||
|
panic("stats execer must not be nil")
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(vin) == "" || strings.TrimSpace(statDate) == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if _, err := exec.ExecContext(ctx, clearSelectedSourceSQL, vin, statDate, string(protocol)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := exec.ExecContext(ctx, projectDailyMileageSQL,
|
||||||
|
vin,
|
||||||
|
statDate,
|
||||||
|
string(protocol),
|
||||||
|
maxSelectedDailyMileageKM,
|
||||||
|
); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err := exec.ExecContext(ctx, markSelectedSourceSQL, vin, statDate, string(protocol))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
const upsertSourceMileageSQL = `
|
const upsertSourceMileageSQL = `
|
||||||
INSERT INTO vehicle_daily_mileage_source
|
INSERT INTO vehicle_daily_mileage_source
|
||||||
(vin, stat_date, protocol, source_key, source_ip, source_endpoint, phone, device_id, platform_name,
|
(vin, stat_date, protocol, source_key, source_ip, source_endpoint, phone, device_id, platform_name,
|
||||||
@@ -148,3 +171,61 @@ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|||||||
quality_reason = VALUES(quality_reason),
|
quality_reason = VALUES(quality_reason),
|
||||||
updated_at = CURRENT_TIMESTAMP
|
updated_at = CURRENT_TIMESTAMP
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const clearSelectedSourceSQL = `
|
||||||
|
UPDATE vehicle_daily_mileage_source
|
||||||
|
SET is_selected = 0
|
||||||
|
WHERE vin = ? AND stat_date = ? AND protocol = ?
|
||||||
|
`
|
||||||
|
|
||||||
|
const projectDailyMileageSQL = `
|
||||||
|
INSERT INTO vehicle_daily_mileage
|
||||||
|
(vin, stat_date, protocol, daily_mileage_km,
|
||||||
|
first_total_mileage_km, latest_total_mileage_km,
|
||||||
|
trusted_source_key, trusted_phone, trusted_source_endpoint, sample_count)
|
||||||
|
SELECT
|
||||||
|
s.vin,
|
||||||
|
s.stat_date,
|
||||||
|
s.protocol,
|
||||||
|
s.daily_mileage_km,
|
||||||
|
s.first_total_mileage_km,
|
||||||
|
s.latest_total_mileage_km,
|
||||||
|
s.source_key,
|
||||||
|
s.phone,
|
||||||
|
s.source_endpoint,
|
||||||
|
s.sample_count
|
||||||
|
FROM vehicle_daily_mileage_source s
|
||||||
|
LEFT JOIN vehicle_data_source ds
|
||||||
|
ON ds.protocol = s.protocol AND ds.source_ip = s.source_ip
|
||||||
|
WHERE s.vin = ?
|
||||||
|
AND s.stat_date = ?
|
||||||
|
AND s.protocol = ?
|
||||||
|
AND s.quality_status = 'OK'
|
||||||
|
AND s.daily_mileage_km BETWEEN 0 AND ?
|
||||||
|
AND COALESCE(ds.enabled, 1) = 1
|
||||||
|
ORDER BY COALESCE(ds.trust_priority, 100),
|
||||||
|
s.sample_count DESC,
|
||||||
|
s.latest_event_time DESC,
|
||||||
|
s.source_key ASC
|
||||||
|
LIMIT 1
|
||||||
|
ON DUPLICATE KEY UPDATE
|
||||||
|
daily_mileage_km = VALUES(daily_mileage_km),
|
||||||
|
first_total_mileage_km = VALUES(first_total_mileage_km),
|
||||||
|
latest_total_mileage_km = VALUES(latest_total_mileage_km),
|
||||||
|
trusted_source_key = VALUES(trusted_source_key),
|
||||||
|
trusted_phone = VALUES(trusted_phone),
|
||||||
|
trusted_source_endpoint = VALUES(trusted_source_endpoint),
|
||||||
|
sample_count = VALUES(sample_count),
|
||||||
|
updated_at = CURRENT_TIMESTAMP
|
||||||
|
`
|
||||||
|
|
||||||
|
const markSelectedSourceSQL = `
|
||||||
|
UPDATE vehicle_daily_mileage_source s
|
||||||
|
JOIN vehicle_daily_mileage m
|
||||||
|
ON m.vin = s.vin
|
||||||
|
AND m.stat_date = s.stat_date
|
||||||
|
AND m.protocol = s.protocol
|
||||||
|
AND m.trusted_source_key = s.source_key
|
||||||
|
SET s.is_selected = 1
|
||||||
|
WHERE s.vin = ? AND s.stat_date = ? AND s.protocol = ?
|
||||||
|
`
|
||||||
|
|||||||
@@ -81,3 +81,35 @@ func TestUpsertSourceMileageSkipsBlankSourceIP(t *testing.T) {
|
|||||||
t.Fatalf("exec calls = %d, want 0", len(exec.calls))
|
t.Fatalf("exec calls = %d, want 0", len(exec.calls))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestProjectDailyMileageSelectsCandidateAndMarksSource(t *testing.T) {
|
||||||
|
exec := &recordingExec{}
|
||||||
|
err := ProjectDailyMileage(context.Background(), exec, "LA9GG64L7PBAF4001", "2026-07-08", envelope.ProtocolJT808)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ProjectDailyMileage() error = %v", err)
|
||||||
|
}
|
||||||
|
if len(exec.calls) != 3 {
|
||||||
|
t.Fatalf("exec calls = %d, want 3", len(exec.calls))
|
||||||
|
}
|
||||||
|
updateSources := exec.calls[0].query
|
||||||
|
projectFinal := exec.calls[1].query
|
||||||
|
markSelected := exec.calls[2].query
|
||||||
|
if !strings.Contains(updateSources, "UPDATE vehicle_daily_mileage_source") || !strings.Contains(updateSources, "is_selected = 0") {
|
||||||
|
t.Fatalf("first query should clear selected candidates: %s", updateSources)
|
||||||
|
}
|
||||||
|
for _, want := range []string{
|
||||||
|
"INSERT INTO vehicle_daily_mileage",
|
||||||
|
"FROM vehicle_daily_mileage_source s",
|
||||||
|
"LEFT JOIN vehicle_data_source ds",
|
||||||
|
"ORDER BY COALESCE(ds.trust_priority, 100)",
|
||||||
|
"s.quality_status = 'OK'",
|
||||||
|
"s.daily_mileage_km BETWEEN 0 AND",
|
||||||
|
} {
|
||||||
|
if !strings.Contains(projectFinal, want) {
|
||||||
|
t.Fatalf("project query missing %q: %s", want, projectFinal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !strings.Contains(markSelected, "UPDATE vehicle_daily_mileage_source s") || !strings.Contains(markSelected, "SET s.is_selected = 1") {
|
||||||
|
t.Fatalf("mark query should flag the elected source: %s", markSelected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user