Files
jobtrackingapp/docs/pre-phase10-ux/letter-library-proof.py
T

91 lines
5.2 KiB
Python

"""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()