#!/usr/bin/env python3 """Emit standards/QA checklist JSON + markdown for the PUB report appendix.""" from __future__ import annotations import json import sys from datetime import date from pathlib import Path ROOT = Path(__file__).resolve().parents[1] REPO = ROOT.parent OUT_DIR = ROOT / "dist" / "report" OUT_DIR.mkdir(parents=True, exist_ok=True) sys.path.insert(0, str(ROOT)) from regulator_paper_assumptions import REGISTRY, v # noqa: E402 from system_b_ledger import build_ledger # noqa: E402 def check(name: str, ok: bool, detail: str = "") -> dict: return {"name": name, "pass": bool(ok), "detail": detail} def main() -> int: ledger = build_ledger() report = ledger.pass_report() figs = ROOT / "rendered" / "report-v2" required_figs = [f"fig{i}_" for i in range(1, 19)] fig_files = list(figs.glob("fig*.png")) if figs.exists() else [] fig_ok = figs.exists() and len(fig_files) >= 18 items = [ check("system_b_ledger_pass", report["passed"], report["headline"]), check( "scenarios_413_688_788", ( v("scenario_conservative_mw") == 413 and v("scenario_reference_mw") == 688 and v("scenario_full_mw") == 788 ), f"{v('scenario_conservative_mw')}/{v('scenario_reference_mw')}/{v('scenario_full_mw')}", ), check( "hub_net_38_thermal_50_effluent_stage", v("hub_net_electric_mw") == 38 and v("hub_thermal_mwth") == 50, f"net={v('hub_net_electric_mw')} thermal={v('hub_thermal_mwth')}", ), check( "assumptions_yaml_present", (ROOT / "assumptions.yaml").is_file(), str(ROOT / "assumptions.yaml"), ), check( "standards_appendix_present", (ROOT / "spirited-energy-standards-qa-appendix.md").is_file(), ), check( "design_tokens_present", (REPO / "report" / "design-tokens.css").is_file() or (ROOT / "report" / "design-tokens.css").is_file(), ), check( "report_v2_figures_f1_f18", fig_ok, f"{len(fig_files)} png files in {figs}", ), check( "tests_passing_metadata", int(v("tests_passing")) >= 68, f"registry tests_passing={v('tests_passing')}", ), check( "executive_brief_present", (ROOT / "spirited-energy-executive-regulatory-brief.md").is_file(), ), check( "site_report_dir", (REPO / "report").is_dir() and (REPO / "report" / "index.html").is_file(), str(REPO / "report"), ), check( "pub_deck_dir", (REPO / "deck").is_dir() and (REPO / "deck" / "index.html").is_file(), str(REPO / "deck"), ), check( "math_addendum_pdf", (REPO / "report" / "spirited-energy-math-addendum.pdf").is_file() or (OUT_DIR / "spirited-energy-math-addendum.pdf").is_file(), "report/ or research/dist/report/", ), check( "iso19115_sources_md", (ROOT / "outputs" / "red-river-heathost-gis" / "docs" / "sources.md").is_file(), "GIS layer inventory with CRS and licence", ), check( "iso19157_missing_not_zero", "test_missing_discharge_is_not_treated_as_zero" in (ROOT / "outputs" / "red-river-heathost-gis" / "tests" / "test_water_metrics.py").read_text(), "missing discharge must not become zero", ), check( "iso8000_yaml_master", (ROOT / "assumptions.yaml").is_file() and "elcc_bess_illustrative" in (ROOT / "assumptions.yaml").read_text(), "613 haircut factors live in the registry", ), check( "appendix_live_hub_is_effluent_stage", "38 MW" in (ROOT / "spirited-energy-standards-qa-appendix.md").read_text() and "thermal ledger remains **145 MWth**" not in (ROOT / "spirited-energy-standards-qa-appendix.md").read_text(), "current gates are 38 MW / 50 MWth, not 111 / 145", ), check( "ledger_headline_not_proven_cover", "undemonstrated" in report["headline"].lower() and "covers 600 MW shortfall" not in report["headline"], report["headline"], ), check( "no_formal_iso_cert_claim_in_appendix", "not claim formal iso" in (ROOT / "spirited-energy-standards-qa-appendix.md").read_text().lower().replace("*", ""), "honesty statement present", ), ] # Presence of each F1–F18 stem for i in range(1, 19): matches = list(figs.glob(f"fig{i}_*.png")) if figs.exists() else [] items.append(check(f"figure_f{i}", bool(matches), matches[0].name if matches else "missing")) passed = sum(1 for i in items if i["pass"]) failed = [i for i in items if not i["pass"]] payload = { "date": str(date.today()), "claim": "method_conformance_not_formal_certification", "passed": passed, "total": len(items), "all_pass": len(failed) == 0, "system_b_headline": report["headline"], "registry_keys": len(REGISTRY), "items": items, } json_path = OUT_DIR / "standards_checklist.json" md_path = OUT_DIR / "standards_checklist.md" json_path.write_text(json.dumps(payload, indent=2) + "\n") lines = [ f"# Standards checklist — {payload['date']}", "", f"**Result:** {'PASS' if payload['all_pass'] else 'FAIL'} ({passed}/{len(items)})", "", "Claim: method conformance (ISO 19115/19157/8000-style + PUB + WCAG practices), **not** formal ISO certification.", "", f"System B: {report['headline']}", "", "| Check | Pass | Detail |", "|---|---|---|", ] for i in items: lines.append(f"| {i['name']} | {'yes' if i['pass'] else 'NO'} | {i['detail']} |") lines.append("") md_path.write_text("\n".join(lines) + "\n") print(f"Standards checklist: {'PASS' if payload['all_pass'] else 'FAIL'} ({passed}/{len(items)})") print(f" wrote {json_path}") print(f" wrote {md_path}") for i in failed: print(f" FAIL {i['name']}: {i['detail']}") return 0 if payload["all_pass"] else 1 if __name__ == "__main__": raise SystemExit(main())