76 lines
2.8 KiB
Python
76 lines
2.8 KiB
Python
#!/usr/bin/env python3
|
|
""""Verifier for the arXiv catalog extraction -- exit 0 iff the artifact is correct.
|
|
|
|
Reads the committed CSV, JSON and MD produced by extract_arxiv.py and checks the
|
|
facts that can be checked without re-running the extractor:
|
|
* CSV header row is exact
|
|
* 15 data rows (matches the Atom feed's entry count)
|
|
* every row has non-empty arxiv_id, title, authors, published, updated, category, summary
|
|
* arxiv_id matches the ^\d{4}\.\d{4,5}[oha]?v?\d*$ pattern
|
|
* JSON parses, 15 entries, keys match the CSV columns
|
|
* CSV and JSON agree on arxiv_id set and published dates
|
|
Exit code IS the verdict (0 = correct, 1 = wrong).
|
|
"""
|
|
import csv
|
|
import json
|
|
import re
|
|
import sys
|
|
|
|
CSV_PATH = "/root/.hermes/wizard-outputs/ledger/arxiv-catalog-2026-10-30.csv"
|
|
JSON_PATH = "/root/.hermes/wizard-outputs/ledger/arxiv-catalog-2026-10-30.json"
|
|
MD_PATH = "/root/.hermes/wizard-outputs/ledger/arxiv-catalog-2026-10-30.md"
|
|
|
|
EXPECTED_HEADER = ["arxiv_id", "title", "authors", "published", "updated",
|
|
"category", "comment", "summary"]
|
|
ID_RE = re.compile(r"^\d{4}\.\d{4,5}[oha]?v?\d*$")
|
|
REQUIRED = {"arxiv_id", "title", "authors", "published", "updated", "category", "summary"}
|
|
|
|
fails = []
|
|
|
|
|
|
def check(name, cond, detail=""):
|
|
if cond:
|
|
print("ok %s" % name)
|
|
else:
|
|
print("FAIL %s %s" % (name, detail))
|
|
fails.append(name)
|
|
|
|
|
|
def main():
|
|
with open(CSV_PATH, newline="", encoding="utf-8") as f:
|
|
reader = csv.DictReader(f)
|
|
check("csv header", reader.fieldnames == EXPECTED_HEADER,
|
|
"got %r" % (reader.fieldnames,))
|
|
rows = list(reader)
|
|
check("csv row count", len(rows) == 15, "got %d" % len(rows))
|
|
|
|
ids = []
|
|
for i, r in enumerate(rows, start=2): # line 1 = header
|
|
for col in REQUIRED:
|
|
check("row%d.%s non-empty" % (i, col), bool(r.get(col, "").strip()),
|
|
"row %d col %s = %r" % (i, col, r.get(col)))
|
|
check("row%d.arxiv_id pattern" % i, ID_RE.match(r.get("arxiv_id", "")) is not None,
|
|
"got %r" % r.get("arxiv_id"))
|
|
ids.append(r.get("arxiv_id", ""))
|
|
|
|
with open(JSON_PATH, encoding="utf-8") as f:
|
|
j = json.load(f)
|
|
check("json parses and is a list", isinstance(j, list), type(j).__name__)
|
|
check("json row count", len(j) == 15, "got %d" % len(j))
|
|
check("json keys match csv columns", set(j[0].keys()) == set(EXPECTED_HEADER),
|
|
"got %r" % sorted(j[0].keys()))
|
|
check("json ids match csv ids", [x.get("arxiv_id") for x in j] == ids)
|
|
|
|
with open(MD_PATH, encoding="utf-8") as f:
|
|
md = f.read()
|
|
check("md mentions 15 rows", "Rows: 15" in md, "not found in %r" % md[:200])
|
|
|
|
if fails:
|
|
print("FAILURES: %d" % len(fails))
|
|
sys.exit(1)
|
|
print("ALL CHECKS PASSED (%d rows)" % len(rows))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|