feat(reporting): real AFCARS data extraction (collect + map + materialize child records)

Summary

Implement real AFCARS data extraction so POST /v1/reporting/afcars/generate produces a genuine, validated federal submission instead of an honest-but-empty draft stub.

Today (post-#762) generate_afcars creates an honest draft submission with record_count = 0 and cannot advance the federal workflow (there is no draft → validated path) or be transmitted (zero-record transmit gate). This issue makes generate actually collect, map, materialize, and validate AFCARS records — the AFCARS analogue of the NCANDS extraction trio (#654 (closed) row mapping, #655 (closed) collect+materialize, #656 (closed) TSV emitter).

Scope

  • Collect foster-care data from craig-cases / craig-placement / craig-financial (the sources AFCARS reports on) for the reporting period, via the established service-to-service pattern (mirror generate_ncands, services/craig-reporting/src/api/ncands.rs).
  • Map each child/placement to an AFCARS record per the federal AFCARS 2020 element set (mirror the NcandsChildRow mapping from #654 (closed); wire federal-mapping resolution per #583).
  • Materialize the rows into a per-submission table idempotently (delete-then-insert on submission_id, as NCANDS does), so record_count == COUNT(afcars_child_rows).
  • Validate field completeness → validation_errors; only then set status validated (restoring the draft → validated path, which #762 (closed) intentionally left unbuilt).
  • Emit the real AFCARS flat file in build_afcars_tsv (child records, not just the metadata row) — currently a header + one metadata row.

Acceptance criteria

  • generate_afcars fetches + maps + materializes real child records; record_count reflects the materialized row count (not a hardcoded 0).
  • A submission with records reaches validated, then can move validated → reviewed → approved → transmitted; a zero-record period stays draft (or surfaces validation errors) and remains non-transmittable.
  • build_afcars_tsv emits one row per child record in the federal column layout.
  • The inline // see #NNN reference in generate_afcars is removed once extraction lands.
  • Restore end-to-end lifecycle integration coverage (generate-with-data → review → approve → transmit) that #762 (closed) could not keep while generate was a stub.
  • Regression + property tests for the mapping (mirror the NCANDS test coverage).

References

  • Blocks the honest-stub fix #762 (closed) (fix(reporting): stop stamping zero-record AFCARS files "validated").
  • Mirrors the NCANDS extraction trio: #654 (closed), #655 (closed), #656 (closed).
  • Federal-mapping resolution: #583.
  • Source: 2026-06-28 automated code-quality audit finding reporting-afcars-hollow-stub-validated — the extraction half of that finding (the "stop lying" half shipped in #762 (closed)).