# -*- coding: utf-8 -*-
"""build_all.py — regenerate every deliverable from 01_System/project_data.py.
Order matters: diagrams and the model feed the study and the deck; everything feeds the
transfer pack; the checker suite runs last and is the gate (GOV-F9.10).
GOV-F4.2: no step may depend on a path that exists only on the machine that built it. The two
office-automation steps below are OPTIONAL and are skipped with a warning where the tool is
absent, rather than killing the build for the next operator (DEF-016).
"""
import os, shutil, subprocess, sys
HERE = os.path.dirname(os.path.abspath(__file__))
XLSX = os.path.join("..", "05_Outputs", "NDIS_and_Aged_Care_Financial_Model_v2.0.xlsx")
DOCX = os.path.join("..", "05_Outputs", "NDIS_and_Aged_Care_Business_Enabling_Study_v2.0.docx")
def _find(*candidates):
"""Return the first candidate that exists, or None. Overridable by environment variable."""
for c in candidates:
if not c:
continue
if os.path.isabs(c) and os.path.exists(c):
return c
w = shutil.which(c)
if w:
return w
return None
def _glob_first(*patterns):
"""Skill directories are re-keyed by the host between sessions, so a hard-coded path to a
helper script stops resolving without anything in this project having changed. Search for the
script by name instead of by address (DEF-016 taught this the first time; the path moved
again on 2026-09-07, which is why this now globs)."""
import glob
for pat in patterns:
hits = sorted(g for g in glob.glob(pat, recursive=True) if "/.trash/" not in g)
if hits:
return hits[0]
return None
RECALC = _find(os.environ.get("NDIS_RECALC")) or _glob_first(
"/mnt/skills/**/xlsx/scripts/recalc.py",
"/root/.claude/skills/**/xlsx/scripts/recalc.py")
SOFFICE = _find(os.environ.get("NDIS_SOFFICE")) or _glob_first(
"/mnt/skills/**/docx/scripts/office/soffice.py",
"/root/.claude/skills/**/docx/scripts/office/soffice.py") or _find("soffice", "libreoffice")
STEPS = [
("Governance projections (txt + core)", [sys.executable, "build_governance.py"], True),
("Registers (xlsx + csv projections)", [sys.executable, "build_registers.py"], True),
("Diagrams (D1-D10)", [sys.executable, "build_diagrams.py"], True),
("Financial model", [sys.executable, "build_financial_model.py"], True),
("Recalculate the workbook",
([sys.executable, RECALC, XLSX, "120"] if RECALC else None), False),
("Pre-build check run (gives the study a real log to cite)",
[sys.executable, os.path.join("checkers", "run_checks.py")], False),
("Study (docx)", [sys.executable, "build_study.py"], True),
("Model export for the deck (model_export.json)",
[sys.executable, "export_model_json.py"], True),
("Decision pack (pptx)", (["node", "build_deck.js"] if shutil.which("node") else None), False),
("Study PDF render",
([sys.executable, SOFFICE, "--headless", "--convert-to", "pdf",
"--outdir", os.path.join("..", "05_Outputs"), DOCX]
if (SOFFICE and SOFFICE.endswith(".py")) else
([SOFFICE, "--headless", "--convert-to", "pdf",
"--outdir", os.path.join("..", "05_Outputs"), DOCX] if SOFFICE else None)), False),
("System breakdown (GOV-B4.10)", [sys.executable, "build_system_breakdown.py"], True),
("Business plan twin (business_plan.json)",
[sys.executable, "build_business_plan_json.py"], True),
("Business plan A3 page (validated, then generated)",
[sys.executable, os.path.join("portable", "templates", "business_plan",
"build_business_plan.py")], True),
("Dashboard and Help Hub", [sys.executable, "build_web.py"], True),
("README, threat model, transfer pack", [sys.executable, "build_handover.py"], True),
]
def main():
skipped = []
for label, cmd, required in STEPS:
if cmd is None:
print("\n=== %s -- SKIPPED, tool not found on this machine" % label)
skipped.append(label)
continue
print("\n=== %s" % label)
r = subprocess.run(cmd, cwd=HERE)
if r.returncode != 0:
if required:
print("BUILD FAILED at: %s" % label); sys.exit(r.returncode)
print(" (optional step failed; continuing)")
skipped.append(label)
if skipped:
print("\nOptional steps skipped or failed: %s" % ", ".join(skipped))
print("Everything else was regenerated. The delivered .xlsx keeps its previously cached values"
" and the .pdf keeps its previous render.")
print("\n=== Placement sweep (GOV-F3.10 — debris archived with a hash manifest, never deleted)")
subprocess.run([sys.executable, "tidy.py", "--ensure", "--sweep"], cwd=HERE)
print("\n=== Source manifest gate (GOV-D1.13 — hash every source of truth)")
subprocess.run([sys.executable, "build_source_manifest.py", "--gate"], cwd=HERE)
print("\n=== Checker suite (the gate)")
r = subprocess.run([sys.executable, os.path.join("checkers", "run_checks.py")], cwd=HERE)
sys.exit(r.returncode)
if __name__ == "__main__":
main()