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

111 lines
3.7 KiB
Python

#!/usr/bin/env python3
"""Extract the Wizards Table roster from WIZARDS-TABLE.md into clean tables.
Lane: Extract (data extraction & tables).
Output:
- /root/.hermes/wizard-outputs/ledger/wizard-roster.csv
- /root/.hermes/wizard-outputs/ledger/roster-summary.json
- /root/troupe-standup/wizard-work/ledger/wizard-roster.csv
- /root/troupe-standup/wizard-work/ledger/roster-summary.json
"""
import csv
import json
import os
import re
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()
# Locate the roster 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")
# Collect data rows that follow the header + separator, until a non-table line.
rows = []
j = header_idx + 2 # skip header, skip separator
while j < len(lines):
ln = lines[j].strip()
if not ln:
j += 1
continue
cells = [c.strip() for c in ln.split("|")]
cells = [c for c in cells if c != ""]
# separator line (all dashes) -> stop
if all(re.fullmatch(r"-+", c) for c in cells) and len(cells) >= 2:
break
# stop at any heading or non-table line
if ln.startswith("#"):
break
if len(cells) == 5:
rows.append(cells)
j += 1
expected_cols = ["Wizard", "Model", "Lane", "Latest check-in", "Current unit"]
assert len(rows) == 10, f"expected 10 roster rows, got {len(rows)}"
for r in rows:
assert len(r) == 5, f"row has {len(r)} cells: {r}"
assert r[0] == r[0].strip() and r[0], f"empty wizard name: {r}"
wizards = [
{"Wizard": r[0], "Model": r[1], "Lane": r[2], "Latest check-in": r[3], "Current unit": r[4]}
for r in rows
]
# ---- CSV ----
csv_path = os.path.join(REPO_DIR, "wizard-roster.csv")
with open(csv_path, "w", newline="", encoding="utf-8") as f:
w = csv.DictWriter(f, fieldnames=expected_cols, lineterminator="\n")
w.writeheader()
w.writerows(wizards)
# ---- Summary JSON ----
staffed = [w["Wizard"] for w in wizards if "Staffed" in w["Current unit"] or "Staffed" in w["Lane"] or "staffed" in w["Current unit"].lower()]
staffed = [w["Wizard"] for w in wizards if "2026-10-15" in w["Latest check-in"]]
staffed += [w["Wizard"] for w in wizards if "Staffed" in w["Current unit"]]
staffed = sorted(set(staffed))
summary = {
"source": SRC,
"extract_at": "2026-10-21",
"total_wizards": len(wizards),
"wizards": wizards,
"staffed_count": len(staffed),
"staffed": staffed,
"lanes": sorted(set(w["Lane"] for w in wizards)),
"checkin_dates": sorted(set(w["Latest check-in"] for w in wizards if w["Latest check-in"] != "— (replies daily)")),
}
sum_path = os.path.join(REPO_DIR, "roster-summary.json")
with open(sum_path, "w", encoding="utf-8") as f:
json.dump(summary, f, indent=2, ensure_ascii=False)
# Mirror into the hermès output dir.
csv_out = os.path.join(OUT_DIR, "wizard-roster.csv")
sum_out = os.path.join(OUT_DIR, "roster-summary.json")
for s, d in [(csv_path, csv_out), (sum_path, sum_out)]:
with open(s, "rb") as f:
data = f.read()
with open(d, "wb") as f:
f.write(data)
print("rows extracted:", len(rows))
print("staffed:", staffed)
print("lanes:", summary["lanes"])
print("wizard dates:", summary["checkin_dates"])
print("wrote:", csv_path, sum_path)