# 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.
