feat(cv): qualify templates and reliable local import processing

This commit is contained in:
cesnimda
2026-10-28 14:36:12 +01:00
parent 73ea888375
commit 3f4db8ca81
69 changed files with 2597 additions and 180 deletions
@@ -0,0 +1,90 @@
"""Independent PDF retention/bounds and Poppler contact-sheet proof for 18 letters.
Keeps the original four-letter corpus and all fidelity tolerances untouched.
"""
import argparse
import hashlib
import json
import re
import subprocess
import unicodedata
from collections import Counter
from pathlib import Path
import pdfplumber
from PIL import Image, ImageDraw
def normalize(text):
return re.sub(r"\s+", "", unicodedata.normalize("NFKC", text)).casefold()
def main():
parser = argparse.ArgumentParser()
parser.add_argument("directory", type=Path)
parser.add_argument("--poppler", type=Path, required=True)
args = parser.parse_args()
source = json.loads((args.directory / "measurements.json").read_text(encoding="utf-8"))
fixtures = []
for fixture in source["fixtures"]:
directory = args.directory / fixture["fixture"]
filename = directory / "export.pdf"
failures, pages, markers, texts = [], [], [], []
columns = [[], []]
sidebar = fixture["measured"]["pageDisplay"] == "grid"
with pdfplumber.open(filename) as document:
for number, page in enumerate(document.pages, 1):
text = page.extract_text() or ""
texts.append(text)
if sidebar:
# PDF line extraction otherwise interleaves separate sidebar/body columns.
boundary = (210 - 68 if fixture["settings"]["themeId"] == "nordic" else 68) * 72 / 25.4
for index, bounds in enumerate(((0, 0, boundary, page.height), (boundary, 0, page.width, page.height))):
columns[index].append(page.crop(bounds).extract_text() or "")
markers.extend(re.findall(r"\b[SEBPR]\d{2}\b", text))
words = page.extract_words()
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 or not words:
failures.append(f"page {number}: {len(outside)} outside words; {len(words)} total words")
if abs(page.width * 25.4 / 72 - 210) > .5 or abs(page.height * 25.4 / 72 - 297) > .5:
failures.append(f"page {number}: unexpected A4 dimensions")
pages.append({"page": number, "widthPt": page.width, "heightPt": page.height, "words": len(words), "outsideWords": outside})
combined = normalize("\n".join("\n".join(column) for column in columns) if sidebar else "\n".join(texts))
for text in fixture["expectedParagraphs"] + [fixture["expectedRecipient"], fixture["expectedSender"], fixture["expectedSubject"]]:
if normalize(text) not in combined:
failures.append("Missing or changed source text: " + text[:90])
if Counter(markers) != Counter(fixture["expectedMarkers"]):
failures.append("PDF/source marker multisets differ")
if len(pages) != fixture["previewPages"]:
failures.append("PDF/preview page count differs")
for page in range(1, len(pages) + 1):
subprocess.run([str(args.poppler), "-f", str(page), "-l", str(page), "-singlefile", "-scale-to", "1400", "-png", str(filename), str(directory / f"poppler-{page}")], check=True, capture_output=True)
fixtures.append({"fixture": fixture["fixture"], "pass": not failures, "failures": failures,
"pdfSha256": hashlib.sha256(filename.read_bytes()).hexdigest(), "pages": pages,
"expectedMarkers": len(fixture["expectedMarkers"]), "actualMarkers": len(markers)})
report = {"total": len(fixtures), "passed": sum(item["pass"] for item in fixtures),
"pages": sum(len(item["pages"]) for item in fixtures), "fixtures": fixtures,
"qualification": "Finite local synthetic retention, page dimensions/count and physical word bounds; sidebar text extracted by physical columns to avoid line interleaving. Not human print acceptance."}
(args.directory / "pdf-proof.json").write_text(json.dumps(report, ensure_ascii=False, indent=2), encoding="utf-8")
for language in ("short-en", "long-nb"):
for edge in ("first", "last"):
sheet = Image.new("RGB", (900, 1365), "#e5e7eb")
draw = ImageDraw.Draw(sheet)
for index, fixture in enumerate([item for item in fixtures if item["fixture"].startswith(language + "-")]):
number = 1 if edge == "first" else len(fixture["pages"])
with Image.open(args.directory / fixture["fixture"] / f"poppler-{number}.png") as image:
image.thumbnail((286, 414))
x, y = (index % 3) * 300 + 7, (index // 3) * 455 + 32
sheet.paste(image, (x, y))
draw.text((x, y - 25), f"{fixture['fixture']} p{number}/{len(fixture['pages'])}", fill="#111827")
sheet.save(args.directory / f"contact-{language}-{edge}.png")
print(f"Matching letter physical proof: {report['passed']}/{report['total']} documents, {report['pages']} pages")
for item in fixtures:
if not item["pass"]:
print(item["fixture"], item["failures"])
raise SystemExit(0 if report["passed"] == report["total"] else 1)
if __name__ == "__main__":
main()