pipeline_miami/verify/reconcile.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.
"""Base case (ii): served DB vs an independent re-read of the exact ingested file.
Run: python -m pipeline_miami.verify.reconcile [--metric KEY] [--all]
Writes docs/qa_reports/miami/<metric_key>__validation.{md,json}.
"""
from __future__ import annotations
import argparse
import json
import sys
import yaml
from pipeline_miami.verify import common
from pipeline_miami.verify.connectors import served_db, source_file
def read_source(c: dict, year: str) -> dict:
reader = getattr(source_file, c["reader"])
args = dict(c.get("reader_args", {}))
if c["reader"] == "abc_book":
return reader(args["source_id"], args["field"])
if c["reader"] == "srs":
return reader(year, args["source_id"])
return reader(year, **args)
def reconcile_one(c: dict) -> dict:
metric = c["metric_key"]
tol = float(c.get("tolerance_abs", 0.0))
served_allow = int(c.get("served_only_allowance", 0))
source_allow = c.get("source_only_allowance")
source_allow = int(source_allow) if source_allow is not None else None
served_all = served_db.fetch(metric)
matches = 0
mismatches: list = []
suppressed = 0
served_only: list = []
source_only: list = []
n_common = 0
for year in c["years"]:
source = {k: v for k, v in read_source(c, year).items() if k[1] == year}
served = {k: v for k, v in served_all.items() if k[1] == year}
sk, fk = set(served), set(source)
served_only += sorted(f"{k[0]} {k[1]}" for k in sk - fk)
source_only += sorted(f"{k[0]} {k[1]}" for k in fk - sk)
for k in sk & fk:
n_common += 1
s, f = served[k], source[k]
if s["suppressed"] or f["suppressed"]:
if s["suppressed"] == f["suppressed"]:
suppressed += 1
else:
mismatches.append((k, s["value"], f["value"], "suppression flag differs"))
continue
if s["value"] is None and f["value"] is None:
matches += 1
elif s["value"] is None or f["value"] is None:
mismatches.append((k, s["value"], f["value"], None))
elif abs(s["value"] - f["value"]) <= tol + 1e-9:
matches += 1
else:
mismatches.append((k, s["value"], f["value"], s["value"] - f["value"]))
return {
"metric_key": metric, "subgroup": "ALL", "population_cut": "n/a", "years": c["years"], "tolerance_abs": tol,
"n_served": sum(1 for k in served_all if k[1] in c["years"]), "n_common": n_common, "matches": matches, "suppressed": suppressed,
"served_only_count": len(served_only), "source_only_count": len(source_only),
"served_only_examples": served_only[:20], "source_only_examples": source_only[:20],
"agreement_rate": matches / (matches + len(mismatches)) if (matches + len(mismatches)) else None,
"served_only_allowance": served_allow, "source_only_allowance": source_allow,
# Served-only cells (the loader wrote a cell the re-read cannot find) fail unless the component
# documents an allowance; source-only cells (the file has a school the loader does not serve —
# schools outside the directory universe) fail only above the component's allowance, unlimited by default.
"status": "PASS" if (matches and not mismatches and len(served_only) <= served_allow and (source_allow is None or len(source_only) <= source_allow)) else "MISMATCH",
"mismatches": mismatches,
}
def render(s: dict) -> str:
out = [f"# Validation (ii) base case — {s['metric_key']}\n", "Served DB cell-by-cell vs an independent re-read of the exact ingested file(s).\n", "## Summary\n"]
for k in ["metric_key", "years", "tolerance_abs", "n_served", "n_common", "matches", "suppressed", "served_only_count", "served_only_allowance", "source_only_count", "source_only_allowance", "agreement_rate", "status"]:
out.append(f"- {k}: {s[k]}")
if s["served_only_examples"]:
out += ["\n## Served-only examples\n"] + [f"- {e}" for e in s["served_only_examples"]]
if s["source_only_examples"]:
out += ["\n## Source-only examples\n"] + [f"- {e}" for e in s["source_only_examples"]]
if s["mismatches"]:
out.append(f"\n## Mismatches ({len(s['mismatches'])})\n")
out += [f"- {k[0]} {k[1]}: served={a} source={b} Δ={d}" for (k, a, b, d) in s["mismatches"][:50]]
return "\n".join(out)
def main() -> None:
ap = argparse.ArgumentParser()
ap.add_argument("--metric")
ap.add_argument("--all", action="store_true")
a = ap.parse_args()
common.ensure_qa_dir()
comps = yaml.safe_load(open(common.COMPONENTS_YAML))
if a.metric:
comps = [c for c in comps if c["metric_key"] == a.metric]
elif not a.all:
sys.exit("pass --metric KEY or --all")
for c in comps:
s = reconcile_one(c)
(common.QA_REPORTS_DIR / f"{c['metric_key']}__validation.md").write_text(render(s))
js = {k: v for k, v in s.items() if k != "mismatches"}
js["n_mismatches"] = len(s["mismatches"])
(common.QA_REPORTS_DIR / f"{c['metric_key']}__validation.json").write_text(json.dumps(js, indent=2))
print(f"{c['metric_key']:36s} {s['status']:9s} common={s['n_common']:>5d} matches={s['matches']:>5d} mismatches={len(s['mismatches']):>4d} suppressed={s['suppressed']:>4d} served_only={s['served_only_count']:>3d} source_only={s['source_only_count']:>3d}")
if __name__ == "__main__":
main()