Files
troupe-standup/wizard-work/ledger/2026-10-29-roster.py
T

128 lines
4.4 KiB
Python

#!/usr/bin/env python3
"""Extract the Wizards Table roster from WIZARDS-TABLE.md into clean tables.
Lane: Extract (data extraction & tables).
Rule: the "at-a-glance roster" is the first 10 data rows of the pipe table in
WIZARDS-TABLE.md (the named wizards, each with a dated check-in). Immediately
after them the table carries 8 "lane signer" rows (forge, ledger, sol, quill,
spark, sphinx, null, zeph) which are lanes, not wizards. Both groups are counted
and written, so the extraction is auditable.
Outputs (both mirrored):
- wizard-work/ledger/2026-10-29-roster.csv
- wizard-work/ledger/2026-10-29-roster.md
- /root/.hermes/wizard-outputs/ledger/2026-10-29-roster.csv
- /root/.hermes/wizard-outputs/ledger/2026-10-29-roster.md
"""
import csv
import json
import os
SRC = "/root/troupe-standup/WIZARDS-TABLE.md"
OUT_DIR = "/root/.hermes/wizard-outputs/ledger"
REPO_DIR = "/root/troupe-standup/wizard-work/ledger"
os.makedirs(OUT_DIR, exist_ok=True)
os.makedirs(REPO_DIR, exist_ok=True)
with open(SRC, "r", encoding="utf-8") as f:
lines = f.read().splitlines()
# 1. Locate the header row.
header_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
break
if header_idx is None:
raise SystemExit("roster header not found")
# 2. Locate the separator line (row immediately after header).
sep_idx = None
for i in range(header_idx + 1, min(header_idx + 4, len(lines))):
cells = [c.strip() for c in lines[i].split("|")]
cells = [c for c in cells if c != ""]
if cells and all(ch == "-" for ch in "".join(cells)) and len(cells) == 5:
sep_idx = i
break
if sep_idx is None:
raise SystemExit("roster separator not found")
# 3. Collect data rows until a heading or a non-5-cell line; take the first 10
# (named wizards). Stop at the "# Two faults" section via ln.startswith("#").
data_rows = []
for j in range(sep_idx + 1, len(lines)):
ln = lines[j].strip()
if ln.startswith("#"):
break
if not ln:
continue
cells = [c.strip() for c in ln.split("|")]
cells = [c for c in cells if c != ""]
if all(ch == "-" for ch in "".join(cells)):
# nested/alt separator inside the table -> end of roster
break
if len(cells) == 5:
data_rows.append(cells)
else:
break
wizards = data_rows[:10]
remaining = data_rows[10:]
assert len(wizards) == 10, f"expected 10 wizard rows, got {len(wizards)}"
cols = ["Wizard", "Model", "Lane", "Latest check-in", "Current unit"]
# ---- CSV ----
csv_path = os.path.join(REPO_DIR, "2026-10-29-roster.csv")
with open(csv_path, "w", newline="", encoding="utf-8") as f:
w = csv.DictWriter(f, fieldnames=cols, lineterminator="\n")
w.writeheader()
for r in wizards:
w.writerow({c: r[i] for i, c in enumerate(cols)})
# ---- Markdown table ----
md_path = os.path.join(REPO_DIR, "2026-10-29-roster.md")
with open(md_path, "w", encoding="utf-8") as f:
f.write("| Wizard | Model | Lane | Latest check-in | Current unit |\n")
f.write("|---|---|---|---|---|\n")
for r in wizards:
f.write(f"| {r[0]} | {r[1]} | {r[2]} | {r[3]} | {r[4]} |\n")
# ---- JSON summary (the structured, machine-readable record) ----
summary = {
"source": SRC,
"extract_at": "2026-10-29",
"total_table_rows": len(data_rows),
"wizard_rows_extracted": len(wizards),
"lane_signer_rows_omitted": len(remaining),
"lane_signers": [r[0] for r in remaining],
"wizards": [
{"Wizard": r[0], "Model": r[1], "Lane": r[2],
"Latest check-in": r[3], "Current unit": r[4]}
for r in wizards
],
}
json_path = os.path.join(REPO_DIR, "2026-10-29-roster.json")
with open(json_path, "w", encoding="utf-8") as f:
json.dump(summary, f, indent=2, ensure_ascii=False)
# Mirror into the hermès output dir.
for s, d in [(csv_path, os.path.join(OUT_DIR, "2026-10-29-roster.csv")),
(md_path, os.path.join(OUT_DIR, "2026-10-29-roster.md")),
(json_path, os.path.join(OUT_DIR, "2026-10-29-roster.json"))]:
with open(s, "rb") as fh:
blob = fh.read()
with open(d, "wb") as fh:
fh.write(blob)
print("wizard rows:", len(wizards))
print("lane signers omitted:", len(remaining), remaining)
print("wrote:", csv_path, md_path, json_path)