69 lines
3.7 KiB
Python
69 lines
3.7 KiB
Python
"""Independent byte-bound completeness/bounds report; no presentation-quality goldens.
|
|
|
|
Run after the producer and unchanged scripts/verify-cv-fidelity.py. All reported
|
|
whitespace/name-wrap observations remain measurements for human review.
|
|
"""
|
|
import argparse
|
|
import hashlib
|
|
import json
|
|
import re
|
|
from collections import Counter
|
|
from pathlib import Path
|
|
|
|
import pdfplumber
|
|
|
|
|
|
def inspect(root, fixture):
|
|
filename = root / fixture["fixture"] / "export.pdf"
|
|
failures, pages, markers = [], [], []
|
|
with pdfplumber.open(filename) as document:
|
|
for index, page in enumerate(document.pages):
|
|
words = page.extract_words()
|
|
text = page.extract_text() or ""
|
|
markers.extend(re.findall(r"\b[SEBPR]\d{2}\b", text))
|
|
outside = [word for word in words if word["x0"] < -1.42 or word["top"] < -1.42
|
|
or word["x1"] > page.width + 1.42 or word["bottom"] > page.height + 1.42]
|
|
if outside:
|
|
failures.append(f"page {index + 1}: {len(outside)} words outside physical page by more than 0.5mm")
|
|
if not words:
|
|
failures.append(f"page {index + 1}: blank output")
|
|
top = min((word["top"] for word in words), default=0)
|
|
bottom = max((word["bottom"] for word in words), default=0)
|
|
pages.append({"page": index + 1, "wordCount": len(words), "widthPt": page.width,
|
|
"heightPt": page.height, "textTopPt": top, "textBottomPt": bottom,
|
|
"occupiedVerticalFraction": (bottom - top) / page.height,
|
|
"trailingWhitespaceFraction": (page.height - bottom) / page.height,
|
|
"outsideWords": outside, "text": text,
|
|
"embeddedImages": len(page.images)})
|
|
if Counter(markers) != Counter(fixture["expectedMarkers"]):
|
|
failures.append("PDF marker multiset differs from independent source fixture")
|
|
if len(pages) != fixture["previewPages"]:
|
|
failures.append("PDF/preview page count differs")
|
|
return {"fixture": fixture["fixture"], "pass": not failures, "failures": failures,
|
|
"pdfSha256": hashlib.sha256(filename.read_bytes()).hexdigest(), "expectedMarkerCount": len(fixture["expectedMarkers"]),
|
|
"actualMarkerCount": len(markers), "nameLineCount": fixture["measured"]["nameLineCount"],
|
|
"splitNameWords": fixture["measured"]["splitNameWords"], "pages": pages}
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("directory", type=Path)
|
|
args = parser.parse_args()
|
|
measurements = json.loads((args.directory / "measurements.json").read_text(encoding="utf-8"))
|
|
fixtures = [inspect(args.directory, item) for item in measurements["fixtures"]]
|
|
result = {"measurementSha256": hashlib.sha256((args.directory / "measurements.json").read_bytes()).hexdigest(),
|
|
"designVersion": measurements["designVersion"], "total": len(fixtures),
|
|
"passed": sum(item["pass"] for item in fixtures), "pages": sum(len(item["pages"]) for item in fixtures),
|
|
"qualification": "Finite synthetic completeness and physical bounds only; whitespace and name wrapping require visual review.",
|
|
"fixtures": fixtures}
|
|
(args.directory / "pdf-proof.json").write_text(json.dumps(result, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
print(f"Source-bound PDF completeness/bounds: {result['passed']}/{result['total']} documents, {result['pages']} pages")
|
|
for item in fixtures:
|
|
if not item["pass"]:
|
|
print(item["fixture"], item["failures"])
|
|
raise SystemExit(0 if result["passed"] == result["total"] else 1)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|