Dokumentation: Seiten auswählen

Report-Output: Permalink & PDF

Nahversorgung: Luftlinien-Näherung (#564)

Der Hinweis beschreibt 900 m Luftlinie als Näherung für 15 Minuten zu Fuß. Es wird keine Route berechnet; Umwege und Barrieren werden nicht berücksichtigt. Web, Steckbrief und PDF benennen den Radius ausdrücklich. Bekannte historische Hinweis-/Ampeltexte werden ausschließlich für die Darstellung projiziert; Snapshot, Score, Kategorien, Quellenstand und Ampel bleiben unverändert. Unbekannte Radiusverträge erhalten neutrale Kurztexte; historische Kategorien werden aus dem gespeicherten Bestand angezeigt. Vertrag und Prüfnachweise trennen Implementierung, CI/Preview, unabhängige Abnahme und Produktions-Readback. #463 bleibt unverändert; PDF/UA #333, PV #291 und NÖGIS #413 bleiben eigene Gates.

PV-Standortertrag (#562)

Variante A ist implementiert: Der Adressreport zeigt gespeicherten spezifischen PVGIS-Ertrag mit Modellannahmen. Neue Berechnungen erzeugen keine Leistung aus Parzellenfläche, keine 500-m²-Ersatzannahme und keine PV-Eurohochrechnung. Alte JSON-Snapshots bleiben unverändert; die Web-/PDF-/Kartensicht unterdrückt historische Gesamt- und Kostenwerte. Fehlende Annahmen werden nicht ergänzt. Vertrag und Abnahme dokumentieren API-Kompatibilität und die getrennten CI-/Preview-/Produktionsnachweise. #291 bleibt offen.

Jeder über POST /api/v1/report erzeugte Report wird persistiert und ist teilbar — als Web-Permalink und als PDF (ADR-0022). Vierfach verfügbar (AI-Parität, ADR-0018): UI, API, MCP, Skill.

Der Reportkopf ordnet Titel und Adresse auf schmalen Bildschirmen über „PDF herunterladen“ an. Lange Adressen dürfen auch innerhalb eines Wortes umbrechen. Ab 768 px steht die PDF-Aktion rechts daneben. Das mindestens 44 px hohe Ziel ist per Touch und Tab/Enter erreichbar; der Tastaturfokus ist sichtbar. Der PDF-Endpunkt und sein Inline-Downloadverhalten bleiben unverändert. Die Browserregression prüft 320, 390 und 1440 px auf dem gebauten App-Pfad mit einer synthetischen gespeicherten Reportantwort (npm run test:e2e:permalink:build). Der Testprozess ersetzt ausschließlich die Neon-Antwort für den synthetischen Testhost; er legt keine Reports an.

POST /api/v1/report ergänzt im Ergebnis:

"meta": { "report_id": "…", "permalink": "/r/…" }
  • /r/{id} — öffentliche, gerenderte Ansicht (Marken-Layout, PDF-Button). noindex (kein Suchmaschinen-Index).
  • ID = CSPRNG ≥128 Bit base62 → nicht erratbar (IDOR-Schutz, ADR-0023).
  • Retention 90 Tage (expires_at) für anonyme Reports, danach 404 + Hard-Delete (täglicher Cron). Nur ip_hash gespeichert, kein Klar-IP.
  • Konto-gebundene Reports (eingeloggt erstellt oder per /r/{id}?claim=1 beansprucht, Migration 0021): expires_at = NULL → laufen nicht ab, abrufbar unter „Meine Reports" (/account/reports). Zusätzlich user_id gespeichert; meta.saved=true signalisiert den Zustand an UI/API.
  • Fail-open: scheitert die Persistenz, kommt der Report ohne report_id (Kernfunktion unberührt).

Endpunkte

MethodePfadErgebnis
POST/api/v1/reportReport persistiert + meta.permalink
GET/api/v1/report/{id}gespeichertes REPORT-SPEC-JSON (404 wenn unbekannt/abgelaufen)
GET/api/v1/report/{id}/pdfHauptreport (application/pdf)
GET/api/v1/report/{id}/sources/pdfOptionaler Quellenanhang desselben Snapshots; gemeinsames PDF-Tageslimit
GET/api/v1/report/{id}/dxfbedingter, release-gebundener DXF-Export (siehe API-Leitfaden); 404, wenn nicht freigeschaltet
GET/r/{id}gerenderte Web-Ansicht + PDF-Download

GET /api/v1/report?address=… (Query) bleibt flüchtig (idempotent, schreibt nicht).

PDF

Server-seitig via @react-pdf/renderer (reines JS, kein Headless-Chromium) — robust/kalt-startarm auf Vercel. Bewusst nüchternes Layout; Zahlen 1:1 aus dem gespeicherten Report. Hauptreport und Quellenanhang teilen den pdf:-Bucket. Der optionale „Quellenanhang (PDF)“-Download ist neben dem Hauptreport im frischen Ergebnis und im Permalink erreichbar, sofern das Speichern erfolgreich war.

Quellennummern sind in beiden Dokumenten identisch und folgen der gespeicherten Quellenliste nach den bestehenden Provenienzprüfungen. Der Anhang nennt Report-ID, ursprünglichen Erstellzeitpunkt, Berichtsstand, Quellen-/Lizenzlinks, gespeicherte Releases und vorhandene Abrufzeitpunkte. Er löst keine neue Standort- oder Quellenabfrage aus. Erforderliche Attributionen, Einschränkungen, Datenlücken und Entwurfsstatus bleiben im Hauptreport. Fehlt eine Report-ID, gibt es keinen Download; unbekannte oder abgelaufene IDs liefern 404. Beide öffentlichen PDFs sind weiterhin nicht als PDF/UA-konform ausgewiesen.

Die Szenario-Vergleichstabelle kann über mehrere Seiten fließen (#751). Überlange ausgewählte Prüfpunkte werden ohne Kürzung in höchstens 650 Unicode-Zeichen und acht explizite Zeilenumbrüche lange Fragmente geteilt, möglichst an einem Leerraum. Jedes Fragment hält Nutzung, Status und Prüfpunkt in denselben Spalten zusammen; Fortsetzungen nennen die Nutzung erneut und zeigen den Spaltenkopf. Kurze Tabellen (höchstens acht Szenarien, zusammen höchstens 650 Prüfpunkt- und 250 Titelzeichen sowie acht explizite Prüfpunkt-Zeilenumbrüche) behalten den bisherigen kompakten Tabellenrahmen. Lange Tabellen brauchen mehr Platz, verändern aber weder den Snapshot noch den ausgewählten Faktor. „Details“ bleibt mit dem ersten Szenariokopf verbunden. Bei überlangen Faktoren fließen die Szenario-Angaben und Faktoren in eigenen, beschrifteten Kartenfortsetzungen. Auch eine zugleich überlange gespeicherte PV-Quellenbezeichnung wird fragmentiert; es gibt keinen unteilbaren großen Basiskartenblock. Überlange Einzelwörter erhalten lokale Trennmöglichkeiten. Normale kurze Karten behalten den bisherigen Aufbau.

npm run qa:pdf:comparison prüft normale, sehr lange, wortlange, stark mehrzeilige und paarweise lange synthetische Fälle sowie eine zugleich über A4 lange PV-Quellenbezeichnung ohne Koordinate oder Netzwerk: unverändertes JSON, vollständige Texte samt Endmarkierungen, Poppler-Glyphengeometrie ohne Kopf-/Fuß- oder Textüberlappung, keine leeren Body-Seiten und Raster aller Seiten. Die Raster werden zusätzlich visuell geprüft; der lokale Lauf ist kein Produktions- oder PDF/UA-Nachweis. Die gezielte Poppler-Renderer-Gegenprobe läuft zusätzlich im üblichen Unit-Testpfad (pdf-comparison-render.test.ts); sie scheitert auf Main vor #751.

Darstellung in Web und PDF (Änderungen seit 4. Oktober 2026)

Web-Report und PDF lesen dieselben Funktionen und denselben gespeicherten Report; die Übersetzung geschieht beim Anzeigen, gespeicherte Reports und die JSON-API bleiben unverändert.

  • Gefundene Adresse (ADR-0078, 05.10.2026): Das Deckblatt trägt unter der eingegebenen Adresse die Zeile „Gefunden: …“ samt Hinweis, wenn der Treffer außerhalb Niederösterreichs liegt oder die Eingabe mehrdeutig war. Wortlaut und Logik: lib/report/found-address.ts. Reports vor dem 05.10.2026 haben kein meta.address.resolved; die Zeile wird für sie aus meta.address.normalized verdichtet.
  • Parzellen-Umriss und Quarantäne (#655, #661): Der Umriss wird nur am betroffenen Punkt gesperrt (siehe REPORT-SPEC §4). Zeichnet das PDF den Umriss, nennt es eine schneidende Quarantänehülle als Relativsatz: „In diesem Ausschnitt fehlt möglicherweise eine Parzelle, deren Amtsgeometrie (BEV) fehlerhaft ist und die deshalb nicht dargestellt wird.“ (bei mehreren „bis zu N Parzellen“). Gemeinsame Funktion für Karte, PDF und DXF: lib/report/parcel-status.ts.
  • Report-Lücken in Nutzersprache (#661): „Noch nicht im Report“ (Web) und der Satz im PDF-Anhang zeigen Bezeichnungen und Gründe wie „Hochwasser-Gefahrenzonen (HORA)“ oder „Klimaprofil“ statt interner Schlüssel, ADR-Nummern und Dateinamen (lib/report/degraded-display.ts). meta.degraded selbst bleibt der maschinenlesbare Schlüssel-Contract.
  • ÖV-Stichtage (#645): Der Hinweis oev.stichtag_hinweis nennt den Fahrplan-Stichtag der Pilotlieferung und eine mögliche Abweichung zum Stichtag des Standort-Checks, wortgleich in Web und PDF.
  • Breitband-Klasse (#643, #648): Die Legendenklasse wird aus breitband.max_download_mbit abgeleitet, auch beim Lesen gespeicherter Reports; Web und PDF nennen den Wert darunter.
  • PVGIS (#661): Fällt die Standortertrag-Abfrage vorübergehend aus (Netzwerkfehler, HTTP 5xx), folgt genau ein Wiederholversuch innerhalb des 10-s-Budgets (REPORT-SPEC §6, Zeile solar); sonst fehlt solar und die Lücke steht in meta.degraded.

Verbindlicher Mehrseiten-Viewervertrag (#332)

Der PDF-Output ist erst viewerfest abgenommen, wenn npm run qa:pdf:poppler über scripts/verify-pdf-poppler.ts denselben produktiven renderReportPdf-Pfad für zwei feste, netzunabhängige semantische QA-Profile prüft. Beide Fixtures haben coordinate: null sowie feste Reportzeit- und Quellwerte, damit weder Karten- noch Provider-Netzaufrufe in den Nachweis gelangen:

ProfilVertrag
freeGespeicherter Free-Report mit mindestens zwei physischen Seiten; keine nur für Premium vorgesehenen Inhalte.
premiumGespeicherter Premium-Report mit mindestens zwei physischen Seiten und Premium-Inhalten.

Eine feste Seitenzahl gehört bewusst nicht zum Vertrag: neue Reportbausteine dürfen den Umfang verändern. Die Prüfung muss aber für beide Profile tatsächlich mehrseitig bleiben und jede physische Seite einbeziehen. Das Harness pinnt deshalb die aktuelle semantische Seitenzuordnung fail-closed auf Free 3 und Premium 4: Ändert ein beabsichtigter Layout- oder Inhaltsumbau den Umfang, müssen die seitenspezifischen Body-Marker im selben Diff bewusst nachgezogen werden. Diese Fixture-Map ist eine Regressionserwartung, keine dauerhafte Produktzusage über exakt 3 beziehungsweise 4 Seiten.

Automatisches Poppler-Gate

Für beide Profile gelten in Linux-/CI-Umgebungen dieselben Pflichten:

  1. Das finale PDF wird mit pdftoppm -gray und mit pdftocairo -gray -png vollständig gerendert. Erfolg nur bei Exit-Code 0, identischer Seitenzahl zum PDF und genau einer nichtleeren Rasterdatei pro physischer Seite und Renderer. pdfinfo, pdftotext und pdfinfo -url prüfen Struktur, Text und Linkziele unabhängig vom Bildrendering.
  2. Jeder Seitenhintergrund bleibt deckend weiß. Schwarze oder transparente Flächen im geprüften Außenrand, ein zu hoher Dunkelanteil, schwarze Ganzseiten und das frühere alternierende Schwarzbild sind Fehler. Eine unerwartet inhaltsleere weiße Seite ist ebenfalls ein Fehler; „weißer Hintergrund“ bedeutet nicht „leere Seite“. Zusätzlich muss der um Kopf, Fuß und Seitenrand beschnittene Body-Crop pro Backend mindestens 0,150 % sichtbar dunkle Pixel mit einer Helligkeit von höchstens 220 enthalten.
  3. Die Textschicht bleibt selektier- und extrahierbar. Der extrahierte Text enthält pro physischer Seite mehrere seitenspezifische Body-Marker sowie die lückenlose Folge Seite X / Y. Die Raster- und Textgates wirken gemeinsam: unsichtbare Textobjekte können sichtbaren Body-Inhalt nicht ersetzen.
  4. Mindestens der Report-Permalink bleibt als echte URI-Linkannotation erhalten. Weitere im Fixture enthaltene Quellen- und Lizenzlinks dürfen nicht zu reinem Bildtext degradiert werden.
  5. Die PDF-Metadaten bleiben maschinenlesbar: Titel mit Standort, LandNutzen als Autor, Betreff, Schlüsselwörter, Sprache de-AT, Creator und Producer. Die vom Parser gemeldete Seitenzahl stimmt mit beiden Renderern und den Seitenzahlen im Dokument überein.

Das Gate prüft den fertigen Output nach React-PDF-Layout und PDF-Finalisierung. Ein erfolgreicher Komponenten-Snapshot oder eine Prüfung nur der ersten Seite genügt nicht.

Manueller Viewer-Check

Vor Abschluss einer Änderung am Seiten-Chrome werden Free- und Premium-PDFs mit demselben Renderer und denselben festen Fixture-Eingaben wie im automatischen Gate zusätzlich vollständig in beiden Viewern geprüft. Das Gate selbst hält keine Reportinhalte als Artefakt zurück; die für den manuellen Check neu erzeugten Dateien werden deshalb über ihren SHA-256 eindeutig protokolliert:

  • Chromium mit PDFium: jede Seite sichtbar, weißer Hintergrund, konsistenter Kopf/Fuß, Textauswahl, anklickbare Links und lückenlose Seitenzahlen.
  • Apple Vorschau: dieselben Sichtprüfungen sowie Titel, Autor und Betreff in den Dokumentinformationen.

Das Protokoll nennt Datum, macOS-/Chromium-/Viewer-Version, Profil, Seitenzahl und SHA-256 des geprüften PDFs. Eine Sichtprüfung in nur einem Viewer oder nur eines Profils erfüllt den Vertrag nicht.

Temporäre QA-Artefakte und Abschlussnachweis

Generierte PDFs, Rasterseiten und extrahierte Textdateien sind temporäre QA-Artefakte. Das Harness erstellt dafür ausschließlich ein validiertes, eindeutiges mkdtemp-Verzeichnis, räumt es auch im Fehlerfall im finally auf und verwendet für die Rasteranalyse das gemeinsame Modul scripts/lib/pdf-poppler-regression.mjs. Die Artefakte werden nicht eingecheckt und enthalten keine echten Kunden- oder Kontodaten. Der Job pdf_poppler in npm run check:local verlangt lokal installiertes Poppler (fehlt es, meldet der Lauf den Job mit Grund als übersprungen) und führt denselben npm run qa:pdf:poppler-Befehl aus; der Workflow ci.yml (GitHub-CI) ist seit 05.10.2026 absichtlich ausgeschaltet. Die temporären Reportinhalte werden nicht als Job-Artefakt veröffentlicht.

Die technische Abnahmematrix für Issue #332 ist auf Produktionscommit 734f22f3b17de1593da72ef997f7f1eac5e38e50 erfüllt. Free und Premium bestanden beide Poppler-Renderer in PR- und Main-CI; PDFium, Chromium und Apple Vorschau wurden über alle jeweiligen Seiten geprüft. Der zwölfseitige Live-Readback bestand Text-, Link-, Metadaten-, Seitenzahl- und Rasterprüfung auf demselben Commit. Evidenz-PR #531 bindet die Issue-Schließung mit Closes #332 atomar an seinen Merge und bestätigt das ergänzte Body-Ink-/Semantikgate im separaten Poppler-CI-Lauf 32944989218. Versionen, SHA-256-Werte der konkret geprüften Dateien, Seitenzahlen, Deployment und Datenschutzgrenze stehen im Produktionsnachweis.

PDF/UA-1-Machbarkeitsgate (#333, Slice A)

Der Viewer-Vertrag aus #332 belegt Darstellung, Textschicht, Links, Metadaten und Seitenzahlen, aber noch keinen semantischen Tag-Baum und keine PDF/UA-Konformität. Issue #333 bleibt deshalb getrennt offen. ADR-0059 definiert als ersten Slice ausschließlich ein synthetisches, nicht produktives PDF/UA-1-Machbarkeits- und Vertragsprüfgate:

  1. Eine feste, per SHA-256 gebundene und ohne ausführbare Inhalte netzwerkfreie HTML-Fixture mit erfundenen Daten deckt Überschriften, Absätze, Liste, Tabelle, Link, informative Abbildung mit Alternativtext sowie dekorative und wiederkehrende Artefakte ab.
  2. Ein Chromium-basierter Kandidatenpfad muss daraus einen semantisch getaggten PDF-1.7-Output mit Sprache, MarkInfo/Marked, PDF/UA-1-XMP, Dokumenttitel und -metadaten, Outline, Strukturbaum in der festgelegten Fixture-Lesereihenfolge, seitenweise gebundener physischer Textschicht, exaktem Linkziel und genau einer informativen Figure mit Alternativtext erzeugen. Ein gepinnter veraPDF-Lauf muss ohne fehlgeschlagene PDF/UA-1-Regel enden; die Textmarkerprüfung behauptet keine Zuordnung einzelner Texte zu konkreten MCIDs.
  3. Eine semantisch entsprechende negative React-PDF-Gegenprobe muss am selben Prüfgate scheitern. Ein Prüfgate, das Kandidat und ungetaggten Bestandsoutput gleichermaßen akzeptiert, ist nicht trennscharf und damit rot.

Der lokale Validatornachweis dieses Slices ist ausgeführt: Der Kandidat bestand 106 Regeln und 4.679 Checks, die React-PDF-Gegenprobe scheiterte erwartungsgemäß an 9 Regeln und 189 Checks. Linux-CI-Lauf 32951907614 bestätigte denselben Vertrag mit 106 Regeln und 4.645 Checks sowie einer trennscharfen Gegenprobe; die Run-Artefaktliste blieb leer. Slice A ist damit auf dem Harness-Commit dbd46f3c3a94 commit- und CI-gebunden abgeschlossen. Der vollständige Lauf steht im Machbarkeitsnachweis. Der Produktionsrenderer bleibt unverändert @react-pdf/renderer; es gibt noch keinen produktiven PDF/UA-Report und keine Screenreader-Abnahme. Ein späterer Rendererwechsel benötigt getrennte Free-/Standard-/Premium-/Planer-Integration, Runtime- und Viewergates, kontrolliertes Rollout, Live-Readback und eine manuelle Screenreader-Prüfung.

PDF/UA-1-Preview-Shadow (#333, Slice B)

ADR-0060 setzt den nächsten Schritt bewusst nicht als Produktumschaltung, sondern als geschützten Vercel-Preview-Shadow um. Der Shadow rendert mit pdfkit@0.20.1 in reinem Node.js. Stock @sparticuz/chromium wurde verworfen, weil dessen unveränderter Build keinen belastbaren Tagged-PDF-Pfad bereitstellt; Full Chrome aus Slice A ist kein Serverless-Betriebsbeleg.

Der interne Vertrag lautet:

  • ausschließlich POST /api/internal/pdf-ua-shadow/[tier] mit free, standard, premium oder planer;
  • nur bei VERCEL_ENV=preview; Produktion, Development, die unterstützten Nicht-POST-Methoden, unbekannte Tiers, Queryparameter und Request-Body antworten leer mit 404; andere framework- oder proxyseitig behandelte Methoden können bereits vor dem Handler einen abweichenden 4xx liefern;
  • Vercel Deployment Protection ist die äußere Schranke; Slice B führt keinen neuen anwendungsseitigen Token ein;
  • die interne Route liegt außerhalb des Auth.js-Proxy-Matchers, liest keine App-Sitzung und setzt keine Auth.js-CSRF-/Callback-Cookies;
  • feste synthetische Fixture ohne DB, Session, Report-Persistenz, Provider, PII, request-abgeleitete Inhalte oder Netzwerkzugriff;
  • pro Tier exakt fünf Seiten, zwei informative Figuren mit Alternativtext und zwei fest erlaubte HTTPS-Links;
  • Figure-ID und Alt-Text-Digest bleiben positionsfest an die Roh-PDF gebunden; ein vertauschtes Manifest wird abgewiesen;
  • kooperativer 50-Sekunden-Response-Timer und 4.000.000-Byte-Limit; gewinnt der Timer, bleibt der Instanz-Lock bis zum tatsächlichen Renderende gesetzt. Einen synchronen Event-Loop-Stall kann er nicht abbrechen; maxDuration=60 ist dafür die äußere Vercel-Grenze. PDFs werden weder persistiert noch als CI-Artefakt veröffentlicht.

Die Tiermatrix übernimmt die bereits implementierten Produktgates, ohne die Produktdaten zu laden:

TierSzenario-Narrativesynthetischer Google-Solar-Block
freeneinnein
standardja, nur nicht rote Szenariennein
premiumja, nur nicht rote Szenarienja
planernein, tabellarischja

Der veraPDF-/Poppler-Nachweis ist für alle vier Tiers lokal, in CI und im geschützten Preview grün. Commit, CI-Jobs, Preview-Deployment, Antwortgrößen, Zeiten und Request-IDs sind im Slice-B-Nachweis gebunden. Der Produktionsreadback belegt für alle vier Tier-POSTs und einen GET leere 404-Antworten auf dem exakten Merge-SHA. Der öffentliche React-PDF-Endpunkt blieb auf demselben SHA unverändert und weiterhin nicht als PDF/UA getaggt; Issue #333 bleibt bis echter Produktintegration, Viewerregression, kontrolliertem PDF/UA-Produktrollout, dessen Live-Readback und Screenreader-Abnahme offen.

Produktnaher PDF/UA-Report-Shadow (#333, Slice C)

ADR-0061 ersetzt die direkte renderer-eigene View-Fixture als Eingang durch vier repository-fixierte, erfundene Objekte des deployten Report-Typs. Ein expliziter Mapper übernimmt nur Tier, Zeitstand, Fläche, drei Steckbrief-Risiken, sechs Szenarien, drei Förderprogramme und den optionalen Solarblock. Adresse, Koordinate, Kataster-, Quellen-, Disclaimer- sowie imagery_quality-/imagery_date- Rohfelder gelangen nicht in die PDF/UA-View.

Der Mapper prüft Fläche, Zeitstand, Kardinalitäten, Risikostatus und die drei Solarwerte fail-closed. Narrative kommen aus scenarios[].narrativ und sind nur in Standard/Premium vorhanden; Google Solar kommt aus solar.google und nur in Premium/Planer. Die Route, Preview-only-Grenze, Vercel Deployment Protection, fünf Seiten, zwei Figures, zwei Links und Runtime-Limits bleiben unverändert; erfolgreiche Antworten tragen nun X-LandNutzen-PDF-UA-Shadow: slice-c-1.

Der Slice-C-Nachweis ist für alle vier Tiers lokal und im geschützten Preview mit veraPDF und beiden Poppler-Backends grün. Er bindet Commit, Deployment, Header, Request-IDs, negative Pfade und erneut geprüfte PDF-Downloads. Das ist ein produktnaher Vertragsbeleg, aber kein Zugriff auf gespeicherte oder reale Reports. Merge-Commit 59036df7aca8a70025efcbf8f0d8dd467700cb9c und Produktionsdeployment dpl_FbKhtfArLz6wnDTAaRn3aq1bVJh9 belegen für alle vier Tier-POSTs und einen GET leere private 404-Antworten. Damit ist die Produktionstrennung verifiziert, nicht der PDF/UA-Produktpfad freigegeben. Vollständige Realreportsektionen, Karten-Alternativtexte, Viewerregression, kontrollierter Rollout, Produkt-Live-Readback und Screenreader-Abnahme bleiben offen; der öffentliche React-PDF-Pfad bleibt unverändert.

Repräsentative PDF/UA-Report-Sektionsparität (#333, Slice D)

ADR-0062 erweitert dieselben vier synthetischen Report-Fixtures um alle für einen repräsentativen vollständigen Report vorhandenen Bereiche. Ein fail-closed Mapper erzeugt exakt 31 geordnete Sektionen aus Lage, Umwelt, Restriktionen und Governance. Fehlende Bereiche, eine andere Reihenfolge oder unerwartete Kardinalitäten brechen vor dem Renderer ab.

Der Shadow rendert nun exakt acht A4-Seiten. Drei informative Figures sind über ID und Alternativtext-Digest gebunden: Parzellenfläche und künstliche ID, vier gemappte Risikobereiche sowie die Kostenwerte aller sechs Szenarien bestimmen die jeweiligen Alternativtexte. Damit sind die Texte datenbasiert, bleiben aber vollständig synthetisch und enthalten weder Adresse noch Koordinate. Zwei Links und 16 Pagination-Artefakte vervollständigen den festen Strukturvertrag. Erfolgreiche Preview-Antworten tragen X-LandNutzen-PDF-UA-Shadow: slice-d-1.

Der Slice-D-Nachweis bindet die lokale Vier-Tier-Prüfung und den exakten Release: 31 Sektionen, acht Seiten, drei Figures, veraPDF 1.30.2 sowie beide Poppler-Backends sind lokal, im geschützten Preview dpl_Ew4nDGJZYUX9fRyiNSuqSz8FFutU und in CI grün. Merge-Commit 056eafb0eddd0ec6369f71db65862f37b788b152 und Produktionsdeployment dpl_2bsKrqhys1CuRFQV6QUV6HiSJghg belegen, dass die interne Route in Produktion für alle vier Tier-POSTs und einen GET mit leeren privaten 404-Antworten geschlossen bleibt.

Die Route liest weiterhin keine Datenbank, Session, gespeicherten Reports, Providerdaten oder Requestinhalte. Der öffentliche React-PDF-Pfad bleibt unverändert und nicht als PDF/UA getaggt. Echte gespeicherte Reports, Karten-/Viewerregression, kontrollierter Produktrollout, PDF/UA-Live-Readback und manuelle Screenreader-Abnahme bleiben offen; Issue #333 bleibt offen.

Gespeicherter PDF/UA-Report-Roundtrip (#333, Slice E)

ADR-0063 bindet den vollständigen synthetischen Vier-Tier-Vertrag an den tatsächlichen Store-Pfad. Ein verpflichtender Integrationstest führt die exakten saveReport()- und getReport()-SQL-Abfragen gegen das isolierte PostgreSQL-Schema pdf_ua_stored_report_test aus, schreibt echtes jsonb und rendert erst das wieder geladene StoredReport-Objekt. Das Schema wird nach dem Lauf entfernt.

Die Persistenznormalisierung ist Teil des Gates: solar.google, die Quelle solar_google und der zugehörige Disclaimer-Baustein dürfen in keinem Tier reports.report_json erreichen. Der fail-closed Mapper übernimmt weder Report-ID, Adresse, Eigentümer-ID noch exakten Opt-in-Zeitpunkt. Die PDF/UA- View enthält nur den kanarischen Speicherpfad, das Speicherdatum, boolesche Konto- und Opt-in-Zustände sowie die Aussage, dass Google Solar nicht persistiert wurde.

Der Preview-Endpunkt liest selbst keine Datenbank, sondern verwendet eine repository-fixierte Kopie desselben persistenznormalisierten Kanaris. Erfolgreiche Antworten tragen X-LandNutzen-PDF-UA-Shadow: slice-e-1; der Vertrag bleibt bei acht Seiten, 31 Sektionen, drei Figures, zwei Links und 16 Pagination-Artefakten. Der öffentliche React-PDF-Pfad bleibt unverändert und nicht als PDF/UA getaggt. Karten-/Viewerregression, kontrollierter Produktrollout, PDF/UA-Live-Readback und manuelle Screenreader-Abnahme bleiben offen; Issue #333 bleibt offen.

PR #541, CI-Lauf 33163812578 und Preview dpl_AejPoE9E4Joeh9J1ireiHZdKz7k7 belegen den Vier-Tier-Vertrag. Merge-SHA f5d57d9ac0b4c57a576fa64b9fbddbff49ef8726 und Produktionsdeployment dpl_GqMcWA5uHvML97ZrciT4dkzPKoQD halten die interne Route für alle vier Tier-POSTs und einen GET leer 404; dies ist Produktionstrennung, kein PDF/UA-Produktrollout.

Apple-PDFKit-/Quartz-Viewerregression (#333, Slice F)

ADR-0064 erweitert npm run qa:pdf:ua-shadow auf macOS um einen zweiten PDF-Stack. Apple PDFKit lädt die vier im selben Lauf erzeugten synthetischen Slice-E-Dateien und bindet je acht Seiten, zwei Linkannotationen und acht seitenspezifische Textmarker. Quartz rendert anschließend 32 Seiten als 1.190 × 1.684 Pixel große 8-Bit-Graustufenbilder; die vorhandenen Rastergrenzen prüfen helle Ränder, Helligkeit, Schwarzflächen und sichtbaren Body-Ink fail-closed.

Der Slice-F-Nachweis belegt lokal auf macOS 26.6.2 mit Swift 6.3.3 4/4 geladene Dateien und 32/32 gerenderte Seiten. Die mittlere Helligkeit lag zwischen 245,13 und 251,63, der Body-Ink zwischen 2,800 % und 7,428 %. Auf Nicht-macOS-Systemen wird der fehlende Apple-Stack ausdrücklich gemeldet; veraPDF und Poppler bleiben die plattformunabhängigen CI-Gates.

PR #543, CI-Lauf 33166935667 und Preview dpl_5NCcXGThbbpocRGCsfhS2VsZMg5H belegen den automatisierten Apple-Readback auf den vier tatsächlich deployten Shadow-Dateien. Merge-SHA e998d3e42886adef491eb489f5c8a874aca652a3 und Produktionsdeployment dpl_D4fhMzmmwCDvhGrTcAN3Mm3TMcEL halten die interne Route für vier Tier-POSTs und einen GET leer 404; dies ist Produktionstrennung, kein PDF/UA-Produktrollout.

Die nachgelagerte manuelle Abnahme vom 4. September 2026 öffnete die exakt SHA-256-gebundene Premium-Preview-Datei in Apple Vorschau 11.0. Alle acht Seiten waren vollständig sichtbar und mit aktivem VoiceOver per Tastatur erreichbar. Der native Accessibility-Baum enthielt 19 Überschriften, sieben Tabellen, sechs Listen, drei beschriebene Abbildungen und zwei benannte Links; VoiceOver wurde danach wieder ausgeschaltet. Damit sind Apple Vorschau und die VoiceOver-Stichprobe für den synthetischen Premium-Shadow abgeschlossen.

Route und Erfolgsheader bleiben slice-e-1, der öffentliche React-PDF-Pfad bleibt unverändert. Chrome/PDFium-Tag-Semantik, echte Karteninhalte, kontrollierter Produktrollout und PDF/UA-Live-Readback eines echten Nutzerreports bleiben offen; Issue #333 bleibt offen.

Zugänge (Parität)

  • UI: /report → nach Erzeugung „Permalink" + „PDF herunterladen".
  • API: siehe Tabelle, OpenAPI unter /docs/api.
  • MCP: Tool export_report → { permalink, pdf_url }.
  • Skill: report.mjs gibt am Ende Permalink + PDF-URL aus.

Grenzen

„Wer den Link hat, sieht den Report" (Teilen-Use-Case) — Schutz = unerratbare ID + noindex + 90-Tage-Retention. Privatere Modi (Auth/Ablauf-Token) sind als Folge möglich.

Chrome/PDFium-Viewerregression (#333, Slice G)

Slice G ergänzt den echten Chrome/PDFium-Viewer für vier synthetische Tiers mit je acht Seiten. Der gepinnte Chrome for Testing 148.0.7778.96 prüft vollständige A4-Seiten, seitenspezifischen Text und benannte Links. Darstellung und Tag-Semantik sind getrennte Gates: Im Standardviewer fehlen Tabellen-, Listen- und Bildrollen. Ein grüner Darstellungscheck ist deshalb keine vollständige Chrome-Barrierefreiheitsabnahme.

Der öffentliche React-PDF-Pfad bleibt unverändert, der Shadow bleibt slice-e-1 und Preview-only. Chrome-Tag-Semantik, echte Karteninhalte, kontrollierter Produktrollout und PDF/UA-Live-Readback eines echten Nutzerreports bleiben offen; Issue #333 bleibt offen. Vertrag und verifizierter Release-Stand: Slice-G-Entscheidung und Slice-G-Nachweis.

Slice G wurde über PR #548 produktiv ausgeliefert: Merge 8ccc9fe28236c894bee69de5502ff11b98c592c0, Deployment dpl_Y8GsNSbTzn5swXMfVsxU7Zt5QRiw, Main-CI 33914599076. Lokal, im Linux-CI und mit den deployten Preview-Dateien bestanden 32/32 Chrome/PDFium-Seiten; sechs Dokuportalseiten und die geschlossene Shadow-Produktionsgrenze wurden live rückgelesen. Die Chrome-Tag-Semantik bleibt ausdrücklich nicht abgenommen. Der Slice-G-Nachweis enthält die commitgebundenen Prüfergebnisse.

PDF/UA-Parzellengeometrie (#550, Slice H)

Slice H ersetzt die feste Grundstücksskizze im geschützten synthetischen Shadow durch validierte Polygon-/MultiPolygon-Vektoren. Der optionale interne Eingang bindet den bestehenden pdf_parcel_outline-Export an den Reporthash und Public-/Export-Release. Rechte, Coverage, Frische und Consumerbindung bleiben Voraussetzung. Fehlende oder abgelehnte Geometrie erzeugt einen Absatz statt einer Figure; Innenringe werden sichtbar als nicht unterstützt abgelehnt. Alt-Text, hervorgehobener Treffer, Nachbarn, Nordrichtung sowie Herkunft und Lizenz folgen dem tatsächlichen Eingang.

Der positive Slice-H-Previewbeleg bleibt ausdrücklich synthetisch. Der damalige fehlende echte Geometriebeleg wird im getrennten Slice I weitergeführt; dessen aktueller Abnahmestand steht im verlinkten Slice-I-Nachweis. Chrome-Tag-Semantik, weitere Karten, Produktrollout und echter PDF/UA- Nutzerreport bleiben offen; #333 bleibt offen. Der öffentliche Renderer bleibt React-PDF. Slice-H-Vertrag und Slice-H-Nachweis trennen lokale Implementierung, CI/Preview, Produktion und Live-Readback.

PDF/UA: echte interne Parzellenreferenz (#553, Slice I)

Slice I ergänzt einen festen internen QA-Einstieg für den vorhandenen anonymen Schul-Referenzreport aus dem DXF-Produktionsnachweis. Ein enger lesender Selektor, getReport, die atomaren pdf_parcel_outline-Gates und wiederholte Report-/ Quellenprüfung binden genau eine Zielparzelle an die finalisierte PDF/UA-Figure. Es gibt keine neue HTTP-Route und keine frei wählbaren Report- oder Geometrieeingaben.

Der eigene QA-Mapper bewahrt die echte Reportidentität. Zielparzelle, Schuladresse, gespeicherte DKM-Fläche und BEV-Herkunft sind real; alle übrigen Prüfbestandteile bleiben sichtbar synthetisch. Die vorhandene Kartenadapter-Vereinfachung von 23 Originalstützpunkten auf neun Koordinaten ist von der unveränderten Auswahl und metrischen Projektion getrennt mit Hashes belegt. Innenringe der Zielparzelle, Mehrdeutigkeit sowie negative Rechte-/Release-/Coverage-/Quality-/Freshness-/ Quarantänegates bleiben gesperrt. Das H-Chrome-Profil 20/7/6/3/2 bleibt erhalten; I hat ein eigenes semantisches 20/7/6/3/4-Profil.

Die echte interne Referenz ist auf Merge 5609b182db810983e0240eacbab5cfce2eb9ac5a mit aktueller Produktionsquelle lesend geprüft. PR #554, der gebaute Preview und zwölf schreibfreie Produktionschecks sind bestanden. Die vollständige Main-CI 33953550809 ist grün. READY-Deployment dpl_FDEorR7WyuagkPvSw281WnREWSXB und getrennte CLI-/HTTP-Belege stehen im Slice-I-Nachweis. Die reale I-PDF wurde intern erzeugt; die HTTP-Prüfung bestätigt Dokumentation und Produktionssperre des synthetischen H-Shadows. ADR-0067 dokumentiert Vertrag und Abnahmetests. Chrome-Tag-Semantik, weitere echte Karten, öffentlicher PDF/UA-Rollout und echter PDF/UA-Nutzerreport bleiben offen; #333 bleibt offen. Öffentlicher React-PDF-Pfad und synthetischer Preview bleiben erhalten. Es wurde kein neuer Report angelegt.