pipeline_miami/verify/run_all.py
Served verbatim from the project repository. Internal working document conventions apply: documents may reference file paths, branch names, and findings-ledger anchors from the repo.
"""Run the closure-track harness end to end and write the full-QA verdict
(docs/qa_reports/miami/__enrollment_full_qa.{json,md}) that gates the
enrollment analysis page.
Run: python -m pipeline_miami.verify.run_all [--summary-only]
"""
from __future__ import annotations
import datetime as dt
import json
import subprocess
import sys
from pipeline_miami.verify import common
STEPS = [
("A_base_case", [sys.executable, "-m", "pipeline_miami.verify.reconcile", "--all"]),
("B_completeness", [sys.executable, "-m", "pipeline_miami.verify.completeness"]),
("C_cross_publisher", [sys.executable, "-m", "pipeline_miami.verify.cross_publisher_enrollment"]),
("D_computed", [sys.executable, "-m", "pipeline_miami.verify.reconcile_computed"]),
("E_invariants", [sys.executable, "-m", "pipeline_miami.verify.served_invariants"]),
# Outcomes track (reported beside the closure verdict, does not gate it)
("O_cross_publisher_absenteeism", [sys.executable, "-m", "pipeline_miami.verify.cross_publisher_absenteeism"]),
("O_cross_publisher_race", [sys.executable, "-m", "pipeline_miami.verify.cross_publisher_race"]),
("O_residual_checks", [sys.executable, "-m", "pipeline_miami.verify.residual_checks"]),
]
def main() -> None:
common.ensure_qa_dir()
# --summary-only: rebuild the verdict files from the per-component reports already on disk
# (after re-running single components with `reconcile --metric KEY`).
if "--summary-only" not in sys.argv:
for name, cmd in STEPS:
print(f"== {name} ==")
subprocess.run(cmd, check=True, cwd=common.REPO_ROOT)
q = common.QA_REPORTS_DIR
import yaml
comps_all = yaml.safe_load(open(common.COMPONENTS_YAML))
comps = [c for c in comps_all if c.get("track", "closure") == "closure"]
outcomes = [c for c in comps_all if c.get("track") == "outcomes"]
base = {c["metric_key"]: json.load(open(q / f"{c['metric_key']}__validation.json")) for c in comps}
# Outcome metrics are validated separately; their summary is written beside the closure-track verdict.
ob = {c["metric_key"]: json.load(open(q / f"{c['metric_key']}__validation.json")) for c in outcomes}
xabs = json.load(open(q / "__cross_publisher_absenteeism.json"))["summary"]
xrace = json.load(open(q / "__cross_publisher_race.json"))
rz = json.load(open(q / "__residual_checks.json"))
inv = json.load(open(q / "__served_invariants.json"))
(q / "__outcomes_validation.json").write_text(json.dumps({
"base_case": {k: {"status": v["status"], "years": v["years"], "n_common": v["n_common"], "matches": v["matches"], "n_mismatches": v["n_mismatches"], "suppressed": v["suppressed"]} for k, v in ob.items()},
"cross_publisher_absenteeism": xabs,
"cross_publisher_race": {"verdict": xrace["summary"]["verdict"], "by_metric": {k: {kk: vv for kk, vv in v.items() if kk != "largest"} for k, v in xrace["by_metric"].items()}},
"residual_checks": {"verdict": rz["verdict"], "R1_max_abs_dz": rz["R1_independent_refit"]["max_abs_dz"], "R1_max_abs_dn": rz["R1_independent_refit"].get("max_abs_dn"), "R1_fits": rz["R1_independent_refit"]["fits"], "R2": {k: v for k, v in rz["R2_served_equals_committed"].items()}, "R3": rz["R3_sanity"]},
"served_invariants": {"verdict": inv["verdict"], "hard": inv["hard"], "shift_alarms": {"n": inv["shift_alarms"]["n"], "unexplained": inv["shift_alarms"]["unexplained"]}},
"overall": "PASS" if all(v["status"] == "PASS" for v in ob.values()) and xabs["verdict"] == "PASS" and xrace["summary"]["verdict"] == "PASS" and rz["verdict"] == "PASS" and inv["verdict"] == "PASS" else "REVIEW",
}, indent=2, default=str))
(q / "__outcomes_validation.md").write_text(
"# Validation — outcome metrics\n\n## (ii) Base case\n\n| metric | years | common | matches | mismatches | suppressed | status |\n|---|---|---:|---:|---:|---:|---|\n"
+ "\n".join(f"| `{k}` | {', '.join(v['years'])} | {v['n_common']} | {v['matches']} | {v['n_mismatches']} | {v['suppressed']} | {v['status']} |" for k, v in ob.items())
+ f"\n\n## (iii-b) Cross-publisher — FLDOE absent-10%+ vs EDFacts DG814, {xabs['year']}\n\nVerdict: **{xabs['verdict']}** — no-PK schools within ±1: {xabs['no_pk']['within_1']}/{xabs['no_pk']['n']} ({xabs['no_pk']['within_1_share'] * 100:.1f}%); PK schools' gap = {xabs['with_pk']['gap_on_pk']['slope']:.3f} × PK membership (r = {xabs['with_pk']['gap_on_pk']['r']:.2f}). Detail: `__cross_publisher_absenteeism.md`.\n"
+ f"\n## (iii-b) Cross-publisher — FLDOE race shares vs NCES CCD\n\nVerdict: **{xrace['summary']['verdict']}** — " + "; ".join(f"{k}: {v['within_2pp_share'] * 100:.1f}% within 2 pp (median Δ {v['median_delta']:+.2f})" for k, v in xrace['by_metric'].items()) + ". Detail: `__cross_publisher_race.md`.\n"
+ f"\n## Residual-z checks\n\nVerdict: **{rz['verdict']}** — independent N, eligibility and refit: max |Δz| {rz['R1_independent_refit']['max_abs_dz']:.1e} over {rz['R1_independent_refit']['fits']} fits; served = committed: {rz['R2_served_equals_committed']['matched']} cells, {rz['R2_served_equals_committed']['csv_only']} CSV-only, {rz['R2_served_equals_committed']['served_only']} served-only. Detail: `__residual_checks.md`.\n"
+ f"\n## (iv) Served-value invariants\n\nVerdict: **{inv['verdict']}** — range violations {inv['hard']['I1_range_violations']}, identity violations {inv['hard']['I2_identity_violations']}, stale rows {sum(r['rows'] for r in inv['hard']['I3_stale_rows'])}, shift alarms {inv['shift_alarms']['n']} ({inv['shift_alarms']['unexplained']} unexplained). Detail: `__served_invariants.md`.\n")
comp = json.load(open(q / "__completeness.json"))
cross = json.load(open(q / "__cross_publisher_enrollment.json"))
computed = json.load(open(q / "__computed_value.json"))
a_ok = all(b["status"] == "PASS" for b in base.values())
b_ok = all(r["source_only_in_directory"] == 0 and r["served_only_cells"] == 0 for r in comp["rows"])
c_ok = cross["summary"]["verdict"] == "PASS"
d_ok = all(r["mismatches"] == 0 for r in computed)
e_ok = inv["verdict"] != "FAIL" # hard rules (range, identity, ownership) gate; shift alarms are listed for review
verdict = {
"generated_at": dt.datetime.now(dt.timezone.utc).isoformat(),
"verdict": {"overall": "PASS" if (a_ok and b_ok and c_ok and d_ok and e_ok) else "FAIL", "A_base_case": a_ok, "B_completeness": b_ok, "C_cross_publisher": c_ok, "D_computed": d_ok, "E_invariants": e_ok},
"sections": {
"A_base_case": {k: {"status": v["status"], "years": v["years"], "n_common": v["n_common"], "matches": v["matches"], "n_mismatches": v["n_mismatches"], "suppressed": v["suppressed"], "served_only": v["served_only_count"], "source_only": v["source_only_count"]} for k, v in base.items()},
"B_completeness": comp,
"C_cross_publisher": cross["summary"],
"D_computed": [{k: v for k, v in r.items() if k != "examples"} for r in computed],
"E_invariants": {"verdict": inv["verdict"], "hard": inv["hard"], "shift_alarms": {"n": inv["shift_alarms"]["n"], "unexplained": inv["shift_alarms"]["unexplained"]}},
},
}
(q / "__enrollment_full_qa.json").write_text(json.dumps(verdict, indent=2, default=str))
out = [f"# QA — Miami-Dade enrollment and capacity: served cells vs sources\n", f"Generated: {verdict['generated_at']}\n", f"## Verdict: **{verdict['verdict']['overall']}**\n",
"| Section | Result |", "|---|---|",
f"| A. Base case (served DB vs re-read of each ingested file, {len(base)} components) | {'PASS' if a_ok else 'FAIL'} |",
f"| B. Completeness (every source cell served; every served cell in source) | {'PASS' if b_ok else 'FAIL'} |",
f"| C. Cross-publisher (FLDOE membership vs NCES CCD) | {'PASS' if c_ok else 'FAIL'} |",
f"| D. Computed values (derived metrics recomputed) | {'PASS' if d_ok else 'FAIL'} |",
f"| E. Served-value invariants (ranges, identities, ownership gate; shift alarms reported) | {inv['verdict']} ({inv['shift_alarms']['unexplained']} unexplained shift alarms) |", "", "## A. Base case\n", "| metric | years | common | matches | mismatches | suppressed |", "|---|---|---:|---:|---:|---:|"]
out += [f"| `{k}` | {', '.join(v['years'])} | {v['n_common']} | {v['matches']} | {v['n_mismatches']} | {v['suppressed']} |" for k, v in base.items()]
(q / "__enrollment_full_qa.md").write_text("\n".join(out))
print(json.dumps(verdict["verdict"], indent=1))
if __name__ == "__main__":
main()