128 lines
4.4 KiB
Python
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)
|