Files
troupe-standup/wizard-work/ledger/extract_live_wizard_roster.py
T

236 lines
8.6 KiB
Python

#!/usr/bin/env python3
"""Extract the live wizard roster (2026-10-29) as a data table.
Lane: Extract (data extraction and tables).
Sources (real, from this checkout host):
1. WIZARDS-TABLE.md -- canonical wizard roster (name, model, lane).
2. checkins/ -- newest check-in per wizard (max date in filename).
3. git log -- newest commit per wizard (author-date).
Outputs (all written under OUT, default /root/troupe-standup/wizard-work/ledger):
live-wizard-roster-2026-10-29.tsv -- summary table (TSV)
live-wizard-roster-2026-10-29.csv -- full file manifest (CSV)
live-wizard-roster-2026-10-29.md -- markdown table (human artifact)
live-wizard-roster-2026-10-29.json -- manifest + tallies
The CHECK file (CHECK-live-wizard-roster-2026-10-29) re-derives every row from
the three sources with independent logic and diffs against the committed TSV.
The verdict is the script's exit code: 0 = correct, nonzero = wrong.
"""
import csv
import json
import os
import subprocess
from collections import defaultdict
from datetime import datetime, timezone
from pathlib import Path
REPO_ROOT = Path(os.environ.get("WIZARDWORK_ROOT", "/root/troupe-standup"))
WIZARD_WORK = REPO_ROOT / "wizard-work" / "ledger"
OUT = Path(os.environ.get("WIZARD_ROSTER_OUT", str(WIZARD_WORK)))
TABLE = REPO_ROOT / "WIZARDS-TABLE.md"
CHKINS_DIR = REPO_ROOT / "checkins"
SUMMARY_NAME = "live-wizard-roster-2026-10-29.tsv"
MANIFEST_NAME = "live-wizard-roster-2026-10-29.csv"
MD_NAME = "live-wizard-roster-2026-10-29.md"
JSON_NAME = "live-wizard-roster-2026-10-29.json"
# Canonical wizard names, fixed by WIZARDS-TABLE.md roster table.
WIZARDS = [
"Timmy",
"Zephyrus Sparkborn",
"Cassian Assayborn",
"Merlin",
"Mercury",
"Gemini",
"Argus",
"Volt",
"Sentinel",
"Beam",
]
# checkins/ filename slug -> canonical wizard (prefix rules first).
def slug_to_wizard(slug):
if slug.startswith("cassian"):
return "Cassian Assayborn"
return {
"zephyrus": "Zephyrus Sparkborn",
"merlin": "Merlin",
"mercury": "Mercury",
"gemini": "Gemini",
"volt": "Volt",
"sentinel": "Sentinel",
}.get(slug)
# git log author string -> canonical wizard (author casing varies in the repo).
AUTHOR_TO_WIZARD = {}
for _w in WIZARDS:
for _a in [_w.lower(), _w]:
AUTHOR_TO_WIZARD[_a] = _w
for _w, _aliases in {
"Cassian Assayborn": ["Cassian", "Cassian Assayborn"],
"Zephyrus Sparkborn": ["zephyrus sparkborn", "zephyrus-sparkborn"],
}.items():
for _a in _aliases:
AUTHOR_TO_WIZARD[_a.lower()] = _w
def canonical_wizard(author):
return AUTHOR_TO_WIZARD.get(author.lower())
def parse_roster():
"""Return {wizard: (model, lane, latest_checkin_in_table, current_unit)}"""
rows = {}
with open(TABLE, encoding="utf-8") as fh:
lines = fh.read().splitlines()
in_table = False
for line in lines:
if line.startswith("| Wizard |"):
in_table = True
continue
if in_table:
if not line.startswith("|"):
break
cells = [c.strip() for c in line.split("|") if c.strip()]
if len(cells) == 5 and cells[0] != "Wizard":
rows[cells[0]] = {
"model": cells[1],
"lane": cells[2],
"latest_checkin": cells[3],
"current_unit": cells[4],
}
return rows
def newest_checkin_per_wizard():
"""{wizard: 'YYYY-MM-DD' or None}"""
cur = {}
for name in WIZARDS:
cur[name] = None
if not CHKINS_DIR.is_dir():
return cur
for fh in CHKINS_DIR.iterdir():
if not fh.is_file() or not fh.name.endswith(".md"):
continue
parts = fh.name[:-3].split("-", 3)
if len(parts) < 4:
continue
date, slug = "-".join(parts[:3]), parts[3]
w = slug_to_wizard(slug)
if w is None:
continue
if cur[w] is None or date > cur[w]:
cur[w] = date
return cur
def newest_commit_per_wizard():
"""{wizard: 'HASH DATE' or None}"""
cur = {w: None for w in WIZARDS}
out = subprocess.run(
["git", "log", "--format=%an%x00%H%x00%ad", "--date=short"],
cwd=REPO_ROOT,
capture_output=True,
text=True,
).stdout
best = defaultdict(lambda: None) # wizard -> (date, hash)
for line in out.splitlines():
if "\x00" not in line:
continue
author, hsh, date = line.split("\x00", 2)
w = canonical_wizard(author)
if w is None or w not in cur:
continue
prev = best[w]
if prev is None or date > prev[0]:
best[w] = (date, hsh)
for w in WIZARDS:
if best[w] is not None:
cur[w] = best[w][1] + " " + best[w][0]
return cur
def build_rows():
roster = parse_roster()
checkins = newest_checkin_per_wizard()
commits = newest_commit_per_wizard()
rows = []
for w in WIZARDS:
r = roster.get(w, {})
rows.append({
"wizard": w,
"model": r.get("model", "—"),
"lane": r.get("lane", "—"),
"latest_checkin": checkins[w] or "— (no check-in)",
"newest_commit": commits[w] or "— (no commit)",
})
return rows
def main():
rows = build_rows()
# --- TSV (summary) ------------------------------------------------------
tsv_path = OUT / SUMMARY_NAME
with open(tsv_path, "w", encoding="utf-8", newline="") as fh:
w = csv.writer(fh, delimiter="\t", lineterminator="\n")
w.writerow(["Wizard", "Model", "Lane", "Latest check-in", "Newest commit"])
for r in rows:
w.writerow([r["wizard"], r["model"], r["lane"],
r["latest_checkin"], r["newest_commit"]])
# --- CSV manifest ---------------------------------------------------------
manifest_path = OUT / MANIFEST_NAME
with open(manifest_path, "w", encoding="utf-8", newline="") as fh:
w = csv.writer(fh, delimiter=",", lineterminator="\n")
w.writerow(["source", "path", "kind", "bytes", "modified_utc"])
for label, p in [
("table", TABLE),
("checkins", CHKINS_DIR),
("git", REPO_ROOT / ".git"),
]:
if p.is_dir():
for f in sorted(p.rglob("*")):
if f.is_file():
wt = datetime.fromtimestamp(f.stat().st_mtime, timezone.utc)
w.writerow([label, str(f.relative_to(REPO_ROOT)),
"file", f.stat().st_size,
wt.strftime("%Y-%m-%dT%H:%M:%SZ")])
else:
wt = datetime.fromtimestamp(p.stat().st_mtime, timezone.utc) if p.exists() else None
w.writerow([label, str(p), "file",
p.stat().st_size if p.exists() else 0,
wt.strftime("%Y-%m-%dT%H:%M:%SZ") if wt else ""])
# --- MD -------------------------------------------------------------------
md_path = OUT / MD_NAME
with open(md_path, "w", encoding="utf-8") as fh:
fh.write("# Live wizard roster (2026-10-29)\n\n")
fh.write("Evidence: roster from `WIZARDS-TABLE.md`; latest check-in from "
"`checkins/` files; newest commit from `git log`.\n\n")
fh.write("| Wizard | Model | Lane | Latest check-in | Newest commit |\n")
fh.write("|--------|-------|------|-----------------|---------------|\n")
for r in rows:
fh.write(f"| {r['wizard']} | {r['model']} | {r['lane']} "
f"| {r['latest_checkin']} | {r['newest_commit']} |\n")
fh.write("\n*Last refreshed 2026-10-29 (UTC).*\n")
# --- JSON manifest ----------------------------------------------------------
counts = {"check-ins": sum(1 for r in rows if r["latest_checkin"] != "— (no check-in)"),
"newest-commits": sum(1 for r in rows if r["newest_commit"] != "— (no commit)"),
"rows": len(rows)}
json_path = OUT / JSON_NAME
with open(json_path, "w", encoding="utf-8") as fh:
json.dump({"extracted_at_utc": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
"source": "WIZARDS-TABLE.md + checkins/ + git log",
"rows": rows, "counts": counts}, fh, indent=2)
print(f"wrote {tsv_path}")
print(f"wrote {manifest_path}")
print(f"wrote {md_path}")
print(f"wrote {json_path}")
for r in rows:
print(f" {r['wizard']:22s} checkin={r['latest_checkin']:22s} commit={r['newest_commit']}")
if __name__ == "__main__":
main()