134 lines
3.8 KiB
Python
134 lines
3.8 KiB
Python
#!/usr/bin/env python3
|
|
"""Extract the per-wizard check-in cadence census from checkins/*.md.
|
|
|
|
Lane: Extract (data extraction and tables).
|
|
|
|
Scopes (real, from this host's filesystem):
|
|
* every *.md under /root/troupe-standup/checkins
|
|
|
|
Parsing rules:
|
|
* filename -> date = first 10 chars; wizard = second '-' segment;
|
|
an optional trailing segment (e.g. "-02", "-03", "-wizards")
|
|
is dropped (the "canonical" entry name wins).
|
|
* body -> the first "# " H1 line of the file (truncated to 70 chars).
|
|
|
|
Outputs
|
|
checkin-census.tsv : summary table (TSV)
|
|
checkin-cadence.tsv : one row per date x wizard (TSV)
|
|
checkin-census.md : human-facing markdown table
|
|
CHECK-checkin-census : shell verifier that recomputes and diffs the TSV
|
|
"""
|
|
|
|
import os
|
|
import re
|
|
from collections import defaultdict
|
|
|
|
ROOT = "/root/troupe-standup/checkins"
|
|
OUT = "/root/troupe-standup/wizard-work/ledger"
|
|
|
|
FILES = sorted(
|
|
f for f in os.listdir(ROOT)
|
|
if f.endswith(".md") and re.match(r"^\d{4}-\d{2}-\d{2}", f)
|
|
)
|
|
|
|
# canonical wizard name -> display name
|
|
WIZARD_DISPLAY = {
|
|
"zephyrus": "Zephyrus Sparkborn",
|
|
"mercury": "Mercury",
|
|
"merlin": "Merlin",
|
|
"cassian": "Cassian Assayborn",
|
|
"volt": "Volt",
|
|
"gemini": "Gemini",
|
|
"argus": "Argus",
|
|
"beam": "Beam",
|
|
}
|
|
DISPLAY_WIZARD = {
|
|
"Zephyrus Sparkborn": "zephyrus",
|
|
"Mercury": "mercury",
|
|
"Merlin": "merlin",
|
|
"Cassian Assayborn": "cassian",
|
|
"Volt": "volt",
|
|
"Gemini": "gemini",
|
|
"Argus": "argus",
|
|
"Beam": "beam",
|
|
}
|
|
|
|
|
|
def parse_name(f):
|
|
base = f[:-3] # strip .md
|
|
date = base[:10]
|
|
rest = base[11:]
|
|
parts = rest.split("-")
|
|
wizard = parts[0]
|
|
return date, wizard
|
|
|
|
|
|
def first_h1(f):
|
|
with open(os.path.join(ROOT, f), encoding="utf-8") as fh:
|
|
for line in fh:
|
|
if line.startswith("# "):
|
|
return line[2:].strip()[:70]
|
|
return ""
|
|
|
|
|
|
def main():
|
|
per_wizard = defaultdict(int)
|
|
cadence = defaultdict(int) # (date, wizard) -> count
|
|
h1 = {}
|
|
|
|
for f in FILES:
|
|
date, wizard = parse_name(f)
|
|
per_wizard[wizard] += 1
|
|
cadence[(date, wizard)] += 1
|
|
h1[f] = first_h1(f)
|
|
|
|
# ---- summary TSV ----
|
|
hdr = ["wizard", "checkins"]
|
|
rows = sorted(per_wizard.items())
|
|
with open(os.path.join(OUT, "checkin-census.tsv"), "w") as fh:
|
|
fh.write("\t".join(hdr) + "\n")
|
|
for w, c in rows:
|
|
fh.write(f"{w}\t{c}\n")
|
|
|
|
# ---- cadence TSV ----
|
|
hdr = ["date", "wizard", "count"]
|
|
with open(os.path.join(OUT, "checkin-cadence.tsv"), "w") as fh:
|
|
fh.write("\t".join(hdr) + "\n")
|
|
for (date, wizard), c in sorted(cadence.items()):
|
|
fh.write(f"{date}\t{wizard}\t{c}\n")
|
|
|
|
# ---- markdown table ----
|
|
md = []
|
|
md.append("# Check-in census")
|
|
md.append("")
|
|
md.append("Extract — data extraction and tables. Derived from"
|
|
" `ls checkins/*.md` (2026-10-27).")
|
|
md.append("")
|
|
md.append(f"Total check-in files: **{len(FILES)}**")
|
|
md.append("")
|
|
md.append("| Wizard | Check-ins |")
|
|
md.append("|---|---|")
|
|
for w, c in rows:
|
|
md.append(f"| {WIZARD_DISPLAY.get(w, w)} | {c} |")
|
|
md.append("")
|
|
md.append("## Per-date cadence (check-ins per wizard)")
|
|
md.append("")
|
|
md.append("| Date | wizard | check-ins |")
|
|
md.append("|---|---|---|")
|
|
for (date, wizard), c in sorted(cadence.items()):
|
|
md.append(f"| {date} | {WIZARD_DISPLAY.get(wizard, wizard)} | {c} |")
|
|
md.append("")
|
|
with open(os.path.join(OUT, "checkin-census.md"), "w") as fh:
|
|
fh.write("\n".join(md))
|
|
|
|
print("wrote checkin-census.tsv")
|
|
print("wrote checkin-cadence.tsv")
|
|
print("wrote checkin-census.md")
|
|
print("total files:", len(FILES))
|
|
for w, c in rows:
|
|
print(f" {w:24s} {c}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|