feat(cv): qualify templates and reliable local import processing
This commit is contained in:
@@ -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()
|
||||
Reference in New Issue
Block a user