111 lines
3.7 KiB
Python
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)
|