# Task 2 Report: Candidate Mileage Schema And Writer ## Outcome - Implemented Task 2 in `go/vehicle-gateway/internal/stats`. - Added candidate mileage schema, sample-to-candidate mapping, and the upsert writer. ## RED Evidence The focused candidate tests failed before implementation because the new symbols did not exist: ```bash cd /Users/lingniu/project/ai-coding/lingniu-vehicle-ingest/go/vehicle-gateway go test ./internal/stats -run 'TestSourceKey|TestUpsertSourceMileage' -count=1 ``` Result: - `undefined: SourceKey` - `undefined: SourceMileageSample` - `undefined: QualityOK` - `undefined: UpsertSourceMileage` ## GREEN Evidence After implementation, the focused candidate tests passed: ```bash cd /Users/lingniu/project/ai-coding/lingniu-vehicle-ingest/go/vehicle-gateway go test ./internal/stats -run 'TestSourceKey|TestUpsertSourceMileage' -count=1 ``` Result: - `ok lingniu-vehicle-ingest/go/vehicle-gateway/internal/stats 0.556s` Package verification also passed: ```bash cd /Users/lingniu/project/ai-coding/lingniu-vehicle-ingest/go/vehicle-gateway go test ./internal/stats -count=1 ``` Result: - `ok lingniu-vehicle-ingest/go/vehicle-gateway/internal/stats 0.235s` ## Files Changed - `go/vehicle-gateway/internal/stats/schema.go` - `go/vehicle-gateway/internal/stats/daily_metric.go` - `go/vehicle-gateway/internal/stats/source_mileage.go` - `go/vehicle-gateway/internal/stats/source_mileage_test.go` ## Self-Review - The candidate schema matches the brief's table shape and indexes. - `SamplesFromEnvelope` now carries `EventTime` and `DeviceID`, which the new candidate mapping needs. - The upsert SQL is focused on the candidate table and reuses the shared `Execer` interface. - No unrelated stats files were modified. ## Review Fix Addendum - Preserved manual `platform_name` values when the candidate upsert receives blank runtime input by switching to `COALESCE(NULLIF(TRIM(VALUES(platform_name)), ''), platform_name)`. - Bootstrapped `vehicle_data_source`, `vehicle_daily_mileage_source`, and `vehicle_daily_mileage` before running alter statements in `Writer.EnsureSchema`. - Added a defensive blank `SourceIP` guard in `UpsertSourceMileage` so malformed candidate rows are skipped instead of written. - Removed the SQL comment that existed only to satisfy a string-match test and updated the focused assertions to check the real `daily_mileage_km` and `platform_name` SQL expressions. - Verification run: - `go test ./internal/stats -run 'TestUpsertSourceMileage|TestWriterEnsuresSchema' -count=1` - `go test ./internal/stats -count=1` - Both passed.