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

114 lines
3.8 KiB
Python

#!/usr/bin/env python3
"""CHECK for recoveries-index.csv / recoveries-index.md (Extract lane, ledger).
Independent re-derivation:
- file list: subprocess `find recoveries -name '*.md' -not -path '*/.git/*'` (sorted)
- bytes: `wc -c` per file
- lines: `wc -l` per file
- headings: re.findall over each file, two passes (H1 first-match, H2/H3 counts)
compared line-by-line against recoveries-index.csv. Exits 0 iff every cell matches.
"""
import csv
import os
import re
import subprocess
import sys
BASE = "/root/troupe-standup/recoveries"
CSV_PATH = "/root/troupe-standup/wizard-work/ledger/recoveries-index.csv"
def sh(args):
return subprocess.run(args, check=True, capture_output=True, text=True).stdout
def find_files():
out = sh(["find", BASE, "-name", "*.md", "-not", "-path", "*/.git/*"]).splitlines()
return sorted(os.path.relpath(os.path.join(root, f), BASE) for root, _, files in os.walk(BASE) for f in files if f.endswith(".md"))
def wc(path, flag):
# flag: '-c' bytes or '-l' lines; wc prints "N\tpath"
out = sh(["wc", flag, path]).splitlines()
return int(out[0].split()[0])
def heading_counts(text):
h1first = ""
h2 = h3 = 0
for raw in text.split("\n"):
s = raw.rstrip("\n")
if not s.startswith("#"):
continue
txt = s.lstrip("#").strip()
if txt == "":
continue
lvl = len(s) - len(s.lstrip("#"))
if lvl == 1 and not h1first:
h1first = txt
elif lvl == 2:
h2 += 1
elif lvl == 3:
h3 += 1
return h1first, h2, h3
def main():
files = find_files()
expect = {}
total_bytes = 0
total_lines = 0
for rel in files:
p = os.path.join(BASE, rel)
b = wc(p, "-c")
l = wc(p, "-l")
text = open(p, encoding="utf-8", errors="replace").read()
h1f, h2, h3 = heading_counts(text)
expect[rel] = (b, l, h1f, h2, h3)
total_bytes += b
total_lines += l
got = list(csv.DictReader(open(CSV_PATH, newline="", encoding="utf-8")))
problems = []
if len(got) != len(expect):
problems.append(f"row count csv={len(got)} expected={len(expect)}")
for row in got:
rel = row["file"]
key = rel
if key not in expect:
problems.append(f"csv has unexpected file {key!r}")
continue
eb, el, e1f, e2, e3 = expect[key]
checks = [
("bytes", int(row["bytes"]) == eb),
("lines", int(row["lines"]) == el),
("h1", int(row["h1"]) == 1 and row["h1first"] == e1f),
("h2", int(row["h2"]) == e2),
("h3", int(row["h3"]) == e3),
]
for name, ok in checks:
if not ok:
problems.append(f"{rel}: {name} csv={row.get(name)} expected={ {'bytes':eb,'lines':el,'h2':e2,'h3':e3}.get(name) if name!='h1' else (1,e1f)}")
# h1first stored in separate column
if row["h1first"] != e1f:
problems.append(f"{rel}: h1first mismatch {row['h1first']!r} != {e1f!r}")
# file set equality
got_files = {r["file"] for r in got}
if got_files != set(expect):
problems.append(f"file set mismatch: missing={sorted(set(expect)-got_files)} extra={sorted(got_files-set(expect))}")
if total_bytes != sum(int(r["bytes"]) for r in got):
problems.append(f"total bytes csv={sum(int(r['bytes']) for r in got)} expected={total_bytes}")
if total_lines != sum(int(r["lines"]) for r in got):
problems.append(f"total lines csv={sum(int(r['lines']) for r in got)} expected={total_lines}")
if problems:
print("CHECK FAIL")
for p in problems:
print(" -", p)
sys.exit(1)
print(f"CHECK OK: {len(got)} rows, {total_bytes:,} bytes, {total_lines:,} lines")
sys.exit(0)
if __name__ == "__main__":
main()