95 lines
3.3 KiB
Python
95 lines
3.3 KiB
Python
#!/usr/bin/env python3
|
|
"""verify_github_repo_census.py
|
|
|
|
Verifier for the github-repo-census-2026-11-02 artifact (extract lane).
|
|
|
|
Independent re-derivation: rebuilds the expected TSV from the committed
|
|
raw.json (no import of the extractor), then compares field-for-field with
|
|
the committed TSV and runs structural checks. Exit code IS the verdict:
|
|
0 iff everything matches, 1 on any mismatch.
|
|
"""
|
|
import csv
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
HERE = Path(__file__).resolve().parent
|
|
RAW = HERE / "github-repo-census-2026-11-02.raw.json"
|
|
TSV = HERE / "github-repo-census-2026-11-02.tsv"
|
|
FIELDS = ["rank", "repo", "owner", "description", "stars", "forks", "language", "url"]
|
|
|
|
|
|
def derive():
|
|
api = json.loads(RAW.read_text(encoding="utf-8"))
|
|
items = sorted(api.get("items", []),
|
|
key=lambda i: i.get("stargazers_count", 0), reverse=True)[:15]
|
|
out = []
|
|
for i, it in enumerate(items, 1):
|
|
out.append({
|
|
"rank": i,
|
|
"repo": it.get("name", ""),
|
|
"owner": (it.get("owner") or {}).get("login", ""),
|
|
"description": (it.get("description") or "").strip(),
|
|
"stars": it.get("stargazers_count", 0),
|
|
"forks": it.get("forks_count", 0),
|
|
"language": (it.get("language") or "").strip(),
|
|
"url": it.get("html_url", ""),
|
|
})
|
|
return out
|
|
|
|
|
|
def read_committed():
|
|
with TSV.open(encoding="utf-8", newline="") as f:
|
|
return list(csv.DictReader(f, delimiter="\t"))
|
|
|
|
|
|
def main():
|
|
try:
|
|
expected = derive()
|
|
except Exception as exc: # noqa: BLE001
|
|
print(f"[fail] could not re-derive from raw.json: {exc}")
|
|
return 1
|
|
try:
|
|
committed = read_committed()
|
|
except Exception as exc: # noqa: BLE001
|
|
print(f"[fail] could not read committed TSV: {exc}")
|
|
return 1
|
|
|
|
problems = []
|
|
|
|
# Structural checks.
|
|
if len(committed) != len(expected):
|
|
problems.append(f"row count: committed={len(committed)} expected={len(expected)}")
|
|
stars = [int(r.get("stars", -1)) for r in committed]
|
|
if stars != sorted(stars, reverse=True):
|
|
problems.append("stars not sorted non-increasing")
|
|
if [r.get("rank") for r in committed] != [str(i) for i in range(1, len(committed) + 1)]:
|
|
problems.append("rank column not 1..N")
|
|
if len({r.get("repo") for r in committed}) != len(committed):
|
|
problems.append("duplicate repo names")
|
|
for r in committed:
|
|
if not r.get("url", "").startswith("https://github.com/"):
|
|
problems.append(f"bad url: {r.get('url')}")
|
|
break
|
|
|
|
# Field-for-field comparison of the full table.
|
|
if len(committed) == len(expected):
|
|
for i, (c, e) in enumerate(zip(committed, expected), 1):
|
|
for field in FIELDS:
|
|
if c.get(field) != str(e[field]):
|
|
problems.append(f"row {i} field {field}: committed={c.get(field)!r} "
|
|
f"expected={e[field]!r}")
|
|
|
|
if problems:
|
|
print("[fail] census verification mismatches:")
|
|
for p in problems:
|
|
print(" -", p)
|
|
return 1
|
|
print(f"[ok] census verified: {len(committed)} rows, sorted desc by stars, "
|
|
f"no duplicate repos, table equals independent re-derivation from raw.json")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|