127 lines
4.2 KiB
Python
127 lines
4.2 KiB
Python
#!/usr/bin/env python3
|
|
"""Independent verifier for the replies volume census artifacts.
|
|
|
|
Recomputes ground truth from the LIVE <repo>/replies/ directory using shell
|
|
pipelines (grep/sed/cut/sort/uniq via subprocess), then asserts the committed
|
|
TSV artifacts and the markdown summary match that ground truth.
|
|
|
|
The extractor parses with a compiled regex over pathlib; this verifier parses
|
|
with shell text tools -- two independent code paths over the same evidence
|
|
directory, so agreement is a real cross-check.
|
|
|
|
Exit status:
|
|
0 artifact matches ground truth
|
|
1 mismatch (or artifact missing / unreadable)
|
|
|
|
Used as the CHECK line: python3 wizard-work/ledger/verify_reply_volume_census.py
|
|
"""
|
|
|
|
import csv
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parents[2]
|
|
REPLIES = REPO_ROOT / "replies"
|
|
DATE_TSV = REPO_ROOT / "wizard-work" / "ledger" / "reply-volume-census.tsv"
|
|
RECIP_TSV = REPO_ROOT / "wizard-work" / "ledger" / "reply-volume-by-recipient.tsv"
|
|
MD = REPO_ROOT / "wizard-work" / "ledger" / "reply-volume-census.md"
|
|
|
|
DATE_PAT = r"^[0-9]{4}-[0-9]{2}-[0-9]{2}-to-"
|
|
FIND_BASENAMES = "find replies -maxdepth 1 -type f -name '*.md' -printf '%f\\n'"
|
|
SEP = "-------------------------------------------------------------------------------"
|
|
|
|
|
|
def sh(cmd):
|
|
"""Run a shell command and return stripped stdout."""
|
|
return subprocess.run(
|
|
cmd, shell=True, check=True, capture_output=True, text=True
|
|
).stdout
|
|
|
|
|
|
def ground_truth():
|
|
"""Independent ground truth derived from the live replies/ directory."""
|
|
date_cmd = (FIND_BASENAMES + " | grep -E '" + DATE_PAT + "' | "
|
|
"sed -E 's/-to-.*//' | sort | uniq -c")
|
|
date_files = sh(date_cmd).strip().splitlines()
|
|
date_counts = {}
|
|
for line in date_files:
|
|
cnt, date = line.split(None, 1)
|
|
date_counts[date] = int(cnt)
|
|
|
|
# Recipients: strip the date prefix, drop ".md", then drop a trailing "-NN".
|
|
recip_cmd = (FIND_BASENAMES + " | grep -E '" + DATE_PAT + "' | "
|
|
"sed -E 's/^" + DATE_PAT + "//' | sed -E 's/\\.md$//' | "
|
|
"sed -E 's/-[0-9]+$//' | sort | uniq -c")
|
|
recip_files = sh(recip_cmd).strip().splitlines()
|
|
recip_counts = {}
|
|
for line in recip_files:
|
|
cnt, recip = line.split(None, 1)
|
|
recip_counts[recip] = int(cnt)
|
|
|
|
return date_counts, recip_counts, sum(date_counts.values())
|
|
|
|
|
|
def parse_tsv(path):
|
|
rows = {}
|
|
with path.open(newline="", encoding="utf-8") as fh:
|
|
reader = csv.reader(fh, delimiter="\t")
|
|
next(reader, None) # skip header row
|
|
for row in reader:
|
|
if len(row) == 2:
|
|
rows[row[0]] = int(row[1])
|
|
return rows
|
|
|
|
|
|
def main():
|
|
date_gt, recip_gt, total_gt = ground_truth()
|
|
problems = []
|
|
|
|
for label, tsv_path, gt in (("date", DATE_TSV, date_gt),
|
|
("recipient", RECIP_TSV, recip_gt)):
|
|
if not tsv_path.is_file():
|
|
problems.append("missing " + str(tsv_path))
|
|
continue
|
|
rows = parse_tsv(tsv_path)
|
|
rows_data = {k: v for k, v in rows.items() if k != "TOTAL"}
|
|
if rows_data != gt:
|
|
problems.append(
|
|
"%s: data rows differ; got %d keys, ground truth %d keys"
|
|
% (label, len(rows_data), len(gt))
|
|
)
|
|
if rows.get("TOTAL", -1) != sum(gt.values()):
|
|
problems.append(
|
|
"%s: TOTAL row %r != sum of rows %d"
|
|
% (label, rows.get("TOTAL"), sum(gt.values()))
|
|
)
|
|
|
|
# Markdown summary must name the correct headline numbers.
|
|
if MD.is_file():
|
|
text = MD.read_text(encoding="utf-8")
|
|
for token in ("430", "34", "17", "README.md"):
|
|
if token not in text:
|
|
problems.append("markdown missing assertion: %s" % token)
|
|
else:
|
|
problems.append("missing " + str(MD))
|
|
|
|
if problems:
|
|
print("CHECK FAIL")
|
|
for p in problems:
|
|
print(" -", p)
|
|
sys.exit(1)
|
|
|
|
print(
|
|
"CHECK PASS: %d replies, %d dates, %d recipients "
|
|
"(md_files=%d, canonical=%d)"
|
|
% (total_gt, len(date_gt), len(recip_gt), md_total(), total_gt)
|
|
)
|
|
sys.exit(0)
|
|
|
|
|
|
def md_total():
|
|
return len(sh("ls -1 replies/*.md").splitlines())
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|