141 lines
5.0 KiB
Python
141 lines
5.0 KiB
Python
#!/usr/bin/env python3
|
|
"""Independent verifier for the WIZARDS-TABLE roster extract (Extract lane).
|
|
|
|
Recomputes the expected table from WIZARDS-TABLE.md using only string/file
|
|
operations (does NOT import extract_wizard_roster.py) and diffs the replay
|
|
against the committed 2026-10-29-wizard-roster.{csv,json,md}.
|
|
|
|
Exit code IS the verdict: 0 = PASS, nonzero = FAIL.
|
|
"""
|
|
import csv
|
|
import io
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
REPO_ROOT = Path("/root/troupe-standup")
|
|
SRC = REPO_ROOT / "WIZARDS-TABLE.md"
|
|
CSV_ART = REPO_ROOT / "wizard-work" / "ledger" / "2026-10-29-wizard-roster.csv"
|
|
JSON_ART = REPO_ROOT / "wizard-work" / "ledger" / "2026-10-29-wizard-roster.json"
|
|
MD_ART = REPO_ROOT / "wizard-work" / "ledger" / "2026-10-29-wizard-roster.md"
|
|
|
|
FAILS = []
|
|
|
|
|
|
def check(name, cond, detail=""):
|
|
tag = "PASS" if cond else "FAIL"
|
|
line = "%s %s" % (tag, name)
|
|
if detail:
|
|
line += " " + detail
|
|
print(line)
|
|
if not cond:
|
|
FAILS.append(name)
|
|
|
|
|
|
def parse_table(source):
|
|
lines = source.read_text(encoding="utf-8").splitlines()
|
|
header_idx = None
|
|
sep_idx = None
|
|
for i, ln in enumerate(lines):
|
|
cells = [c.strip() for c in ln.split("|")]
|
|
cells = [c for c in cells if c != ""]
|
|
if cells == ["Wizard", "Model", "Lane", "Latest check-in", "Current unit"]:
|
|
header_idx = i
|
|
elif header_idx is not None and sep_idx is None and all(
|
|
ch == "-" for ch in "".join(cells)):
|
|
sep_idx = i
|
|
if header_idx is None or sep_idx is None:
|
|
return None
|
|
rows = []
|
|
for ln in lines[sep_idx + 1:]:
|
|
stripped = ln.strip()
|
|
# Stop at the next section heading (e.g. '## Lane signers ...').
|
|
if stripped.startswith("#"):
|
|
break
|
|
if not stripped:
|
|
continue
|
|
cells = [c.strip() for c in stripped.split("|")]
|
|
cells = [c for c in cells if c != ""]
|
|
if all(ch == "-" for ch in "".join(cells)):
|
|
break
|
|
if len(cells) != 5:
|
|
return None
|
|
rows.append(cells)
|
|
return rows
|
|
|
|
|
|
def main():
|
|
actual = parse_table(SRC)
|
|
if actual is None:
|
|
print("FAIL table header not found")
|
|
sys.exit(1)
|
|
check("re-derived table row count", len(actual) == 18, "rows=%d" % len(actual))
|
|
wizards = actual[:10]
|
|
lane_signers = [r[0] for r in actual[10:]]
|
|
check("10 wizard rows", len(wizards) == 10)
|
|
check("8 lane signer rows", len(lane_signers) == 8)
|
|
check("lane_signers", lane_signers == [
|
|
"forge", "ledger", "sol", "quill", "spark", "sphinx", "null", "zeph"],
|
|
repr(lane_signers))
|
|
|
|
# --- 1) CSV replay ---
|
|
expected_csv = io.StringIO()
|
|
writer = csv.DictWriter(expected_csv,
|
|
fieldnames=["Wizard", "Model", "Lane",
|
|
"Latest check-in", "Current unit"],
|
|
lineterminator="\n")
|
|
writer.writeheader()
|
|
for r in wizards:
|
|
writer.writerow({c: r[i] for i, c in enumerate(
|
|
["Wizard", "Model", "Lane", "Latest check-in", "Current unit"])})
|
|
got_csv = CSV_ART.read_text(encoding="utf-8")
|
|
if got_csv != expected_csv.getvalue():
|
|
FAILS.append("CSV replay mismatch")
|
|
print(" expected CSV head:\n" + expected_csv.getvalue()[:400])
|
|
print(" got CSV head:\n" + got_csv[:400])
|
|
else:
|
|
check("CSV replay", True)
|
|
|
|
# --- 2) Markdown replay ---
|
|
md_lines = MD_ART.read_text(encoding="utf-8").splitlines()
|
|
md_data = []
|
|
for l in md_lines:
|
|
segs = [p.strip() for p in l.split("|")]
|
|
segs = [p for p in segs if p != ""]
|
|
if len(segs) == 5 and segs[0] == "Wizard":
|
|
continue
|
|
if len(segs) == 5 and all(ch == "-" for ch in "".join(segs)):
|
|
continue
|
|
if l.startswith("| "):
|
|
md_data.append(l)
|
|
expected_md = ["| " + " | ".join(r) + " |" for r in wizards]
|
|
check("MD data rows replay", md_data == expected_md,
|
|
"got=%d expected=%d" % (len(md_data), len(expected_md)))
|
|
|
|
# --- 3) JSON replay ---
|
|
j = json.loads(JSON_ART.read_text(encoding="utf-8"))
|
|
check("JSON total_table_rows", j.get("total_table_rows") == 18,
|
|
repr(j.get("total_table_rows")))
|
|
check("JSON wizard_rows_extracted", j.get("wizard_rows_extracted") == 10,
|
|
repr(j.get("wizard_rows_extracted")))
|
|
check("JSON lane_signers",
|
|
j.get("lane_signers") == lane_signers, repr(j.get("lane_signers")))
|
|
check("JSON lane_signers_count", j.get("lane_signers_count") == 8,
|
|
repr(j.get("lane_signers_count")))
|
|
check("JSON wizards records match",
|
|
j.get("wizards") == [
|
|
{"Wizard": r[0], "Model": r[1], "Lane": r[2],
|
|
"Latest check-in": r[3], "Current unit": r[4]} for r in wizards],
|
|
repr(j.get("wizards")))
|
|
|
|
if FAILS:
|
|
print("\n%d CHECK(S) FAILED" % len(FAILS))
|
|
sys.exit(1)
|
|
print("\nALL CHECKS PASSED -- table re-derived from WIZARDS-TABLE.md; "
|
|
"CSV / MD / JSON replay clean")
|
|
sys.exit(0)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|