Report-Spezifikation — JSON-Contract
Nahversorgungsdarstellung (#564)
900 m Luftlinie als Näherung für 15 Minuten zu Fuß; keine berechnete Route. Umwege und Barrieren werden nicht berücksichtigt. Radius und Ampelschwellen bleiben gemäß #463 unverändert. Der Ausgabevertrag regelt neue Reports und die rein darstellungsseitige Korrektur bekannter Alttexte.
Dieses Dokument beschreibt den deployten Report-JSON-Contract von
LandNutzen. Verbindliche technische Quelle ist lib/report/types.ts; die
Assembler- und Tierlogik liegt in lib/report/build.ts und
lib/report/tier.ts. Frühere Zielannahmen zu einem separaten
Puppeteer-Premium-Template, einem Top-Level-narrative, confidence,
bestand oder grundbuch sind nicht Teil des aktuellen Contracts.
Der öffentliche PDF-Pfad rendert alle Tiers mit @react-pdf/renderer und
pdf-lib. Der reine Node.js-PDF/UA-Shadow aus
ADR-0060 verwendet nur feste
synthetische Daten in geschützten Vercel Previews und verändert weder diesen
JSON-Contract noch den Produktionsrenderer.
Der optionale Quellenanhang (/api/v1/report/{id}/sources/pdf, #739) verwendet
unverändert dieselbe gespeicherte sources-Liste. Die Nummern sind reine
Darstellung und ergänzen keine Felder im JSON-Contract. Hauptreport und Anhang
bleiben über Report-ID, meta.generated_at und meta.as_of zuordenbar.
Querverweise: PRODUCT.md (nicht im öffentlichen Doku-Portal verfügbar) · ARCHITECTURE.md (nicht im öffentlichen Doku-Portal verfügbar) · ADR-0015 Kostenschätzung (nicht im öffentlichen Doku-Portal verfügbar) · ADR-0013 PDF (nicht im öffentlichen Doku-Portal verfügbar) · ADR-0006 Auth/Multitenant (nicht im öffentlichen Doku-Portal verfügbar) · ADR-0016 Grundbuch (nicht im öffentlichen Doku-Portal verfügbar) · ADR-0014 HORA-Pass (nicht im öffentlichen Doku-Portal verfügbar) · DATA-SOURCES.md · POC-PLAN.md (nicht im öffentlichen Doku-Portal verfügbar)
1. Grundprinzipien
1.1 Trennung Engine ↔ LLM (verbindlich)
Konsistent mit ADR-0015 (nicht im öffentlichen Doku-Portal verfügbar)
berechnet die Engine Zahlen, Ampeln, Kosten und Förderzuordnungen. Erst danach
darf ein optionaler LLM-Aufruf die Fakten nicht roter Nicht-PV-Szenarien in ein bis
zwei Sätze umformulieren. Die LLM-Eingabe filtert rote und PV-Szenarien; die
Zuordnung einer formal gültigen Modellantwort prüft die Ampel derzeit jedoch
nicht ein zweites Mal. Das Ergebnis liegt ausschließlich in
scenarios[].narrativ und bleibt unverbindlicher Zusatztext.
Das Runtime-Gate erlaubt diesen Aufruf nur für standard und premium.
free und planer bleiben auch bei gesetztem AI-Gateway-Key ohne LLM-Aufruf.
Ohne Key, bei Budgetgrenze, Timeout, Provider- oder Parsefehler bleibt der
Report vollständig und das Narrativ fehlt; meta.degraded weist den Grund
aus.
Der Prompt verbietet neue Zahlen und Bewertungen. Ein separater
Regex-Zahlenabgleich, Auto-Retry oder Top-Level-guard_passed ist derzeit
jedoch nicht implementiert und darf nicht als vorhandene Schutzschicht
behauptet werden. Zahlen und fachliche Aussagen bleiben deshalb ausschließlich
in den strukturierten Engine-Feldern verbindlich; ein Narrativ ist optionaler
Fließtext.
1.2 Herkunfts-Klassen pro Feld
| Herkunft | Bedeutung |
|---|---|
| `engine` | Deterministische Regel-/Geo-/Cost-Engine auf dem bereits assemblierten Input. Nie LLM. |
| `llm` | Optionaler Text ausschließlich in scenarios[].narrativ für standard/premium; nicht verbindlich. |
| `static` | Konstante aus Konfig/Recht, etwa Disclaimer-, Lizenz- und Tiertexte. |
| `external` | 1:1 extern übernommene Werte; soweit anwendbar quellen- und releasegebunden. |
1.3 Determinismus & Caching
Bei gleichem assembliertem Layer-Input, Tier und Regelwerk erzeugt die
Szenario-Engine dieselben Zahlen und Ampeln. Externe Abfragen,
meta.generated_at, meta.as_of, Laufzeiten und optionale Narrative können
zwischen zwei Reportläufen abweichen. Ein gespeicherter Report ist deshalb die
stabile Momentaufnahme; es gibt keinen dokumentierten Cache-Schlüssel aus
parcel_id, Engine- und Config-Version im aktuellen Top-Level-Contract.
1.4 Adresszuordnung als Voraussetzung (#561)
Neue Reportläufe benötigen einen vom österreichbeschränkten Geocoder bestätigten
Standort. Eine leere Trefferliste liefert 422 address_not_found mit einem
Hinweis zur Korrektur von Straße, Hausnummer, Postleitzahl und Ort in Österreich.
Provider-/HTTP-/Timeout-/ungültige Antwortfehler liefern stattdessen
503 geocoding_unavailable; sie behaupten keinen Adressfehler. Beide Fälle
enden vor Quellenabfragen, Szenarien und Reportpersistenz. Es entstehen weder
Report-ID noch Permalink oder PDF. GET und POST der v1-API verwenden den
Fehler-Envelope { error: { code, message } }; der Legacy-Endpunkt bewahrt
seinen String unter error und ergänzt code. MCP reicht beide Zustände als
Toolfehler weiter, einschließlich export_report.
Gültige AT-Treffer mit einzelnen fehlenden Datenebenen bleiben Teilreports mit
meta.degraded. Ein unbekanntes Bundesland wird als at-unknown / „Bundesland
nicht bestimmt“ ausgegeben. Historisch gespeicherte Reports mit
meta.coordinate=null bleiben unverändert gespeichert; Web/PDF zeigen ihre
Region neutral als „Nicht bestimmt“. Alte JSON-Snapshots werden nicht umgeschrieben.
Passen mehrere Adressen auf die Eingabe, fragt der Report-Pfad Nominatim einmal
mit bis zu zehn Kandidaten ab und wählt den ersten Treffer in Niederösterreich,
sonst den ersten überhaupt (ADR-0078, seit 05.10.2026). Web und PDF nennen die
gefundene Adresse als „Gefunden: …“; die Felder meta.address.resolved und
meta.address.ambiguous stehen in §3. Eine Adresse außerhalb Niederösterreichs
liefert einen Teilreport ohne NÖ-Ebenen mit entsprechendem Hinweis.
Die vorhandenen Tageslimits zählen Anfragen, einschließlich fehlgeschlagener Adressversuche, sobald diese den bestehenden Auth-/Quota-Guard erreichen. Der atomare Zähler bleibt vor dem Geocoder; keine Erstattung und kein neuer Bucket. UI und direkte Reportseite lassen die Adresse nach Fehlern bearbeitbar und zeigen keinen Report-/Download-Erfolg. Prüfstand.
1.5 PV-Standortertrag (#562)
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.
Neue solar-Snapshots tragen systemverlust_pct und modell_leistung_kwp.
Neue PV-Szenarien tragen pv_report_mode: "specific_yield_v1", cost: null,
cost_components: [] und ausschließlich den spezifischen Ertrag in kennzahlen,
sofern vorhanden. Alte GET-/JSON-Exporte bleiben im historischen Vertrag erhalten.
2. Top-Level-Schema
```ts interface Report { meta: ReportMeta; parcel: ReportParcel; widmung: ReportWidmung | null; risiko: ReportRisiko; scenarios: ReportScenario[]; foerderungen: ReportFoerderung[]; ruleset: ReportRuleset; sources: SourceAttribution[]; disclaimer: ReportDisclaimer;
steckbrief?: ReportSteckbriefEintrag[]; standort_bilanz?: ReportStandortBilanz | null; solar?: ReportSolar | null; erreichbarkeit?: ReportErreichbarkeit | null; breitband?: ReportBreitband | null; laerm?: ReportLaerm | null; oev?: ReportOev | null; poi_dichte?: ReportPoiDichte | null; schutzgebiete?: ReportSchutzgebiete | null; denkmal?: ReportDenkmal | null; zweitwohnsitz?: ReportZweitwohnsitz | null; pendeln?: ReportPendeln | null; demografie?: ReportDemografie | null; boden?: ReportBoden | null; bahn?: ReportBahnanbindung | null; erdbeben?: ReportErdbeben | null; lcz?: ReportLcz | null; wlv?: ReportWlv | null; brachflaechen_potenzial?: ReportBrachflaechenPotenzial | null; } ```
Pflicht-Top-Level-Keys für alle Tiers sind meta, parcel, widmung,
risiko, scenarios, foerderungen, ruleset, sources und
disclaimer. meta.report_id und meta.permalink sind nur nach erfolgreicher
Persistenz vorhanden. Es gibt aktuell weder schema_version noch eine zweite
Top-Level-report_id; ebenso keine Top-Level-Blöcke bestand, grundbuch,
confidence oder narrative.
`steckbrief` ist optional und rein abgeleitet: eine Ampel-Übersicht
aller vorhandenen Indikator-Layer (Widmung, Hochwasser, Altlasten, Radon,
Lärm, Schutzgebiete, Denkmal, Breitband) für den schnellen Blick oben im
Report. Keine eigene Datenquelle, kein Gesamt-Verdikt. Einträge:
`{ schluessel, label, ampel: gruen|gelb|rot|unbekannt, kurz }`; feste
Reihenfolge, fehlende Layer werden ausgelassen (Lücke steht in `meta.degraded`).
Die Altlasten-Ampel wird aus der release-gebundenen Punktprüfung abgeleitet:
Adresspunkt von einer veröffentlichten Altlast mit ST_Covers abgedeckt →
rot, nächste veröffentlichte Altlast innerhalb 1 km → gelb, sonst grün.
Fehlt der qualifizierte Quellenvertrag, darf kein grünes Ergebnis entstehen.
`standort_bilanz` ist optional/null und rein abgeleitet aus dem
`steckbrief`: ein kompakter Risiko-Rollup, den die Szenario-Engine als
Entscheidungs-Kontext über den Nutzungsszenarien ausweist. Form:
`{ rot: [{schluessel,label}], gelb: [{schluessel,label}], gruen_count: int, hinweis: string }`.
Reine Zählung/Auflistung der nicht-grünen Indikatoren — KEIN Gesamt-Verdikt
(eine einzelne „Gesamtampel" wäre über-claimend). null, wenn kein Indikator vorliegt.
ÖV-Kappung (M11, Fachlicher Entscheid 16.07.2026): Der Rollup ist der universelle
„jede Nutzung"-Kontext. Eine ÖV-Erschließung ohne Güteklasse (oev.ampel == "rot") ist für die ÖV-nachrangigen Nutzungen Freiflächen-PV und Lager
kein kritischer Standortfaktor und kann in diesem universellen Rollup nicht
rot zählen — sie wird auf gelb (zu prüfen) gekappt. Der ÖV-Eintrag im
steckbrief selbst bleibt rot (Rohsignal „keine Güteklasse"); nur sein
Beitrag zur standort_bilanz wird gekappt, und sein kurz-Text weist die
Nachrangigkeit aus. Die Kappung betrifft ausschließlich schluessel: "oev";
alle anderen roten Indikatoren zählen unverändert rot.
3. `meta` — Identität, Stichtag, Tier
| Feld | Typ | Pflicht | Herkunft | Beschreibung |
|---|---|---|---|---|
| `meta.address.raw` | string | ja | external | Vom User eingegebene Adresse; wird dem Szenario-Narrativ nicht übergeben. |
| `meta.address.normalized` | string | null | ja | engine | Normalisierte Geocoder-Adresse oder null, wenn keine Geokodierung gelang. |
meta.address.resolved | { street, house_number, postcode, place } (je string | null) oder null | optional | engine | Strukturierte Fundadresse des gewählten Geocoder-Treffers (Nominatim addressdetails); Grundlage der Zeile „Gefunden: …“ in Web und PDF. Seit 05.10.2026 (ADR-0078); ältere Reports haben das Feld nicht. |
meta.address.ambiguous | boolean | optional | engine | true, wenn mehrere unterschiedliche Adressen auf die Eingabe passten; der Report-Pfad wählt dann den ersten Treffer in Niederösterreich, sonst den ersten österreichweit (ADR-0078). Fehlt in älteren Reports (= unbekannt). |
meta.coordinate | Objekt oder null | ja | engine | WGS84-lat/lon samt source: "nominatim" oder "bev"; bei fehlender Geokodierung null. |
| `meta.region.slug` | string | ja | engine | Regionaler Slug, zum Beispiel at-noe; fail-safe Ableitung aus Geocoder/Adresse. |
| `meta.region.name` | string | ja | engine | Sichtbarer Regionsname. |
| `meta.report_tier` | free/standard/premium/planer | ja | engine | Technisches Tier. Die provisorische Auflösung und fehlende Billing-Zusage stehen in §15. |
| `meta.as_of` | ISO-8601 date | ja | engine | Report-Stichtag des Laufs. Fachliche Quellenstände bleiben in den jeweiligen Layer-/Source-Feldern. |
| `meta.generated_at` | ISO-8601 datetime | ja | engine | Generierungszeitpunkt. |
| `meta.degraded` | { layer, reason, fallback }[] | ja | engine | Ausgefallene, gesperrte oder unvollständige Layer; leer, wenn kein Degradierungsgrund vorliegt. |
| `meta.timings` | { layer, ms }[] | optional | engine | Diagnostische, nicht deterministische Laufzeiten. Überlappende Werte dürfen nicht summiert werden. |
| `meta.report_id` | string | optional | engine | Permalink-ID nach erfolgreicher Persistenz. |
| `meta.permalink` | string | optional | engine | Teilbarer Pfad /r/{report_id} nach erfolgreicher Persistenz. |
| `meta.saved` | boolean | optional | engine | Kontogebundene dauerhafte Ablage; fehlend/false bedeutet nicht automatisch einen Persistenzfehler. |
meta.org_id, meta.requested_by, meta.engine_version und
meta.config_version sind im aktuellen ReportMeta nicht vorhanden. Die
fachliche Ruleset-Version steht stattdessen unter ruleset.config_version.
`meta.degraded[]`-Element: `{ "layer": "geosphere", "reason": "source_not_released", "fallback": "Kein qualifizierter aktiver SPARTACUS-v3-Release verfügbar" }`. `meta.timings[]`-Element: `{ "layer": "parcel", "ms": 812 }`. Einzelne Layer laufen konkurrent; `layers_total` umfasst den gesamten Fan-out, `report_total` zusätzlich Szenario-Engine und ein gegebenenfalls erlaubtes Narrativ. Für den HTTP-Pfad liefert `/api/v1/report` außerdem den standardisierten `Server-Timing`-Header mit `guard`, `auth`, `session_tier`, `rate_limit`, `report_build`, bei POST `body_parse`/`persistence` und `total`. `X-LandNutzen-Timing` transportiert denselben Wert als namespaced Produktions-Fallback, falls ein vorgeschalteter Host den Standard-Header entfernt.
4. `parcel` — BEV-Kataster
Der qualifizierte Snapshot-Pfad liest ausschließlich den Active Release von
bev-parzellen/public für public_display/point_lookup. Der erste Vertrag
deckt nur bev-gaenserndorf-pilot-v1 mit der BBox
[16.64,48.28,16.80,48.40] ab. Außerhalb kann ein getrennt behandelter
BEV-WMS-Legacy-Pfad ausschließlich die Grundstücksidentität liefern; er erbt
weder Release noch Snapshot-Geometrie. Der Neun-Bundesländer-Testkorpus
belegt nur das Identitätsformat, keine österreichweite Datenabdeckung.
Neue Reports tragen die Lookup- und Provenienzfelder. Bereits gespeicherte Reports bleiben wegen der optionalen Felder lesbar.
| Feld | Typ | Pflicht | Herkunft | Beschreibung |
|---|---|---|---|---|
| `parcel.found` | boolean | ja | engine | Ob der Report eine verwendbare Parzellenidentität besitzt. Kann bei einem separaten WMS-Fallback auch dann `true` sein, wenn der qualifizierte Snapshot-Lookup `outside_coverage` oder `failed` meldet; es ist deshalb nicht mit einem Release-Treffer gleichzusetzen. `false` führt zum Teilreport. |
| `parcel.lookup_status` | enum `hit`|`no_hit`|`outside_coverage`|`ambiguous_boundary`|`source_not_released`|`failed` | optional; für neue Reports | engine | Öffentliche Abbildung des release-gebundenen Lookups. Der interne Adapter bleibt fünfstufig und liefert Gate-Fehler technisch als `failed`; der Report projiziert Rechte-, Zweck-, Legacy-, Active-Release-, Freshness- und Quality-Gates auf `source_not_released`. Operative Provider-/Runtimefehler bleiben `failed`. `no_hit` ist nur innerhalb qualifizierter Coverage zulässig; Boundary-Mehrdeutigkeit wird nicht auf einen Treffer reduziert. |
| `parcel.lookup_failure_code` | enum `rights_blocked`|`purpose_not_allowed`|`legacy_unverified`|`no_active_release`|`source_unavailable`|`freshness_rejected`|`quality_rejected`|`internal_error` | bei `source_not_released` oder `failed` | engine | Maschinenlesbarer Grund ohne interne Rohdaten. Eine konservative Quarantäne-Envelope verhindert einen sonst ungesicherten `no_hit`; ein gültiger veröffentlichter Innentreffer hat Vorrang, ein bloßer Randtreffer neben Quarantäne bleibt `quality_rejected`/`source_not_released`. Operative Fehler werden nicht als Release-Gate umetikettiert. |
| `parcel.parcel_uid` | string | bei qualifiziertem `hit` | engine | Reversible interne LandNutzen-ID AT:{kg_code}:{parcel_number_normalized}; keine amtliche neue Kennung. |
| `parcel.source_release_id` | UUID | bei release-gebundenem `hit`, `no_hit` oder `ambiguous_boundary` | engine | Exakter qualifizierter Source Release des Snapshot-Ergebnisses. Fehlt beim getrennten WMS-Legacy-Pfad. |
| `parcel.match_method` | enum `point_in_polygon`|`wms_getfeatureinfo` | bei `found`, soweit verfügbar | engine | Tatsächliche Matchmethode; Snapshot-Treffer verwenden `point_in_polygon`, Legacy-Fallbacks `wms_getfeatureinfo`. |
| `parcel.match_confidence` | number | optional | engine | Technische Matchkonfidenz 0..1; ersetzt keine Vermessungs- oder Rechtsauskunft. |
| `parcel.grundstuecksnummer` | string | wenn `found` | external | Unveränderter BEV-Quellalias der Grundstücksnummer. |
| `parcel.katastralgemeinde` | string | wenn `found` | external | Katastralgemeinde samt Nummer, soweit vom Bezugskanal geliefert. |
| `parcel.area_m2` | number | null | nur beim qualifizierten Snapshot-Treffer | engine | Aus der unverändert veröffentlichten DKM-Geometrie berechnete Fläche. Der WMS-Fallback liefert keine Fläche. |
| `parcel.area_tolerance_pct` | number | null | nur beim qualifizierten Snapshot-Treffer | static | Toleranzmarge DKM ≠ Grundbuch — Fachlicher Entscheid 11.08.2026 (#81): Grenzkataster null (vermessungsgenau, keine Angabe), sonst ±10 % Flächenschärfe. |
| `parcel.source` | enum `dkm_mirror`|`wms` | wenn `found` | engine | Tatsächlicher Datenweg. `dkm_mirror` bezeichnet den release-gebundenen Snapshot-Pfad; `wms` den getrennten Identitätsfallback ohne qualifizierte Geometrie. |
| `parcel.kataster` | enum `grenzkataster`|`grundsteuerkataster`|`unbekannt` | wenn `found` | external | Kataster-Status aus BEV-RSTATUS. Anzeige (#320): Report, Karte und PDF nutzen katasterAnzeige() (lib/report/labels.ts (nicht im öffentlichen Doku-Portal verfügbar)); unbekannt wird sichtbar ausgewiesen. Keine Rechtsauskunft. |
| `parcel.bev_stand` | ISO-8601 date | bei release-gebundenem Ergebnis; bei WMS-Identität | engine | Snapshot: fachlicher Quellenstand des Active Release. WMS: Abrufdatum, weil dieser Kanal keinen qualifizierten Snapshot-Stand liefert. |
| `parcel.hinweis` | string | wenn kein eindeutiger qualifizierter Treffer | engine | Sichtbarer Hinweis für Grenze, Lücke, Mehrdeutigkeit oder Fehler. |
| `parcel.vermessung_disclaimer` | string | sobald Parzellendaten angezeigt werden | static | Fixer Pflicht-Hinweis, dass DKM-Geometrie und -Fläche keine rechtsverbindliche Vermessung ersetzen. |
Ein WMS-Treffer bewahrt den Snapshot-Zustand: außerhalb der Coverage
outside_coverage, bei einem Release-Gate source_not_released, bei einem
operativen Fehler failed. Der Fallback macht daraus nie einen qualifizierten
Snapshot-Treffer; meta.degraded führt entsprechend
ausserhalb_pilot_spiegelung, source_not_released oder
request_fehlgeschlagen.
Der Report-JSON-Vertrag enthält keine Rohgeometrie und keine
geometry_ref. ADR-0055 (nicht im öffentlichen Doku-Portal verfügbar)
trennt den öffentlichen public_display/point_lookup strikt vom Pilotkanal
bev-parzellen/export. Nur ein qualifizierter Export-Release für
bev-gaenserndorf-pilot-v1 darf über
parcel_versions_export_current Geometrie ausgeben.
Der bestehende Public-Snapshot umfasst 29.746 veröffentlichte Originalpolygone
und 15 Quarantänefälle. Sein historischer ogr2ogr -spat-Filter übernimmt auch
Pilotgrenzen schneidende Originalgeometrien vollständig; der separate Export
übernimmt dagegen ausschließlich unveränderte ST_CoveredBy-Polygone.
outsideCoverageRowCount beträgt 1.211 und macht alle nicht vollständig
umschlossenen, ausgeschlossenen Public-Parzellen nachvollziehbar; 28.535
zulässige Originalpolygone sind im separaten Export-Pointer produktiv aktiviert. Public-
und Export-Mengen sowie ihre Manifeste sind unabhängig gebunden. Ein im Pilot
liegender Report-Standort allein öffnet keine teilweise oder vollständig
außerhalb liegende Originalgeometrie; Clipping ist ausgeschlossen.
Der bestehende, rate-limitierte /api/parcel-geojson-Pfad verwendet
parcel_geojson ausschließlich als internes Kartenoverlay eines
bereits gespeicherten wertschöpfenden Standortreports; er ist kein
eigenständiges BEV-Datenprodukt. Die verpflichtende reportId muss einen
vorhandenen Report mit exakt übereinstimmender Koordinate und mindestens einer
Nicht-BEV-Datenquelle bezeichnen. Fehlende oder unbekannte IDs bleiben 404,
abweichende Standorte oder fehlender Mehrwert 403. Nur ein bestätigter hit
mit derselben öffentlichen Release-ID und derselben öffentlichen
Pointer-Version liefert die vorhandene
FeatureCollection mit BEV-Attribution, Quellen-/Lizenzlink, Quellenstand,
Änderungshinweis und Vermessungsgrenze. Unsichere Zustände erhalten 503;
Erfolg und Fehler bleiben private, no-store. Vorhandene Reports werden nur
gelesen; diese Prüfung erzeugt weder neue Reports noch weitere
personenbezogene Produktionsschreibvorgänge.
Quarantänisierte Quellparzellen (ungültige BEV-Geometrie) werden nie
ausgegeben; bekannt ist nur ihre Hülle (Bounding Box), die mehrere
Quadratkilometer überdecken kann. Seit 05.10.2026 (#655) entscheiden Karte,
PDF-Outline und DXF gleich und sperren die Parzelle nur am betroffenen Punkt
statt pauschal für den ganzen Ausschnitt: Liegt der Adresspunkt in genau einer
gültigen Parzelle, bleibt es ein hit, auch wenn eine Hülle Punkt und Ausschnitt
überdeckt; metadata.quarantinedInWindow nennt die Zahl der Hüllen im
Ausschnitt. Nur wenn der Punkt in keiner gültigen Parzelle oder auf einer
Grenze liegt und eine Hülle ihn überdeckt, ist das Ergebnis quality_rejected
(503 source_quality_rejected); schneidet eine Hülle nur den Ausschnitt,
sperrt sie nicht. Einzelheiten:
Vertrag, Abschnitt „Quarantäne“.
Die Report-PDF verwendet ausschließlich pdf_parcel_outline. Nur ein
bestätigter hit zeichnet die Parzellenoutline (auch bei einer Quarantänehülle,
die nur den Ausschnitt schneidet, siehe oben); bei no_hit,
outside_coverage, ambiguous_boundary, quality_rejected,
source_not_released oder failed bleibt sie null. Rechte, Freshness,
Quality, Pilot-Coverage, Quarantäne und erfolgreicher Importlauf werden in
einem einzigen atomaren SQL-Read geprüft; es gibt keinen Legacy- oder
WMS-Geometrie-Fallback und keine Eigentümerdaten.
parcel_dxf ist im qualifizierbaren Rechtevertrag enthalten, code-seitig
release_bound und seit dem Produktions- und Rollback-Readback vom 26. August 2026 kontrolliert aktiviert. DXF_EXPORT_ENABLED=true öffnet die
Route nur gemeinsam mit dem qualifizierten Export-Release; der jederzeitige
Rollbackzustand false liefert 404 und entfernt den Pfad aus dem
öffentlichen OpenAPI-Schema. Der DXF-Aufruf verwendet denselben gespeicherten
Mehrwertreport, dieselbe Koordinate und dieselbe Public-/Export-Parität und
gibt nur vollständige, unveränderte Originalpolygone in voller
Stützpunktdichte samt BEV-Attribution aus. Die
schriftliche BEV-Klärung in
#77 ist erfüllt;
#412 ist mit positivem,
negativem, Parser- und Rollback-Readback abgeschlossen. Der Export-Pointer ist
produktiv aktiviert; GeoJSON-/PDF-/DXF-Consumer sind an denselben Release
gebunden. Der
öffentliche BEV-Exportvertrag beschreibt die
vollständigen Rechte- und Aktivierungsgrenzen.
5. `widmung` — Flächenwidmung als Ampel
Quelle: generalisierte Widmungsumhüllenden des Landes Niederösterreich,
release-gebundener Snapshot (widmung-noe, Migration 0058). Die Punktabfrage
nutzt ST_Covers und verlangt einen vollständig in einer Kategorie liegenden
5-m-Kreis. Ein qualifizierter Nullbefund oder Grenzfall bleibt endgültig.
Nur wenn der Snapshot nicht antworten kann, darf der separat freigegebene
WMS-GetFeatureInfo-Pfad (widmung-noe-wms, RRU_WI_HUELLE) einspringen.
Kein wöchentlicher Live-Abgleich und keine exakte Parzellenwidmung.
| Feld | Typ | Pflicht | Herkunft | Beschreibung |
|---|---|---|---|---|
| `widmung.klasse` | enum `bauland`|`gruenland`|`industrie`|`sonder`|`gemischt`|`unbekannt` | ja | engine | Aggregierte Hauptklasse. |
| `widmung.detail` | string | null | ja | external | Original-Widmungstext NÖGIS oder null. |
| `widmung.ampel` | enum `gruen`|`gelb`|`rot` | ja | engine | `gruen`=Bauland baureif, `gelb`=eingeschränkt/aufschließungsbedürftig, `rot`=Grünland/Bauverbot. |
| `widmung.ampel_begruendung` | string | ja | engine | Regelbasierter Kurztext (kein LLM), z. B. „Bauland-Wohngebiet, baureif". |
| `widmung.rechtsverbindlich` | boolean | ja | static | Immer `false` — nur Indikation, keine rechtsverbindliche Auskunft (DATA-SOURCES §NÖGIS). |
| `widmung.quelle_stand` | ISO-8601 date | null | ja | engine | Stand der Widmung oder null, wenn kein belastbarer Stand vorliegt. |
| `widmung.source` | enum `dkm_mirror`|`wms` | optional | engine | Herkunft: dkm_mirror = release-gebundener Snapshot der generalisierten Widmungsumhüllenden in PostGIS (historischer Feldwert, keine DKM-Parzellenwidmung); wms = separat freigegebener Live-Fallback über RRU_WI_HUELLE, nur wenn der Snapshot nicht antworten kann, nicht bei qualifiziertem Nullbefund/Grenzfall. |
6. `risiko` — Naturgefahren, Altlasten, Klima
| Feld | Typ | Pflicht | Herkunft | Beschreibung |
|---|---|---|---|---|
| `risiko.hochwasser.stufe` | enum `keine`|`niedrig`|`mittel`|`hoch`|`unbekannt` | ja | engine | Pfad B (NÖGIS) implementiert (lib/geo/hochwasser.ts): Stufe aus der häufigsten Abflusszone — HQ30/HQ100 → hoch, HQ300 → niedrig, keine → keine. (HORA-Pfad A bleibt Lizenz-blockiert.) |
| `risiko.hochwasser.hq_zone` | enum `HQ30`|`HQ100`|`HQ300`|null | ja | engine | Häufigste (ungünstigste) Abflusszone, in der der Punkt liegt (NÖ-OGD HYD_HW30/100/300ETC, PostGIS-Mirror oder WMS-GFI). null = keine kartierte Zone. |
| `risiko.hochwasser.jaehrlichkeiten` | number[] | ja | engine | Alle am Punkt vorhandenen Jährlichkeiten, aufsteigend (z. B. [30,100,300]). Der Quellwert 200 aus HYD_HW300ETC bleibt als 200 erhalten, klassifiziert aber nominal als HQ300. |
| `risiko.hochwasser.ampel` | enum `gruen`|`gelb`|`rot`|`unbekannt` | ja | engine | HQ30/HQ100 → rot; HQ300 → gelb; keine Zone → grün. |
| `risiko.hochwasser.hinweis` | string | ja | static | Wortgleich mit `disclaimer.bausteine.hochwasser_indikator`. |
| `risiko.hochwasser.source` | enum `mirror`|`wms` | optional | engine | Tatsächlicher Datenweg. Optional für rückwärtskompatible, bereits gespeicherte Reports. Das Karten-Overlay bleibt unabhängig davon WMS. |
| `risiko.hochwasser.quelle_stand` | ISO-8601 date | ja | engine | Mirror: amtlicher LASTUPDATE-Stand des aktiven Snapshots. WMS: Abrufdatum, falls der Punktdienst keinen belastbaren Stand liefert. |
| `risiko.altlasten.intersect` | boolean | ja | engine | true, wenn ST_Covers für den nach EPSG:31287 transformierten Adresspunkt und eine veröffentlichte Altlast-Geometrie gilt. Polygonkanten zählen als Treffer. Das Feld behauptet keinen Parzellen-Intersect. |
| `risiko.altlasten.nearby_m` | number | null | ja | engine | Metrischer Abstand in der nativen Projektion EPSG:31287 zur nächsten veröffentlichten Altlast innerhalb 1 km; null, wenn im Radius keine liegt. |
| `risiko.altlasten.referenz` | string | null | ja | engine | Rückwärtskompatible quellenspezifische Referenz. Bei neuen WFS-Reports identisch mit ubaid; bei einem No-Hit null. |
| `risiko.altlasten.ubaid` | string | null | optional | engine | Kanonische eindeutige Identität der getroffenen beziehungsweise nächsten WFS-Fläche. ALTNR ist nicht eindeutig und wird nicht als Identität verwendet. Optional nur für die Lesbarkeit historisch gespeicherter Reports. |
| `risiko.altlasten.hinweis` | string | ja | static | Fix, beginnt mit „Nicht-Listung garantiert keine Schadstofffreiheit." und trägt seit #317 den Rechtsstand: seit 1.1.2025 keine Verdachtsflächen (ALSAG-Novelle BGBl. I Nr. 30/2024); Erstabschätzungs-Flächen des GIS Altlasten (§ 18 Abs. 4 ALSAG) mangels offener Lizenz nicht enthalten (DATA-SOURCES §ALSAG, R-03). |
| `risiko.altlasten.quelle_stand` | ISO-8601 date | null | ja | engine | Historisches Kompatibilitätsfeld: alte INSPIRE-Reports behalten ihren damaligen Fachstand. Neue WFS-Reports setzen null, weil der Provider keinen fachlichen Stichtag liefert. Der technische Abruf darf hier nicht als Datenstand erscheinen. |
| `risiko.altlasten.source_release_id` | UUID | bei neuem WFS-Report | engine | Exakter aktiver, qualifizierter Release, der Treffer, Nähe oder belastbaren No-Hit gemeinsam bindet. |
| `risiko.altlasten.retrieved_at` | ISO-8601 timestamp | bei neuem WFS-Report | engine | Technischer WFS-Abrufzeitpunkt des aktiven Releases; ausdrücklich kein fachlicher Datenstand. |
| `risiko.altlasten.source_vintage` | null | bei neuem WFS-Report | engine | Immer null, solange der Umweltbundesamt-WFS keinen fachlichen Stichtag ausweist. |
| `risiko.altlasten.hist_nutzung` | object | null | optional | engine | Visueller Indikator-Sub-Block (Issue #135). Keine algorithmische Klassifikation, kein LLM-Bilderverstehen — der Frontend-Layer zeigt nur die Karten. |
| `risiko.altlasten.hist_nutzung.zeitstaende[]` | array | ja | engine | Liste der historischen Zeitstände. Wien-Pilot: 3 (Luftbildplan 1938 / 1956 / Orthofoto heute, alle WMTS `mapsneu.wien.gv.at`). |
| `risiko.altlasten.hist_nutzung.zeitstaende[].slug` | enum `lb1938`|`lb1956`|`heute` | ja | engine | Stabile ID des Zeitstands. |
| `risiko.altlasten.hist_nutzung.zeitstaende[].jahr` | int | ja | engine | Aufnahmejahr. |
| `risiko.altlasten.hist_nutzung.zeitstaende[].quelle` | string | ja | engine | Klartext-Quelle (z. B. „data.wien.gv.at — Luftbildplan 1938"). |
| `risiko.altlasten.hist_nutzung.zeitstaende[].verfuegbar` | boolean | ja | engine | `true` innerhalb Wien-BBox, sonst `false`. |
| `risiko.altlasten.hist_nutzung.zeitstaende[].layer_ref` | string | null | ja | engine | MapLibre-Raster-Tile-URL (`{z}/{y}/{x}`); `null` wenn `verfuegbar=false`. |
| `risiko.altlasten.hist_nutzung.zeitstaende[].attribution` | string | ja | engine | Kurzattribution für Karten-Footer (CC BY 4.0). |
| `risiko.altlasten.hist_nutzung.hinweis` | string | ja | static | Fix: „Historische Karten und Luftbilder werden als visueller Indikator angezeigt. Keine Schadstoffaussage, keine algorithmische Bewertung. Bei Verdachtsmomenten ist eine fachliche Bodenuntersuchung erforderlich." |
| `risiko.altlasten.hist_nutzung.coverage_hinweis` | string | null | ja | engine | Innerhalb Coverage `null`, außerhalb Pilot-Beschränkungs-Hinweis. |
| `risiko.radon` | object | null | optional | engine | Radon-Indikator-Block (Issue #132). `null` außerhalb Österreichs oder bei API-Ausfall (→ `degraded.radon`). Gemeinde-Ebene, keine Messung. |
| `risiko.radon.potenzialklasse` | 1 | 2 | 3 | null | ja | engine | ÖNRAP-Klassifikation (1=niedrig, 2=mittel, 3=hoch). |
| `risiko.radon.rnv_status` | enum `kein_schutz`|`vorsorge`|`schutz`|`unbekannt` | ja | engine | RnV-Kategorie nach BGBl. II Nr. 470/2020 (rechtlich verbindlich). |
| `risiko.radon.rnv_gebiet_raw` | string | null | ja | engine | Roh-Text aus der Quelle (audit-tauglich). |
| `risiko.radon.gemeinde` | string | null | ja | engine | Gemeinde-Name. |
| `risiko.radon.bezirk` | string | null | ja | engine | Politischer Bezirk. |
| `risiko.radon.gem_nr` | number | null | ja | engine | 5-stellige Gemeindekennzahl (Statistik Austria). |
| `risiko.radon.ampel` | enum `gruen`|`gelb`|`rot`|`unbekannt` | ja | engine | Engine-Mapping: Klasse 3 ODER Schutzgebiet → rot; Klasse 2 ODER Vorsorgegebiet → gelb; sonst grün. |
| `risiko.radon.hinweis` | string | ja | static | Wortgleich mit `disclaimer.bausteine.radon_indikator_nicht_messung`. |
| `risiko.radon.quelle_stand` | ISO-8601 date | ja | engine | Abrufdatum (Live-API gegen LFRZ). |
| `solar` | object | null | optional | engine | Standortgenaues Solarpotenzial (ADR-0037 (nicht im öffentlichen Doku-Portal verfügbar)). `null` bei PVGIS-Ausfall → keine Ersatzkennzahlen. Die Abfrage hat ein Gesamtbudget von 10 s; bei Netzwerkfehler oder HTTP 5xx folgt genau ein Wiederholversuch nach 300 ms (nur, wenn dann noch mindestens 2 s Budget bleiben), nicht bei Timeout, 429, anderen 4xx oder ungültiger Antwort (lib/geo/solar.ts, #661). |
| `solar.spez_ertrag_kwh_pro_kwp` | number | ja | external | PVGIS spez. Jahresertrag [kWh/kWp/Jahr] bei optimaler Ausrichtung (AC); keine Gesamtenergie. |
| `solar.einstrahlung_kwh_m2` / `solar.optimale_neigung_grad` / `solar.optimaler_azimut_grad` | number | ja | external | Einstrahlung [kWh/m²] + von PVGIS bestimmte optimale Neigung/Azimut (0 = Süd). |
| `solar.quelle` / `solar.quelle_stand` | string | ja | external | Strahlungs-DB (z. B. `PVGIS-SARAH3`) + Abrufdatum. |
| `solar.google` | object | null | optional (premium/planer) | external | Dachgenaue Google-Anreicherung (Building Insights). DC-Werte, Live-Overlay — vor Persistenz entfernt (stripGoogleSolar, ToS R-07); auf Permalinks abwesend. null = keine Coverage, kein Key oder nicht berechtigtes Tier. |
| `solar.google.max_kwp` / `.max_panels` / `.roof_segments` | number | wenn `google` | external | Größte berechnete Belegung: kWp, Panel-Anzahl, Dachflächen. |
| `solar.google.max_yearly_kwh_dc` / `.implied_spez_kwh_pro_kwp_dc` | number | wenn `google` | external | Jahresertrag (DC) + abgeleiteter spez. Ertrag (DC, ≠ PVGIS-AC). |
| `solar.google.imagery_quality` / `.imagery_date` / `.region_code` | string | wenn `google` | external | Luftbild-Qualität (HIGH/MEDIUM/BASE) + Datum + ISO-Regioncode. |
| `erreichbarkeit` | object | null | optional | engine | Erreichbarkeits-Score (#140, ADR-0036). Pro POI-Klasse der nächste kuratierte POI + Distanz + Fahrzeit. Env-gated: ohne `OSRM_BASE_URL` Luftlinien-Schätzung (`quelle: "luftlinie"`, Phase 2.0); mit gesetztem Backend echte Auto-Fahrzeit via OSRM `/table` (`quelle: "osrm"`, Phase 2.5), fail-open zurück auf Luftlinie bei Fehler/Timeout. |
| `erreichbarkeit.eintraege[]` | array | ja | engine | 4 Einträge (autobahn, bahnhof, spital, schule), je mit Pfad zum nächsten kuratierten POI. |
| `erreichbarkeit.eintraege[].klasse` | enum `autobahn`|`bahnhof`|`spital`|`schule` | ja | engine | POI-Kategorie. |
| `erreichbarkeit.eintraege[].label` | string | ja | static | Anzeige-Label (DE). |
| `erreichbarkeit.eintraege[].poi_id` | string | null | ja | engine | Stabile POI-ID aus `lib/maps/poi-at.ts` (nicht im öffentlichen Doku-Portal verfügbar). |
| `erreichbarkeit.eintraege[].poi_name` | string | null | ja | engine | Klartext-Name des nächsten POI. |
| `erreichbarkeit.eintraege[].distanz_km` | number | null | ja | engine | Distanz in km (1 Nachkommastelle). Bei `quelle: "osrm"` Straßendistanz, sonst Luftlinie. |
| `erreichbarkeit.eintraege[].fahrzeit_min` | number | null | ja | engine | Fahrzeit in Minuten. Bei `quelle: "osrm"` echte Routing-Fahrzeit, sonst Schätzung (Luftlinie × 1.35 / 50 km/h). |
| `erreichbarkeit.eintraege[].quelle` | enum `luftlinie`|`osrm`|`ors` | ja | engine | Methode: `luftlinie` ohne Routing-Backend (Default), `osrm` mit gesetztem `OSRM_BASE_URL`. Pro Klasse kann einzeln auf `luftlinie` zurückfallen, wenn OSRM den POI nicht erreicht. |
| `erreichbarkeit.hinweis` | string | ja | static | Pflicht-Hinweis: „Indikator-Schätzung … keine echte Routing-Fahrzeit". |
| `erreichbarkeit.coverage_hinweis` | string | ja | static | Hinweis auf kuratiertes POI-Set (Pilot-Phase). |
| `breitband` | object | null | optional | engine | Breitband-Versorgungs-Indikator (RTR/Breitbandatlas, Festnetz). `null` außerhalb versorgter Zellen/Österreichs oder bei API-Ausfall (→ `degraded.breitband`). Summe aller Infrastruktur-Anbieter, keine Anschlusszusage. Live via WMTS-GetFeatureInfo (REST-API per 30.06.2025 eingestellt). |
| `breitband.max_download_mbit` | number | null | ja | engine | Max. Download-Rate der 100×100-m-Rasterzelle (Mbit/s; `0` = „< 1"). |
| `breitband.klasse` | string | null | ja | engine | Legendenklasse des Breitbandatlas, abgeleitet aus max_download_mbit („< 30“, „≥ 30 bis < 100“, „≥ 100 bis < 300“, „≥ 300 bis < 1000 Mbit/s“, „≥ 1 Gbit/s“); null ohne Wert. Nicht der Klassentext der Quelle — der widerspricht dem Wert (Messung 04.10.2026). Gespeicherte Reports leiten die Klasse beim Lesen neu ab; Web und PDF nennen darunter den Wert. |
| `breitband.ampel` | enum `gruen`|`gelb`|`rot`|`unbekannt` | ja | engine | Engine-Mapping: ≥ 100 → grün; ≥ 30 → gelb; < 30 → rot; kein Wert → unbekannt. |
| `breitband.hinweis` | string | ja | static | Wortgleich mit `disclaimer.bausteine.breitband_indikator`. |
| `breitband.quelle_stand` | ISO-8601 date | ja | engine | Abrufdatum (Live WMTS-GFI gegen `breitbandatlas.gv.at`). |
| `breitband.foerderausbau` | object | null | optional | engine | #322 (M4): Geförderter Ausbau (BBA2020/BBA2030) in derselben Zelle als Zukunftssignal — gesetzt, sobald ein Förderprojekt trifft, sonst fehlend/null. Verändert ampel/max_download_mbit nicht (Ist-Schwellen 100/30 bleiben). Eigener Layer Gefoerderter-Ausbau-Rasterzellen (WMTS-GFI), fail-soft. R-03: gefördert ≠ Anschluss-/Termin-Zusage. |
| `breitband.foerderausbau.programm` | string | wenn gesetzt | engine | Programm + Netz-Modell, roh (z. B. „BBA2030: OpenNet"). |
| `breitband.foerderausbau.technik` | string | null | wenn gesetzt | engine | Ausbautechnik (z. B. „FTTP"). |
| `breitband.foerderausbau.projektende` | string | null | wenn gesetzt | engine | Geplantes Projektende inkl. Statustext (z. B. „…2027 (Zelle gebaut)") — Plan, keine Zusage. |
| `breitband.foerderausbau.foerdernehmerin` | string | null | wenn gesetzt | engine | Bauendes/förderndes Unternehmen. |
| `breitband.foerderausbau.is_connect` | boolean | wenn gesetzt | engine | true = Connect (punktuelle Anschlussförderung, nicht zwingend Bau), sonst flächiger Rasterausbau. |
| `breitband.foerderausbau.meldung` | string | wenn gesetzt | engine | Anzeigefertiger R-03-Kopfsatz (Web+PDF identisch); zwei Varianten nach is_connect. |
| `laerm` | object | null | optional | engine | Umgebungslärm-Indikator (strategische EU-Lärmkarten, Kartierungsrunde 2022, END 2002/49/EG). Der aktuelle Vertrag umfasst vier Quelltypen: Straße, Schiene, Flug sowie Industrie/IPPC; IPPC ist nur in den ausgewiesenen Ballungsräumen kartiert. Kompatibilitätsregel: Nur wenn alle acht Flug-/IPPC-Felder als unteilbares Acht-Felder-Tupel vorhanden sind, darf ein Consumer den Vier-Quellen-Scope behaupten. Fehlt auch nur eines, ist der gespeicherte Altbericht fail-closed ausschließlich als Straße/Schiene zu kennzeichnen; Flug/IPPC dürfen weder als geprüft noch als „ohne Treffer“ erscheinen. `null` nur bei nicht verfügbarem/ungeprüftem Live-Pfad (→ `degraded.laerm`); „keine Zone" ist ein gültiges Ergebnis innerhalb des ausgewiesenen Scopes (Ampel grün). Berechnete Mittelungspegel in 4 m Höhe, keine Messung und kein 10-m-Raster. Der WMS-Pfad ist an einen qualifizierten Source-Release mit Live-Checks für Report, Karte und Legende gebunden. Quelle: www.laerminfo.at, Lizenz: CC BY 4.0, Attribution exakt „Datenquelle: www.laerminfo.at“. |
| `laerm.strasse_lden_db` | number | null | ja | engine | Untergrenze des Straßen-Lden-Bandes in dB (z. B. 65 für 65–69); `null` = keine Zone. |
| `laerm.strasse_lden_klasse` | string | null | ja | engine | Roh-Klassenbezeichner der Quelle (z. B. „Lden6569"). |
| `laerm.strasse_lnight_db` | number | null | ja | engine | Untergrenze des Straßen-Lnight-Bandes (Nacht). |
| `laerm.strasse_lnight_klasse` | string | null | ja | engine | Roh-Klassenbezeichner Straße Nacht. |
| `laerm.schiene_lden_db` | number | null | ja | engine | Untergrenze des Schienen-Lden-Bandes. |
| `laerm.schiene_lden_klasse` | string | null | ja | engine | Roh-Klassenbezeichner Schiene Tag-Abend-Nacht. |
| `laerm.schiene_lnight_db` | number | null | ja | engine | Untergrenze des Schienen-Lnight-Bandes (Nacht). |
| `laerm.schiene_lnight_klasse` | string | null | ja | engine | Roh-Klassenbezeichner Schiene Nacht. |
| `laerm.flug_lden_db` | number | null | optional (Altberichte) | engine | Untergrenze des Flug-Lden-Bandes; `null` = keine kartierte Fluglärm-Zone. |
| `laerm.flug_lden_klasse` | string | null | optional (Altberichte) | engine | Roh-Klassenbezeichner Flug Tag-Abend-Nacht. |
| `laerm.flug_lnight_db` | number | null | optional (Altberichte) | engine | Untergrenze des Flug-Lnight-Bandes (Nacht). |
| `laerm.flug_lnight_klasse` | string | null | optional (Altberichte) | engine | Roh-Klassenbezeichner Flug Nacht. |
| `laerm.industrie_lden_db` | number | null | optional (Altberichte) | engine | Untergrenze des Industrie-/IPPC-Lden-Bandes; IPPC-Zonen sind nur in ausgewiesenen Ballungsräumen verfügbar. |
| `laerm.industrie_lden_klasse` | string | null | optional (Altberichte) | engine | Roh-Klassenbezeichner Industrie/IPPC Tag-Abend-Nacht. |
| `laerm.industrie_lnight_db` | number | null | optional (Altberichte) | engine | Untergrenze des Industrie-/IPPC-Lnight-Bandes (Nacht). |
| `laerm.industrie_lnight_klasse` | string | null | optional (Altberichte) | engine | Roh-Klassenbezeichner Industrie/IPPC Nacht. |
| `laerm.max_lden_db` | number | null | ja | engine | Höchste Lden-Untergrenze über den beim Erstellen geprüften Scope (aktuell Straße, Schiene, Flug und Industrie/IPPC; in Altberichten ausschließlich Straße/Schiene); `null` = keine Zone innerhalb dieses Scopes. |
| `laerm.ampel` | enum `gruen`|`gelb`|`rot`|`unbekannt` | ja | engine | Engine-Mapping: Lden ≥ 70 ODER Lnight ≥ 60 → rot; sonst kartierte Zone → gelb; keine Zone → grün. |
| `laerm.hinweis` | string | ja | static | Neue Reports sind wortgleich mit `disclaimer.bausteine.laerm_indikator` und enthalten verbindlich: „Die Ampel ist ein Screening auf Basis der EU-Lärmkarten und ersetzt kein schalltechnisches Gutachten; Ansprüche auf Schallschutzmaßnahmen richten sich nach eigenen, teils niedrigeren Grenzwerten.“ Gespeicherte Altberichte können einen historischen Freitext enthalten (unter anderem die überholte „10-m-Raster“-Behauptung). Sichtbare Consumer dürfen diesen Text nicht ungeprüft wiedergeben: Sie normalisieren ihn auf einen aktuellen, Straße/Schiene-spezifischen Altbericht-Hinweis mit genau diesem R-03-Satz; Messwerte und Ampel des Snapshots bleiben unverändert. |
| `laerm.quelle_stand` | ISO-8601 date | ja | engine | Abrufdatum des release-gebundenen Live-WMS-GFI gegen `inspire.lfrz.gv.at`; ausdrücklich nicht Datenjahr oder Kartierungsstand (der ist 2022). |
| `oev` | object | null | optional | engine | ÖV-Güteklassen-Indikator (ÖROK/BMIMI-Modell, #101 Phase 2). Nur PostGIS-Mirror (Pilot-bbox, jährliche Datei-Lieferung über AustriaTech — kein amtlicher WMS/WFS): außerhalb der Spiegelung `null` (→ `degraded.oev`, reason `ausserhalb_pilot_spiegelung`). „Keine Güteklasse" am Punkt ist ein gültiges Ergebnis (Ampel rot). |
| `oev.gueteklasse` | string | null | ja | engine | Güteklasse `A` (beste) … `G`; `null` = keine Klasse am Punkt. Klassen-Polygone überlappen — die beste Klasse gewinnt. |
| `oev.ampel` | enum `gruen`|`gelb`|`rot` | ja | engine | Deterministisch (`classifyOev`): A–C → grün, D–G → gelb, keine Klasse → rot. Schwellen am 16.07.2026 vom Cofounder bestätigt (M11); offene Rot-Differenzierung bei Bedarfsverkehr in #341. |
| `oev.ampel_begruendung` | string | ja | engine | Klartext-Begründung der Ampel (deterministisch, kein LLM). |
| `oev.hinweis` | string | ja | static | Wortgleich mit `disclaimer.bausteine.oev_indikator`. |
| `oev.quelle_stand` | ISO-8601 date | null | ja | engine | Fahrplan-Referenzdatum der Lieferung (aus der Spiegelung; nie das Report-Datum). |
| `oev.stichtag_typ` | string | null | optional | engine | Typ des Fahrplan-Stichtags der Pilotlieferung (z. B. „Werktag in den Herbstferien“ für 29.10.2025); null bei unbekanntem Stand. Fehlt in Berichten vor dem 04.10.2026. |
| `oev.standort_check_stand` | ISO-8601 date | null | optional | engine | Stichtag der österreichweiten Lieferung, die der kostenlose Standort-Check liest (nur Vergleich, keine Klasse daraus); null ohne lesbaren aktiven Release. Fehlt in Berichten vor dem 04.10.2026. |
| `oev.stichtag_hinweis` | string | optional | engine | Sichtbarer Hinweis zu Stichtag und möglicher Abweichung zum Standort-Check, wortgleich in Web und PDF (oevStichtagHinweis). Fehlt in Berichten vor dem 04.10.2026; deren Darstellung bleibt unverändert. |
| `oev.source` | const `mirror` | ja | engine | Herkunfts-Transparenz (analog `parcel.source`). |
| `schutzgebiete` | object | null | optional | engine | Schutzgebiete-Indikator (NÖ-OGD Natur- + Wasserschutz). null außerhalb NÖ oder bei API-Ausfall (→ `degraded.schutzgebiete`). „Kein Treffer" ist ein gültiges Ergebnis (grün). Keine Rechtsauskunft. Live via WMS-GetFeatureInfo. |
| `schutzgebiete.treffer[]` | array | ja | engine | Alle zutreffenden Designationen (überlappende möglich). |
| `schutzgebiete.treffer[].kategorie` | string | ja | engine | Stabile ID (z. B. naturschutzgebiet, europaschutz_ffh, landschaftsschutzgebiet, nationalpark, naturpark, wasserschutzgebiet, wasserschongebiet). |
| `schutzgebiete.treffer[].label` | string | ja | engine | Anzeige-Label (DE). |
| `schutzgebiete.treffer[].name` | string | null | ja | engine | Gebietsname aus der Quelle. |
| `schutzgebiete.treffer[].schwere` | enum `gelb`|`rot` | ja | engine | rot = streng (Naturschutz-/Europaschutz-/Nationalpark-/Wasserschutzgebiet); gelb = milder (Landschaftsschutz/Naturpark/Wasserschongebiet). |
| `schutzgebiete.ampel` | enum `gruen`|`gelb`|`rot`|`unbekannt` | ja | engine | Ungünstigste Schwere über alle Treffer; grün wenn keiner. |
| `schutzgebiete.hinweis` | string | ja | static | Wortgleich mit `disclaimer.bausteine.schutzgebiete_indikator`. |
| `schutzgebiete.quelle_stand` | ISO-8601 date | ja | engine | Abrufdatum (Live WMS-GFI gegen sdi.noe.gv.at). |
| `denkmal` | object | null | optional | engine | Denkmalschutz (BDA-Denkmalverzeichnis §3 DMSG, NÖ). null, wenn keine Parzelle (KG+GstNr) aufgelöst wurde. Parzellenscharfer Match gegen vorgenerierte Liste (kein Netz/DB). Keine Rechtsauskunft; Ensembles/Umgebungsschutz nicht erfasst. |
| `denkmal.gelistet` | boolean | ja | engine | Liegt das Grundstück (KG-Code + GstNr) direkt im Denkmalverzeichnis? |
| `denkmal.objekte[]` | array | ja | engine | Auf der Parzelle gelistete Objekte (titel, schutzart). |
| `denkmal.gemeinde` | string | null | ja | engine | Gemeinde (aus KG-Code abgeleitet). |
| `denkmal.gemeinde_count` | number | null | ja | engine | Anzahl denkmalgeschützter Objekte in der Gemeinde (Kontext). |
| `denkmal.ampel` | enum `gruen`|`gelb`|`rot`|`unbekannt` | ja | engine | gelistet → gelb; sonst grün. M8 (16.07.2026): Denkmalschutz ist ein Bewilligungsvorbehalt (Veränderung/Restaurierung/Abbruch BDA-pflichtig), kein Standort-Risiko — „Auflagen, aber Nutzung möglich". Für ein Reaktivierungs-Tool sind Denkmäler oft Zielobjekte; deshalb nie rot. |
| `denkmal.nachbarn[]` | array | ja | engine | Denkmalgeschützte Nachbarparzellen im 14-m-Umkreis (Rand-zu-Rand; Fachlicher Entscheid 11.08.2026, #326: Nachbarbegriff baubehördlicher Verfahren/Parteistellung). Je Eintrag kg, gnr, distanz_m (0 = direkt angrenzend), objekte[]. Release-gebundener Mirror-Lookup (lookupParcelNeighborsMirror, Capability point_lookup) + In-Memory-BDA-Match. Rein informativ, keine Wirkung auf ampel (M8). Nur aussagekräftig bei nachbarn_geprueft=true. |
| `denkmal.nachbarn_geprueft` | boolean | ja | engine | false = Prüfung nicht möglich (Parzelle außerhalb des release-gebundenen Pilot-Spiegels oder Lookup nicht verfügbar) — dann ist nachbarn: [] keine Aussage und wird als „nicht geprüft“ angezeigt, nie als „keine Denkmäler in der Nachbarschaft“. |
| `denkmal.hinweis` | string | ja | static | Wortgleich mit `disclaimer.bausteine.denkmal_indikator`. |
| `denkmal.quelle_stand` | ISO-8601 date | ja | engine | Abrufdatum (Lookup gegen die committete BDA-Liste). |
| `wlv` | object | null | optional | engine | Gefahrenzonenplan Wildbach & Lawine (WLV/BML, LFRZ WMS-GFI). null bei API-Ausfall (→ `degraded.wlv`); „keine Zone" ist ein gültiges Ergebnis (grün). Nur Gemeinden mit Plan (alpiner Raum). Keine rechtsverbindliche Zonen-Auskunft. |
| `wlv.zonen[]` | string[] | ja | engine | Distinkte Objektart-Werte am Punkt (z. B. „Rote Zone Wildbach", „Summenzone - Gelbe Zone Wildbach", „Raumrelevanter Bereich"). |
| `wlv.ampel` | enum `gruen`|`gelb`|`rot` | ja | engine | Rote Zone → rot; echte erhobene Zone (Gelbe/Summen-/Blaue/Braune) → gelb; keine Zone oder ausschließlich Raumrelevanter Bereich → grün. M10 (16.07.2026): Der Raumrelevante Bereich ist keine erhobene Gefahrenzone, sondern ein Planungs-Hinweis → keine Warnfarbe. |
| `wlv.raumrelevanter_bereich` | boolean | ja | engine | M10: Punkt liegt in einem Raumrelevanten Bereich der WLV. Treibt in UI/PDF einen eigenen ℹ️-Hinweis statt einer Warnfarbe; unabhängig von `ampel` (liegt zusätzlich eine echte Zone vor, führt diese die Ampel, der Hinweis bleibt Kontext). |
| `wlv.gemeinde` | string | null | ja | engine | Hauptgemeindename des Gefahrenzonenplans. |
| `wlv.revisionsjahr` | number | null | ja | engine | Revisionsjahr des Plans (Daten-Vintage). |
| `wlv.hinweis` | string | ja | static | Wortgleich mit `disclaimer.bausteine.wlv_indikator`. |
| `wlv.quelle_stand` | ISO-8601 date | ja | engine | Abrufdatum (Live WMS-GFI gegen inspire.lfrz.gv.at/000901). |
| `pendeln` | object | null | optional | engine | Pendler-/Arbeitsplatzindikator der GEMEINDE (#363; Statistik Austria, Abgestimmte Erwerbsstatistik + Arbeitsstättenzählung, Neon-Mirror stat_aest_gemeinde). Neutraler Info-Block ohne Ampel (Klima-Präzedenz): reine Statistik, kein Steckbrief-/Bilanz-Eintrag. null ohne Gemeindebezug (gem_nr aus dem Radon-Layer fehlt → `degraded.pendeln`, reason `keine_gemeinde`) oder wenn die Gemeinde nicht im Datenstand liegt (reason `gemeinde_nicht_im_datenstand`; Wien ist nach Gemeindebezirken codiert). |
| `pendeln.gemeinde` / `pendeln.gem_nr` | string / number | ja | engine | Gemeindename + 5-stellige Statistik-Austria-GKZ. |
| `pendeln.jahr` | int | ja | engine | Berichtsjahr der AEST (Stichtag 31.10.; ~1,5 Jahre Publikations-Lag). |
| `pendeln.auspendler_pct` | number | null | ja | engine | Auspendler:innenanteil an den Erwerbstätigen am Wohnort (%). |
| `pendeln.erwerbstaetigenquote_pct` / `pendeln.arbeitslosenquote_pct` | number | null | ja | engine | Erwerbstätigenquote 15–64 / Arbeitslosenquote 15+ (%). |
| `pendeln.arbeitsstaetten` / `pendeln.besch_am_arbeitsort` / `pendeln.unternehmen` / `pendeln.bev_gemeinde` | number | null | ja | engine | Arbeitsort-Kennzahlen der Gemeinde (Arbeitsplatzdichte-Kontext). |
| `pendeln.hinweis` | string | ja | static | Wortgleich mit `disclaimer.bausteine.pendeln_indikator`. |
| `pendeln.source` | const `mirror` | ja | engine | Datenweg-Kennung (Neon-Mirror, kein Live-Call). |
| `pendeln.quelle_stand` | ISO-8601 date | ja | engine | Daten-Vintage des Mirrors (max(stand) der Tabelle) — nie das Abfragedatum. |
| `demografie` | object | null | optional | engine | Bevölkerungsdynamik der GEMEINDE (#364; Statistik Austria, Bevölkerungsstand zum 1.1. + Wanderungsstatistik, Neon-Mirror stat_bev_gemeinde/stat_wanderung_gemeinde). Neutraler Info-Block ohne Ampel. Eine Geburten-/Sterbebilanz je Gemeinde existiert als OGD nicht und wird bewusst nicht ausgewiesen (Discovery #364, R-03). null-/degraded-Semantik wie `pendeln`. |
| `demografie.gemeinde` / `demografie.gem_nr` | string / number | ja | engine | Gemeindename + 5-stellige Statistik-Austria-GKZ. |
| `demografie.stichtag` / `demografie.bev` | ISO-8601 date / number | ja | engine | Aktuellster Bevölkerungsstand zum 1.1. |
| `demografie.bev_vor_1j` / `demografie.bev_vor_5j` / `demografie.delta_1j` / `demografie.delta_5j_pct` | number | null | ja | engine | Vergleichsbestände + Deltas; null, wenn das Vergleichsjahr nicht im Mirror liegt (kein erfundener Trend). |
| `demografie.wanderung` | object | null | ja | engine | Jüngstes Jahr mit vollständigem Saldo: `{ jahr, binnen_saldo, aussen_saldo, gesamt_saldo }` — Zu-/Wegzüge über die Gemeindegrenze (gemeindeinterne Umzüge ausgenommen); Außenwanderung liegt als OGD erst ab 2015 vor. |
| `demografie.hinweis` | string | ja | static | Wortgleich mit `disclaimer.bausteine.demografie_indikator`. |
| `demografie.quelle_stand` | ISO-8601 date | ja | engine | Daten-Vintage des Mirrors — nie das Abfragedatum. |
| `demografie.source` | const `mirror` | ja | engine | Datenweg-Kennung (Neon-Mirror, kein Live-Call). |
| `boden` | object | null | optional | engine | BFW-Bodenkarte eBOD am Punkt (#360; Gründigkeit + Wasserverhältnisse, PostGIS-Pilot-Mirror boden, Migrationen 0031 + 0033). Beide Themen gehören zu genau einem qualifizierten, unveränderlichen Erfolgs-Snapshot und werden atomar gemeinsam veröffentlicht. Neutraler Info-Block ohne Ampel. EHRLICHKEITS-REGEL: `kartiert=false` ist eine Abdeckungslücke (die eBOD erfasst nur landwirtschaftlich genutzte Flächen — typisch bebautes Gebiet), KEIN Befund über den Boden; der Block wird dann trotzdem gerendert. `null` außerhalb der Pilot-Spiegelung (→ `degraded.boden`, reason `ausserhalb_pilot_spiegelung`). |
| `boden.kartiert` | boolean | ja | engine | Liegt am Punkt ein eBOD-Polygon (mind. ein Thema)? |
| `boden.gruendigkeit` / `boden.wasserverhaeltnisse` | string | null | ja | engine | Klartext-Klassen der BFW-Kartierung (z. B. „mittelgründig“, „trocken“). Treffen am randinklusiven Punkt mehrere unterschiedliche Klassen, enthält der String alle Werte mit dem Präfix „Mehrdeutig an Kartengrenze“; keine Klasse wird bevorzugt. |
| `boden.hinweis` | string | ja | static | Wortgleich mit `disclaimer.bausteine.boden_indikator` (Landwirtschafts-Abdeckung, historisches Erhebungsalter, kein Bodengutachten). |
| `boden.quelle_stand` | ISO-8601 date | ja | engine | Atom-Feed-Stand der eBOD-Lieferung aus dem Mirror — nie das Abfragedatum. |
| `boden.source` | const `mirror` | ja | engine | Datenweg-Kennung (PostGIS-Mirror, kein Live-Call). |
| `bahn` | object | null | optional | engine | Bahnanbindung aus dem ÖBB-GTFS-Soll-Fahrplan (#380; CC BY 4.0, österreichweiter Snapshot gtfs_halt, Migration 0036): nächste Station im 5-km-Umkreis mit der GEMESSENEN Bedienung. Neutraler Info-Block ohne Ampel — ergänzt `oev` (ÖROK-Güteklasse = Modellwert mit Ampel), ersetzt ihn nicht. ⚠ Nur Schienenverkehr: Regionalbusse und Verbundlinien sind im Feed nicht enthalten. null, wenn keine Station im Umkreis liegt (→ `degraded.bahn`, reason `keine_station_im_umkreis`). |
| `bahn.station` / `bahn.distanz_m` | string / number | ja | engine | Stationsname + Luftlinie in Metern (kein Gehweg) — wird immer mit ausgewiesen. |
| `bahn.halte_werktag` / `bahn.halte_samstag` / `bahn.halte_sonntag` | number | ja | engine | Geplante Halte an je einem Referenztag (Median-Tag des Fahrplanjahres nach aktiven Services), beide Richtungen, alle Bahnsteige der Station. Der ÖBB-Feed steuert den Betriebstag über calendar_dates.txt; eine reine calendar.txt-Summe ergäbe Werte, die an keinem Tag zutreffen (#380). |
| `bahn.spitzenstunde` / `bahn.spitzenstunde_halte` | number | null | ja | engine | Stunde (0–23) mit den meisten Werktags-Halten und deren Anzahl. |
| `bahn.top_ziele` | array | ja | engine | Häufigste Fahrtziele ab dieser Station als `{ ziel, halte }[]`; Züge, die AN dieser Station enden, sind ausgenommen. |
| `bahn.hinweis` | string | ja | static | Wortgleich mit `disclaimer.bausteine.bahn_indikator` (Bus-Lücke, Regelfahrplan, Luftlinie). |
| `bahn.quelle_stand` | ISO-8601 date | ja | engine | Fahrplanjahr-Start der Lieferung — nie das Abfragedatum. |
| `bahn.source` | const `mirror` | ja | engine | Datenweg-Kennung (Snapshot-Mirror, kein Live-Call). |
| `erdbeben` | object | null | optional | engine | Erdbebenzone nach ÖNORM B 1998-1 (#427; GeoSphere Austria, CC BY 4.0, österreichweiter Snapshot erdbeben_ort, Migration 0045, release-gebunden nach FND-03). Ampel per fachlichem Entscheid 11.08.2026 (#453): ampel + ampel_begruendung, Zone 0–1 grün · 2–3 gelb · 4 rot (classifyErdbeben); ausdrücklich keine Kostenwirkung auf die Szenarien; kein Steckbrief-/Bilanz-Eintrag (Bilanz-Einbindung nicht entschieden, Präzedenz M11). null außerhalb der Abdeckung (→ `degraded.erdbeben`, reason `ausserhalb_abdeckung`). |
| `erdbeben.zone` | number (0–4) | ja | engine | Gefahrenzone nach ÖNORM B 1998-1. Zone 0 ist ein gültiger Wert (niedrigste Stufe), kein Leerwert. |
| `erdbeben.ort` / `erdbeben.plz` / `erdbeben.distanz_m` | string / number / number | ja | engine | Der Ort, dem die Norm diese Zone zuordnet, samt Luftlinie in Metern. Steht IMMER dabei: Die ÖNORM ordnet je Ort zu, nicht je Grundstück — das Ortsnetz hat einen Median-Nachbarabstand von 3,3 km. |
| `erdbeben.ega` / `erdbeben.pga` | number | ja | engine | Referenzwerte der Quelle, bewusst uninterpretiert und mit Feldnamen ausgewiesen. Was sie fachlich genau bezeichnen, ist aus der Quelle nicht dokumentiert (offener Punkt im Rechte-Review); beobachtet gilt durchgängig PGA = EGA × √2. |
| `erdbeben.hinweis` | string | ja | static | Wortgleich mit `disclaimer.bausteine.erdbeben_indikator` (Ortsbezug, 2-km-Kartengenauigkeit, Zuständigkeit des Tragwerksplaners). |
| `erdbeben.quelle_stand` | ISO-8601 date | ja | engine | Veröffentlichungsstand des Quell-Items — nie das Abfragedatum. |
| `erdbeben.source` | const `mirror` | ja | engine | Datenweg-Kennung (Snapshot-Mirror, kein Live-Call). |
| `lcz` | object | null | optional | engine | Umgebungsstruktur nach Local Climate Zones (#454; Demuzere et al. 2022, CC BY 4.0, 5×5-Zellen-Aggregat lcz_umgebung (~500 m N-S × ~335 m O-W), Migration 0046, release-gebunden nach FND-03): modale LCZ-Klasse eines 5×5-Zellen-Fensters um die Adresse (Gradraster: rund 500 m Nord-Süd × 335 m Ost-West). Neutraler Info-Block ohne Ampel — Strukturbeschreibung der NACHBARSCHAFT (Stewart & Oke: „local“ = Nachbarschaftsskala), keine Parzellenaussage, keine Klimamessung; kein Steckbrief-/Bilanz-Eintrag. null außerhalb des Österreich-Fensters (→ `degraded.lcz`, reason `ausserhalb_abdeckung`). |
| `lcz.klasse` | number (1–17) | ja | engine | Modale LCZ-Klasse des Fensters (Stewart & Oke 2012). Web/PDF zeigen die neutral-deskriptive deutsche Beschreibung IMMER zusammen mit dem offiziellen englischen Klassennamen (LCZ_KLASSEN in labels.ts — redaktionelle Setzung, es existiert kein etabliertes Laienvokabular). |
| `lcz.anteil` / `lcz.n_valid` | number / number | ja | engine | Einhelligkeit des Fensters: Zellen der Modalklasse von maximal 25 gültigen Rasterpixeln (~100 m N-S × ~67 m O-W). Ein heterogenes Fenster (z. B. 11/25) wird sichtbar ausgewiesen, nie geglättet. |
| `lcz.konfidenz` | number (0–100) | ja | engine | Mittlere ML-Modell-Konfidenz (%) der Modal-Pixel aus dem Probability-Layer der Quelle — die Aggregations-Entscheidung (#454): Einzelpixel-Konfidenz streut 38–100 %, das Fenster-Mittel ist die ehrliche Angabe. |
| `lcz.distanz_m` | number | ja | engine | Luftlinie zum Mittelpunkt der Gitterzelle (max. ~304 m konstruktionsbedingt — die Zellen sind anisotrop). |
| `lcz.hinweis` | string | ja | static | Wortgleich mit `disclaimer.bausteine.lcz_indikator` (ehrliche Fenstergeometrie ~500 × 335 m, Nachbarschaftsaussage, ML-Herkunft, Nominaljahr 2018). |
| `lcz.quelle_stand` | ISO-8601 date | ja | engine | Nominaljahr der Karte (2018-01-01) — nie das Abrufdatum; Darstellung zeigt das Jahr. |
| `lcz.source` | const `mirror` | ja | engine | Datenweg-Kennung (Snapshot-Mirror, kein Live-Call). |
6.1 `risiko.klima` — release-gebundener SPARTACUS-v3-Snapshot
Sichtbarer Titel: „SPARTACUS v3 · Datenstand 2025 · 1-km-Raster". Die Attribution wird wortgleich als „Datenquelle: GeoSphere Austria - https://data.hub.geosphere.at" ausgegeben. Der Pflicht-Hinweis lautet: „Das 1-km-Raster ist ein regionaler Klimaindikator und weder Messung noch Prognose für eine einzelne Parzelle."
Neue Reports liefern den Klimawert ausschließlich aus dem aktiven,
qualifizierten Release geosphere-spartacus-v3 und befüllen alle unten
aufgeführten Provenienzfelder. Diese zusätzlichen Felder bleiben im
JSON-Contract optional, damit bereits gespeicherte Reports ohne v3-Provenienz
weiterhin lesbar sind. Im API-Schema gelten sie als unteilbares Alles-oder-
Nichts-Tupel: Fehlt auch nur eines, ist der Block kein gültiger v3-Report und
ein Client darf weder SPARTACUS v3 noch einen konkreten Release
nachträglich behaupten.
| Feld | Typ | Pflicht | Herkunft | Beschreibung |
|---|---|---|---|---|
| `risiko.klima.jahresmittel_temp_c` | number | ja | engine | SPARTACUS-Jahresmittel der Lufttemperatur in °C. |
| `risiko.klima.jahresniederschlag_mm` | number | ja | engine | SPARTACUS-Jahressumme des Niederschlags in mm. |
| `risiko.klima.sonnenstunden_jahr` | number | ja | engine | SPARTACUS-Jahressumme der Sonnenscheindauer in Stunden; keine Einstrahlung und keine Grundlage für eine PV-Eignungs- oder Ertragsentscheidung. |
| `risiko.klima.referenzjahr` | int | ja | engine | Abgeschlossenes Kalenderjahr des Rasterwerts. Der qualifizierte v3-Snapshot verwendet 2025; gespeicherte Alt-Reports behalten ihr ursprüngliches Jahr. |
| `risiko.klima.quelle_stand` | ISO-8601 date | ja | engine | Rückwärtskompatibler Anzeige-Stichtag. Die belastbare Snapshot-Provenienz steht zusätzlich in source_vintage und retrieved_at. |
| `risiko.klima.source_slug` | const `geosphere-spartacus-v3` | optional | engine | Registry-Schlüssel der tatsächlich gelesenen Quelle. |
| `risiko.klima.source_release_id` | UUID | optional | engine | ID des beim atomaren Lookup aktiven, qualifizierten Releases. |
| `risiko.klima.release_key` | UUID | optional | engine | Deterministischer fachlicher Release-Schlüssel. |
| `risiko.klima.dataset_version` | const `3` | optional | engine | SPARTACUS-Hauptversion; als String serialisiert. |
| `risiko.klima.transform_version` | const `spartacus-v3-snapshot-v1` | optional | engine | Version der deterministischen Artefakt-Transformation. |
| `risiko.klima.source_vintage` | string | optional | engine | Fachlicher Datenstand des qualifizierten Snapshots, aktuell 2025. |
| `risiko.klima.retrieved_at` | ISO-8601 date-time | optional | engine | Abrufzeitpunkt des unveränderlich nachgewiesenen Quellartefakts. |
| `risiko.klima.resolution_m` | const `1000` | optional | engine | Native Rasterauflösung in Metern. |
| `risiko.klima.native_crs` | const `EPSG:3416` | optional | engine | Koordinatenreferenzsystem der Rasterzentren. |
| `risiko.klima.easting_m` / `risiko.klima.northing_m` | int / int | optional | engine | Zentrum der tatsächlich verwendeten 1-km-Rasterzelle in EPSG:3416. |
| `risiko.klima.attribution` | string | optional | engine | Wortgleiche sichtbare Attribution: „Datenquelle: GeoSphere Austria - https://data.hub.geosphere.at". |
| `risiko.klima.attribution_url` | URL | optional | engine | Sichtbarer Quellenlink https://data.hub.geosphere.at. |
| `risiko.klima.license` | const `CC-BY-4.0` | optional | engine | Lizenzkennung des aktiven Releases. |
| `risiko.klima.license_url` | URL | optional | engine | Link zur GeoSphere-Lizenzinformation. |
| `risiko.klima.terms_url` | URL | optional | engine | Sichtbarer Link zu den GeoSphere-Nutzungsbedingungen. |
| `risiko.klima.boundary` | string | optional | static | Wortgleicher Pflicht-Hinweis zur regionalen Aussagegrenze des 1-km-Rasters. |
6.2 `risiko.hitzebelastung` — historische GeoSphere-HORA-Schwellentage
Der optionale Block ist nur bei einer eigenständigen qualifizierten
Freigabe der Quelle geosphere-hora-hitzelayer zulässig. Sein Ursprung
ist ausschließlich die CC-BY-4.0-Distribution
hora_hitzelayer-v1-1km für die historische Klimaperiode 1991–2020;
HORA 3.0, HORA-Pass und LFRZ bleiben gesperrt.
Der aktive Quellenrelease ist im
Produktionsnachweis
rückgelesen. Derselbe Nachweis dokumentiert außerdem den später deployten
Report-Consumer: Ein nicht persistierender öffentlicher GET auf Merge-Commit
08d55c3 lieferte den vollständigen optionalen Block für den versionierten
Referenzpunkt. Ein fehlender Block bleibt bei fehlender Rasterabdeckung oder
einem fail-closed Release-/Rechte-Gate weiterhin vertragskonform.
hitzetage_30c_jahr,extreme_hitzetage_35c_jahr,tropennaechte_20c_jahrundhitzewellentage_kysely_jahrsind historische mittlere Tage pro Jahr, nicht Werte eines aktuellen Kalenderjahres.periodenbeginnist1991,periodenendeist2020,source_vintageist1991-2020,dataset_versionist1.source_slug,source_release_id,release_key,transform_version,retrieved_atundquelle_standbinden den konkreten aktiven Release.resolution_m: 1000,native_crs: EPSG:3416,easting_mundnorthing_mbeschreiben die tatsächlich genutzte regionale Rasterzelle.attribution,attribution_url,license: CC-BY-4.0,license_url: https://creativecommons.org/licenses/by/4.0/legalcode,terms_urlundboundarysind untrennbare Pflichtprovenienz.
Fehlt eine Freigabe, ein Wert oder ein Herkunftsfeld, bleibt der gesamte Block unsichtbar; ältere Reports ohne diesen additiven Block bleiben gültig. Die Darstellung enthält keine Ampel, keine Prognose, keinen Trend und keine parzellenscharfe Messung. Der Klima-Block daneben bezeichnet weiterhin das abgeschlossene Referenzjahr 2025. Details: öffentlicher HORA-Hitzebelastungs-Vertrag.
6.3 Weitere optionale Blöcke (Feldübersicht, Stand 07.10.2026)
Diese Blöcke stehen im deployten Report-Typ (lib/report/types.ts), haben aber
keine eigene Zeile in der Tabelle oben. Alle sind optional; ein fehlender Block
ist kein negativer Befund, der Grund steht gegebenenfalls in meta.degraded.
poi_dichte(OpenStreetMap, Pilot-Mirror, Lage-Indikator ohne Restriktion):score(0–100, erreichte Kategorien von fünf),categories(nahversorger,bildung,gesundheit,gastro,gruenraum, je boolean),nearby_count,ampel(gruen|gelb|rot),ampel_begruendung,hinweis,quelle_stand,source(mirror). Eine Kategorie gilt als erreicht, wenn mindestens ein POI in rund 900 m Luftlinie liegt (siehe Abschnitt „Nahversorgungsdarstellung“ oben); ÖV wird separat überoevabgebildet.zweitwohnsitz(Länder-Verordnung via RIS, Gemeinde-Ebene, keine Rechtsauskunft):gelistet,gemeinde,bezirk,bundesland,vo,vo_fundstelle,vo_url,ampel,ampel_begruendung,hinweis,quelle_stand.null, wenn die Gemeinde nicht bestimmbar ist.brachflaechen_potenzial(KI-Potenzialkarte 2024 aus dem qualifizierten OGD-Snapshot, neutraler Hinweis, keine amtliche Brachflächen-Feststellung):status(potential_hint|multiple_potential_hints|no_hint),features[](unveränderte Rohwerte je Fläche:source_feature_id,kg_raw,gnr_raw,gkz_raw,bl_kz_raw,ez_raw,brownfield_total,prediction_subclass_dop,build_area_m2,sealed_area_m2,seal_percent,source_area_m2,quality_warnings[]),hinweis,quelle_stand,layer_revision,source_release_id,attribution. Reine Grenzmehrdeutigkeit, fehlende Coverage sowie technische oder Release-Fehler ergebennullund einen Eintrag inmeta.degraded.denkmal.nachbarn_radius_m(number): der tatsächlich geprüfte Radius der Nachbarprüfung in Metern; Quelle für alle Anzeigetexte zudenkmal.nachbarn[].
Abgrenzung zum Standort-Check: Die Snapshot-Lanes widmung-wien,
widmung-vorarlberg, hochwasser-at und wlv-gzp, die der kostenlose
Standort-Check zusätzlich liest, werden vom Report-Assembler (lib/report/build.ts) nicht gelesen. Widmung und Hochwasser
im Report bleiben Niederösterreich-Pfade; der Report-Contract ändert sich
dadurch nicht (Stand 07.10.2026).
7. bestand — historisches Ziel, nicht im aktuellen Contract
Der deployte Report-Typ besitzt keinen Top-Level-Block bestand. Die früher
hier beschriebene Kombination aus Microsoft Building Footprints, Copernicus-
Versiegelung und Sentinel-2-NDVI wurde nicht als Report-Contract umgesetzt.
Produktcode und API-Konsumenten dürfen diese Felder weder voraussetzen noch
aus einem fehlenden Block fachliche Aussagen ableiten. Eine spätere Einführung
benötigt einen eigenen Quellen-, Rechte-, Coverage- und Contract-Slice.
8. grundbuch — historisches Ziel, nicht im aktuellen Contract
Der deployte Report-Typ besitzt keinen Top-Level-Block grundbuch; weder
Premium noch Planer erhalten ihn. Es gibt auch keinen aktuellen Report-Pfad,
der JustizOnline-Auszüge, Eigentümerangaben oder einen original_r2_key
liefert. ADR-0016 (nicht im öffentlichen Doku-Portal verfügbar) bleibt ein
historisches Ziel- und Datenschutzdokument, kein Betriebsbeleg. Vor einer
Einführung wären mindestens Rechtsgrundlage, Rollen, Klarnamengrenze,
Provider-/AVV-Vertrag, Retention und ein eigener API-Contract zu entscheiden.
9. scenarios[] — bis zu sechs Nutzungsszenarien
Bei erfolgreichem Geocoding und Engine-Lauf liefert die aktuelle Engine die
sechs konfigurierten Nutzungsklassen Wohnen, Gewerbe, PV-Anlage, Co-Working,
Lager und Tourismus. Ohne Koordinate oder bei Enginefehlern bleibt die Liste
leer. Ampel, Kennzahlen, Kosten und Fördertrigger sind deterministisch
(ADR-0015). Die vorhandenen sechs Kostenmodelle werden unabhängig von der
Ampelfarbe berechnet; ein rotes Nicht-PV-Szenario behält deshalb seine
Kostenspanne. PV bleibt seit #562 ohne Kostenrechnung. Der optionale Fließtext wird erst nach der Engine hinzugefügt
und ist nur für standard und premium zulässig.
| Feld | Typ | Pflicht | Herkunft | Beschreibung |
|---|---|---|---|---|
| `scenario_id` | string | ja | engine | Stabiler Slug, z. B. `pv-freiflaeche`. |
| `nutzungsklasse` | enum `wohnen`|`gewerbe`|`pv_anlage`|`co_working`|`lager`|`tourismus` | ja | engine | Nutzungsklasse (ADR-0015 Säule 3). |
| `titel` | string | ja | engine | Anzeige-Titel (Konfig-Template, kein LLM). |
| `ampel` | enum `gruen`|`gelb`|`rot` | ja | engine | Eignung: harte Ausschlusskriterien (rot) / weiche Faktoren (gelb/grün). |
| `ampel_faktoren` | object[] | ja | engine | `{ faktor, bewertung:"+/-/0", quelle_ref }` — nachvollziehbare Regel-Treffer (Widmung, Fläche, Altlast sowie die Risiko-Layer Hochwasser HQ30/HQ100, Denkmalschutz, strenge Schutzgebiete → je -, stufen grün auf gelb). `quelle_ref` ist ein rollenbasierter Herkunfts-Slug (z. B. `annahme`, `standard-anker`, `fachentscheid-YYYY-MM-DD`) und nennt nie eine Person; abweichende Slugs aus Alt-Snapshots werden beim Lesen auf diese Form normalisiert. |
| `ausschluss_grund` | string | null | wenn `rot` | engine | Harte Mindestanforderung verletzt (z. B. Mindestfläche, Schutzgebiet). |
| `kennzahlen` | object | optional | engine | Szenario-spezifische deterministische Kennzahlen. Neue PV-Semantik: ausschließlich `spez_ertrag_kwh_pro_kwp`, sofern vorhanden; siehe §1.5. Alle aus Engine, nie LLM. |
| `cost` | object | null | ja | engine | `{ low, expected, high, unit:"EUR", bandwidth_pct }`; der Typ erlaubt null, PV liefert null (nicht berechnet); die übrigen Kostenmodelle liefern unabhängig von der Ampelfarbe eine Kostenspanne. |
| `cost_components` | object[] | ja | engine | Exakt ADR-0015: `[{ label, amount, source }]` — `source` zitiert Tabelle/Faktor. |
| `methodology` | string | ja | static | Exakt ADR-0015: „ÖNORM B 1802-1:2022 + Heft 3/2025 + BPI Q-aktuell". |
| `as_of_date` | ISO-8601 date | ja | engine | ADR-0015 — Cost-Stichtag (Config-Stand). |
| `disclaimer` | string | ja | static | ADR-0015 — „Grob-Kostenschätzung, ersetzt keine Detailkalkulation durch Sachverständigen/Planer". |
| `foerder_trigger` | string[] | ja | engine | IDs förderfähiger Bausteine → join auf `foerderungen[]` (z. B. `kpc-flaechenrecycling`, `kpc-waermepumpe`). |
ruleset_unbestaetigt | boolean | ja | engine | Kennzeichnet sichtbar, dass die fachlichen Regeln noch nicht freigegeben sind. |
| `narrativ` | string | optional; nur standard/premium | llm | 1–2 Sätze. Der LLM-Input enthält nur nicht rote Nicht-PV-Szenarien; beim Ergebnis-Mapping fehlt derzeit ein zweiter Ampel-Guard. Fehlt bei free, planer, fehlendem Key oder jedem fail-open LLM-Fehler. |
Offene Frage / Annahme: ADR-0015 listet Nutzungsklassen Wohnen/Gewerbe/PV/Co-Working/Lager/Tourismus, die exakten harten Mindestanforderungen + Cost-Bausteine pro Klasse liefert der POC- Woche-3-Workshop des Cofounders. Dieser Spec fixiert nur Struktur, nicht die Werte → Workshop-Input erforderlich.
10. foerderungen[] — deterministischer Konfigurationskatalog
Die aktuelle Engine liest den eingecheckten, fachlich noch unbestätigten
Entwurfs-Förderkatalog und verbindet ihn deterministisch über
scenarios[].foerder_trigger. Ein Förder-RAG, ein
Klassifikations-LLM, match_score oder match_begruendung sind im deployten
Report-Contract nicht vorhanden.
| Feld | Typ | Pflicht | Herkunft | Beschreibung |
|---|---|---|---|---|
| `foerder_id` | string | ja | engine | Stabiler Slug, referenziert von scenarios[].foerder_trigger. |
| `programm` | string | ja | static | Sichtbarer Programmname. |
| `traeger` | string | ja | static | Träger aus der unbestätigten Entwurfskonfiguration. |
| `max_betrag_eur` | number | null | ja | static | Konfigurationswert, nie vom LLM erzeugt. |
| `max_satz_pct` | number | null | ja | static | Konfigurationswert, nie vom LLM erzeugt. |
| `bedingung` | string | ja | static | Sichtbare Bedingung aus der Konfiguration. |
| `quelle` | string | ja | static | Quellen-/Standhinweis des Katalogeintrags. |
11. `sources[]` — Attribution-Pflicht (POC-Kriterium)
Pro tatsächlich verwendetem Layer genau ein Eintrag. Erfüllt das POC-Akzeptanzkriterium „Quellen-Block … Datenquelle + Lizenz + Stand" und die Attribution-Pflicht aus DATA-SOURCES §Attribution-Maintenance.
| Feld | Typ | Pflicht | Herkunft | Beschreibung |
|---|---|---|---|---|
| `layer` | string | ja | engine | Stabile Report-Sektion, z. B. `parcel`, `widmung`, `klima` oder `altlasten`. |
| `quelle` | string | ja | static | Sichtbarer Quellenname beziehungsweise vorgeschriebener Attributionstext. |
| `lizenz` | string | ja | static | Sichtbare Lizenzbezeichnung, z. B. „CC BY 4.0". |
| `stand` | string | ja | engine | Fachlicher Quellenstand. Fehlt ein Vintage, muss ein technischer Abruf ausdrücklich als solcher etikettiert werden, z. B. Techn. Abruf 2026-08-26. |
| `source_release_id` | UUID | optional | engine | Exakter qualifizierter Release bei release-gebundenen Quellen. Für den FND-03-BEV-Snapshot bei `hit`, `no_hit` und `ambiguous_boundary` Pflicht. |
| `quelle_url` | URL | optional | static | Öffentliche Primärquelle; niemals interner Artefakt- oder Storage-Pfad. |
| `lizenz_url` | URL | optional | static | Maschinenlesbarer Lizenzlink für Web und PDF. |
| `aenderungshinweis` | string | optional | static/engine | Sichtbarer Hinweis auf LandNutzen-Normalisierung oder Ableitung. Beim FND-03-BEV-Snapshot Pflicht. |
| `non_endorsement_hinweis` | string | optional | static | Sichtbarer Hinweis, dass die Quelle die LandNutzen-Auswertung nicht bestätigt oder unterstützt. Beim FND-03-BEV-Snapshot Pflicht. |
| `modellhinweis` | string | optional | static | Sichtbare fachliche Grenze eines Modells oder einer Ableitung. |
Für den qualifizierten BEV-Snapshot müssen Quelle, CC-BY-4.0-Lizenz,
Primär- und Lizenzlink, fachlicher Stand, source_release_id,
Änderungshinweis und Non-Endorsement sowohl im Web als auch im PDF sichtbar
bleiben. Ein WMS-Legacy-Treffer darf keine Snapshot-Release-ID übernehmen.
Für den Altlasten-WFS müssen quelle exakt
„Datenquelle: Umweltbundesamt", die bedingte UBA-Weiterverwendung,
Primär-/Bestätigungslink, der als technisch etikettierte Abruf und
source_release_id in Web und PDF sichtbar bleiben. Die UBA-Bestätigung ist
keine OGD-/CC-Lizenz. Ein fehlender oder abgelaufener Pointer sowie Rechte-,
Qualitäts-, Frische- oder Scope-Drift erzeugen meta.degraded und niemals
einen grünen Altlasten-No-Hit.
sources[] enthält die im konkreten Report tatsächlich verwendeten
Attributionen. Eine feste Mindestzahl und
confidence.poc_min_layers_met gehören nicht zum aktuellen Runtime-Contract.
Fehlende oder gesperrte Quellen werden über meta.degraded und den jeweils
nullable/fehlenden Layer sichtbar, nicht über ein erfundenes Konfidenzobjekt.
12. confidence — historisches Ziel, nicht im aktuellen Contract
Der deployte Report-Typ besitzt keinen Top-Level-Block confidence und
berechnet keine Gesamt-Konfidenz. Das ist beabsichtigt: Quellenausfälle und
fachliche Grenzen bleiben layergenau in meta.degraded, Provenienzfeldern,
Hinweisen und Kostenbandbreiten sichtbar. Ein einzelner Gesamtwert würde diese
unterschiedlichen Grenzen unzulässig verdichten.
13. `disclaimer` — Haftung & Opt-in
| Feld | Typ | Pflicht | Herkunft | Beschreibung |
|---|---|---|---|---|
| `disclaimer.text` | string | ja | static | Voller Haftungsausschluss-Text (ADR-0007/POC-Kriterium B). |
| `disclaimer.opt_in_required` | boolean | ja | static | Immer `true` — Checkbox vor Report-Anzeige (POC-Kriterium B). |
| `disclaimer.opt_in_recorded_at` | ISO-8601 datetime | null | ja | engine | Wann der User die Checkbox bestätigt hat (Audit). `null` = noch nicht bestätigt → Report bleibt verdeckt. |
| `disclaimer.bausteine` | object | ja | static | Quellen-spezifische Pflicht-Hinweise. Vier Kernfelder sind immer vorhanden; optionale Felder werden nur bei vorhandenem Layer gesetzt, unter anderem für Radon, Breitband, Lärm, ÖV, POI-Dichte, Hochwasser, Schutzgebiete, Denkmal/Nachbarn, Zweitwohnsitz, WLV, Pendeln, Demografie, Boden, Bahn, Erdbeben, LCZ, Google Solar und Brachflächen-Potenzial. grundbuch_stichtag ist im Typ reserviert, bleibt mangels aktuellem grundbuch-Block aber ungesetzt. |
14. Optionale Szenario-Narrative
Es gibt keinen Top-Level-Block narrative. Der einzige aktuelle LLM-Output
ist das optionale scenarios[].narrativ aus §9. standard und premium
verwenden dafür denselben standardReport-Task; free und planer rufen
keinen LLM auf. Modell und Usage werden operativ im LLM-Usage-Ledger erfasst,
nicht als narrative.modell in den Report geschrieben.
Es gibt außerdem weder narrative.summary, narrative.empfehlung noch
narrative.guard_passed. Fehlt das optionale Narrativ, bleiben Engine-Felder
und PDF vollständig. Die Promptgrenze „keine neuen Zahlen“ ist kein Ersatz
für den nicht implementierten automatischen Zahlen-Gegencheck.
15. Aktuelle technische Tiermatrix
Es gibt noch keine organizations.tier-Spalte und kein deploytes
Tier-/Billing-Modell. lib/report/tier.ts leitet Premium/Planer derzeit
provisorisch aus aktiven institutionellen Organisationstypen ab; alle nicht
explizit zugeordneten Fälle bleiben fail-safe free. standard existiert im
technischen Contract, wird vom provisorischen Org-Mapping aber noch nicht
vergeben. Frühere Preis-, Abo- oder „registriert = standard“-Aussagen sind
keine aktuelle Produktzusage.
| Contract-/Runtime-Aspekt | free | standard | premium | planer |
|---|---|---|---|---|
| Pflicht-Top-Level-Felder aus §2 | gleich | gleich | gleich | gleich |
sechs scenarios[] mit Engine-Zahlen | bei erfolgreichem Engine-Lauf | bei erfolgreichem Engine-Lauf | bei erfolgreichem Engine-Lauf | bei erfolgreichem Engine-Lauf |
scenarios[].narrativ | nein | optional; Input nur nicht rot | optional; Input nur nicht rot | nein |
solar.google | nein | nein | optional bei API-Key/Coverage | optional bei API-Key/Coverage |
Persistenz von solar.google | entfällt | entfällt | nein, Live-Overlay wird vor Speicherung entfernt | nein, Live-Overlay wird vor Speicherung entfernt |
foerderungen[], sources[], disclaimer | gleicher Contract | gleicher Contract | gleicher Contract | gleicher Contract |
bestand, grundbuch, confidence, Top-Level-narrative | nicht vorhanden | nicht vorhanden | nicht vorhanden | nicht vorhanden |
Weitere optionale Layer hängen von Coverage, aktivem Quellenrelease, Rechten, Qualität und Providerverfügbarkeit ab, nicht pauschal vom Tier. Ein fehlender Layer darf nicht als negativer fachlicher Befund umgedeutet werden.
Die Report-Quota ist vom dargestellten Produkttier getrennt (Report-Quota-Vertrag):
- anonym: 10 Anfragen/Tag pro pseudonymem Request-IP-Bucket;
- bestehende, sicher aufgelöste Kontositzung: 10 Anfragen/Tag pro pseudonymem Account-Bucket — unabhängig von anderen Konten hinter derselben IP;
- API-Key: das eigene in
api_keys.daily_limithinterlegte Tageslimit.
Ein angebotener ungültiger oder widerrufener API-Key bleibt 401 und darf
nicht auf Sitzung oder anonym zurückfallen. Kann die bestehende Sitzung nicht
sicher aufgelöst werden oder wurde der User gelöscht, gilt der anonyme
IP-Bucket. X-Auth-Mode weist anon, session oder key aus; die beiden
Rate-Limit-Header beschreiben den tatsächlich gewählten Bucket. Der
Account-Bucket ersetzt nicht reports.ip_hash: Dort bleibt das Pseudonym der
tatsächlichen Request-IP gespeichert.
Tier-Bestimmung erfolgt serverseitig aus Auth-Kontext (ADR-0006), nie client-seitig — Feld-Stripping passiert im Backend, bevor das JSON den Server verlässt. Die bestehende Browser-Sitzung eröffnet kein neues headless Cookie-Auth-Verfahren; Maschinenclients verwenden weiterhin API-Keys.
16. PDF-Mapping und PDF/UA-Shadow
Der öffentliche Produktpfad verwendet für alle vier Tiers
@react-pdf/renderer und anschließend pdf-lib. Es gibt kein deploytes
Puppeteer-Premium-Template, keine grundbuch-Sektion, keine angehängte
HORA-Pass-Datei und kein narrative.guard_passed. Das PDF rendert die im
gespeicherten Report tatsächlich vorhandenen strukturierten Felder sowie ein
gegebenenfalls vorhandenes scenarios[].narrativ. Der bestehende
Mehrseiten-Viewervertrag bleibt in REPORT-OUTPUT
verbindlich; er ist kein PDF/UA-Nachweis.
ADR-0060 definiert getrennt den
internen pdfkit@0.20.1-Shadow:
| Tier | feste Narrative | fester synthetischer Google-Solar-Block | Outputvertrag |
|---|---|---|---|
free | nein | nein | 5 Seiten, 2 Figuren, 2 Links |
standard | ja, nur nicht rot | nein | 5 Seiten, 2 Figuren, 2 Links |
premium | ja, nur nicht rot | ja | 5 Seiten, 2 Figuren, 2 Links |
planer | nein | ja | 5 Seiten, 2 Figuren, 2 Links |
Dieser Shadow ist ausschließlich per POST /api/internal/pdf-ua-shadow/[tier]
in Vercel Preview erreichbar. Die Route prüft Methode, URL, Query, Body-Rahmen
und den Tier-Pfad, aber keine Request-Payload fließt in die PDF-Inhalte.
Slice C nach ADR-0061 speist nun
vier repository-fixierte Objekte dieses deployten Report-Contracts ein. Der
Mapper bindet meta, parcel, steckbrief, scenarios, foerderungen und
den optionalen solar.google-Block an die vorhandene PDF/UA-View. Nicht
freigegebene Felder — insbesondere Adresse, Koordinate und Provider-Rohfelder
— werden nicht weitergereicht. Der Pfad liest weiterhin weder DB, Session,
Provider noch fachliche Umgebungsdaten. Seine exakte
Seiten-/Figuren-/Linkzahl ist ein synthetischer Testvertrag und keine
Produktzusage. Produktion antwortet für die interne Route absichtlich 404;
dies ist für alle vier Tier-POSTs und einen GET auf Merge-Commit
59036df7aca8a70025efcbf8f0d8dd467700cb9c und Produktionsdeployment
dpl_FbKhtfArLz6wnDTAaRn3aq1bVJh9 rückgelesen. Der öffentliche
React-PDF-Pfad bleibt unverändert. Issue #333 bleibt bis zur
vollständigen Realreport-, Viewer-, Rollout-, Live- und Screenreader-Abnahme
offen.
Slice D nach
ADR-0062 ersetzt diese
offene Realreportsektionen-Lücke im synthetischen Contract: Die Fixtures
enthalten nun alle 31 repräsentativen Bereiche des deployten Report-Typs,
und der Mapper muss sie exakt geordnet in Lage, Umwelt, Restriktionen und
Governance überführen. Fehlende Bereiche oder abweichende relevante
Kardinalitäten schlagen fail-closed fehl.
Der Outputvertrag wächst für alle vier Tiers auf acht Seiten, drei Figures,
zwei Links und 16 Pagination-Artefakte. Die Figure-Alternativtexte werden aus
Fläche und künstlicher Fixture-ID, vier Risikosektionen sowie den sechs
Szenariokosten abgeleitet. Sie enthalten keine Adresse, Koordinate oder reale
Karte. Die Preview-Route kennzeichnet den Stand als slice-d-1; Datenbank-,
Session-, Provider- und Netzwerkgrenzen bleiben unverändert.
Diese Sektionsparität gilt nur für die repository-fixierten synthetischen Objekte. Echte gespeicherte Reports, der Mehrseiten-Viewer des neuen Renderers, Produktrollout, Live-Readback und Screenreader-Abnahme bleiben offen. Der öffentliche React-PDF-Pfad bleibt unverändert.
Der geschützte Slice-D-Preview dpl_Ew4nDGJZYUX9fRyiNSuqSz8FFutU bestand den
Vier-Tier-Vertrag auf Commit 088ed8dab1749da623e354ffaa5bab48acc3cd17.
Merge-Commit 056eafb0eddd0ec6369f71db65862f37b788b152 und
Produktionsdeployment dpl_2bsKrqhys1CuRFQV6QUV6HiSJghg belegen die
Produktionstrennung: Die interne Route antwortet dort für alle vier
Tier-POSTs und einen GET leer 404. Das öffnet keinen PDF/UA-Produktpfad.
Slice E nach
ADR-0063 schließt die
synthetische Store-Lücke: free, standard, premium und planer werden
über die exakten saveReport()- und getReport()-SQL-Abfragen in einem
isolierten PostgreSQL-Testschema als echtes jsonb gespeichert und wieder als
StoredReport gelesen. Der Renderer erhält ausschließlich dieses geladene
Objekt. Der Test prüft Ablauf, Eigentümer, Disclaimer-Opt-in und den Ausschluss
von solar.google, solar_google sowie dem zugehörigen Disclaimer-Baustein
aus reports.report_json.
Der fail-closed Mapper übernimmt keine Report-ID, Adresse, Eigentümer-ID oder
exakten Opt-in-Zeitpunkt. Die View PDF-UA-SLICE-E-1 exponiert nur minimierte
Persistenzmerkmale. Der Preview-Shadow rendert eine repository-fixierte Kopie
des persistenznormalisierten Kanaris und liest selbst keine Datenbank;
erfolgreiche Antworten tragen slice-e-1. Acht Seiten, 31 Sektionen, drei
Figures, zwei Links und 16 Pagination-Artefakte bleiben unverändert.
PR #541, CI-Lauf 33163812578 und der geschützte Preview
dpl_AejPoE9E4Joeh9J1ireiHZdKz7k7 verifizieren alle vier Tiers. Der Merge-SHA
f5d57d9ac0b4c57a576fa64b9fbddbff49ef8726 wird vom Produktionsdeployment
dpl_GqMcWA5uHvML97ZrciT4dkzPKoQD geliefert; dort bleibt die interne Route
für alle vier Tier-POSTs und einen GET leer 404.
Diese Prüfung verwendet keine Produktionsdatenbank und keinen gespeicherten Nutzerreport. Der öffentliche React-PDF-Pfad bleibt unverändert. Karten- und Viewerregression, Produktrollout, PDF/UA-Live-Readback sowie manuelle Screenreader-Abnahme bleiben offen; Issue #333 bleibt offen.
Slice F nach
ADR-0064 schließt den
automatisierbaren Apple-Rendererteil der Viewerregression. Auf macOS lädt
PDFDocument die vier bereits vom bestehenden Harness erzeugten PDFs. Für
jedes Tier sind acht Seiten, zwei Linkannotationen, acht seitenspezifische
Textmarker und mindestens 100 Zeichen Textschicht pro Seite verpflichtend.
Quartz rendert anschließend alle 32 Seiten in 8-Bit-Graustufe; dieselben
Rastergrenzen wie bei Poppler verwerfen leere, abgeschnittene oder dunkel
gefüllte Seiten.
Der lokale Referenzlauf auf macOS 26.6.2 und Swift 6.3.3 bestand 32/32 Seiten.
Nicht-macOS-Läufe melden den fehlenden Apple-Stack ausdrücklich und behalten
veraPDF sowie beide Poppler-Backends als CI-Vertrag. PR #543, CI-Lauf
33166935667 und Preview dpl_5NCcXGThbbpocRGCsfhS2VsZMg5H belegen auch die
vier deployten Shadow-Dateien mit veraPDF und Apple PDFKit / Quartz. Merge-SHA
e998d3e42886adef491eb489f5c8a874aca652a3 wird vom Produktionsdeployment
dpl_D4fhMzmmwCDvhGrTcAN3Mm3TMcEL geliefert; dort bleibt die interne Route
für vier Tier-POSTs und einen GET leer 404.
Slice F verändert weder slice-e-1 noch Route oder React-PDF-Produktpfad. Die
manuelle Apple-Vorschau- und VoiceOver-Stichprobe wurde am 4. September 2026
für die SHA-256-gebundene synthetische Premium-Preview-Datei abgeschlossen:
acht von acht Seiten waren vollständig sichtbar und per Tastatur erreichbar;
Überschriften, Tabellen, Listen, drei beschriebene Abbildungen und zwei
benannte Links wurden im nativen Accessibility-Baum exponiert.
Chrome/PDFium-Tag-Semantik, echte Karten, Produktrollout und PDF/UA-Live-Readback eines echten Nutzerreports bleiben offen; Issue #333 bleibt offen.
17. Historisches Beispiel-JSON — nicht als aktueller Contract verwenden
Das folgende Beispiel bleibt ausschließlich als Entscheidungshistorie des
frühen Zielmodells erhalten. Es ist kein gültiges Beispiel des deployten
Report-Typs: Unter anderem schema_version, die zweite Top-Level-
report_id, bestand, grundbuch, confidence und narrative existieren
heute nicht; mehrere meta- und Förderfelder weichen ebenfalls ab.
Implementierung, Tests und Integrationen müssen stattdessen den Typ aus §2
beziehungsweise lib/report/types.ts verwenden.
```json { "schema_version": "1.0.0", "report_id": "9f2c1e7a-0b44-4d21-9c3e-7a1b6f0e2d55", "meta": { "address": { "raw": "Hauptstraße 12, 2230 Gänserndorf", "normalized": "Hauptstraße 12, 2230 Gänserndorf, Niederösterreich, AT" }, "coordinate": { "lat": 48.3417, "lon": 16.7192, "source": "bev" }, "region": { "slug": "at-noe", "name": "Niederösterreich" }, "report_tier": "premium", "org_id": 42, "requested_by": "user", "as_of": "2026-05-16", "generated_at": "2026-05-16T09:14:02Z", "engine_version": "0.3.0", "config_version": "bodenwerte@2025.4+herstellungskosten@heft3-2025+bpi@2025Q2", "degraded": [] }, "parcel": { "found": true, "lookup_status": "hit", "parcel_uid": "AT:08221:1247/3", "source_release_id": "cb735e6f-6c32-5de0-87fd-594aa34a1903", "match_method": "point_in_polygon", "match_confidence": 1, "grundstuecksnummer": "1247/3", "katastralgemeinde": "08221 Gänserndorf", "area_m2": 1840, "area_tolerance_pct": 10, "source": "dkm_mirror", "kataster": "grundsteuerkataster", "bev_stand": "2026-04-01", "vermessung_disclaimer": "Grundstücksgeometrie und -fläche stammen aus der BEV-Digitalen Katastralmappe (DKM) und ersetzen keine rechtsverbindliche Vermessung. Verbindliche Grenzen und Flächen ergeben sich nur aus Grundbuch bzw. behördlicher Vermessung (Grenzkataster)." }, "widmung": { "klasse": "bauland", "detail": "Bauland-Wohngebiet (BW)", "ampel": "gruen", "ampel_begruendung": "Bauland-Wohngebiet, baureif, keine harten Restriktionen", "restriktionen": [], "rechtsverbindlich": false, "quelle_stand": "2026-05-12" }, "risiko": { "hochwasser": { "stufe": "niedrig", "hq_zone": null }, "hora_pass": { "r2_key": "hora-pass/48.3417-16.7192.pdf", "gefahren": { "hochwasser": "niedrig", "oberflaechenabfluss": "niedrig", "lawine": "keine", "erdbeben": "mittel", "rutschung": "keine", "windspitzen": "mittel", "blitzdichte": "niedrig", "hagel": "mittel", "schneelast": "niedrig", "hitzeepisoden": "mittel" } }, "altlasten": { "intersect": false, "nearby_m": null, "referenz": null, "ubaid": null, "hinweis": "Nicht-Listung garantiert keine Schadstofffreiheit. Enthalten sind ausschließlich veröffentlichte Altlasten des Altlastenportal-WFS; der Status „Altlast vorgeschlagen“ ist ausgeschlossen. Punktprüfung, keine rechtsverbindliche Auskunft.", "quelle_stand": null, "source_release_id": "8ed8d136-f3f8-5a78-946b-4b419a050269", "retrieved_at": "2026-08-26T03:16:54.083Z", "source_vintage": null }, "geogen": { "rutschung": "keine" }, "klima": { "jahresmittel_temp_c": 11.9, "jahresniederschlag_mm": 467.7, "sonnenstunden_jahr": 2121.7, "referenzjahr": 2025, "quelle_stand": "2026-08-04", "source_slug": "geosphere-spartacus-v3", "source_release_id": "81ef8803-2d64-5d30-9d7f-cd526ce57e69", "release_key": "74f23173-4256-5296-b4e3-d93aa1f70a5c", "dataset_version": "3", "transform_version": "spartacus-v3-snapshot-v1", "source_vintage": "2025", "retrieved_at": "2026-08-04T23:16:02.000Z", "resolution_m": 1000, "native_crs": "EPSG:3416", "easting_m": 650500, "northing_m": 498500, "attribution": "Datenquelle: GeoSphere Austria - https://data.hub.geosphere.at", "attribution_url": "https://data.hub.geosphere.at", "license": "CC-BY-4.0", "license_url": "https://dataset.api.hub.geosphere.at/v1/docs/user-guide/license.html", "terms_url": "https://data.hub.geosphere.at/de/legal?lang=de", "boundary": "Das 1-km-Raster ist ein regionaler Klimaindikator und weder Messung noch Prognose für eine einzelne Parzelle." } }, "bestand": { "bebaut": true, "gebaeude_anzahl": 1, "footprint_flaeche_m2": 142, "versiegelungsgrad_pct": 31, "ndvi_index": 0.42, "footprint_stand": "2025-09-01" }, "grundbuch": { "bezirksgericht": "Gänserndorf", "katastralgemeinde": "08221 Gänserndorf", "einlagezahl": "612", "as_of": "2026-04-02T07:51:10Z", "eigentum": { "art": "Schlichtes Miteigentum", "personen": [ { "anteil": "1/2", "name": "[PSEUDONYMISIERT]", "wohnort_grob": "AT, NÖ" }, { "anteil": "1/2", "name": "[PSEUDONYMISIERT]", "wohnort_grob": "AT, Wien" } ], "aktivierungs_komplexitaet": "mittel" }, "belastungen": [ { "art": "Hypothek", "berechtigter": "[PSEUDONYMISIERT]", "impact": "lösbar mit Refinanzierung" } ], "dienstbarkeiten_zugunsten": [], "dienstbarkeiten_zulasten": [ { "art": "Wegerecht", "ueber": "GSt 1247/3", "zugunsten": "GSt 1247/4" } ], "score": { "aktivierungs_faehigkeit": "mittel", "punktwert": 1, "kommentar": "Zwei Miteigentümer, eine lösbare Hypothek. Aktivierung mit überschaubarem Koordinationsaufwand realistisch, Vorbereitung 3–6 Monate." }, "original_r2_key": "grundbuch/3f9a...c1.pdf", "disclaimer": "Stand: 2026-04-02. Spätere Änderungen können bestehen. Verbindliche Auskunft nur via aktuellem Auszug." }, "scenarios": [ { "scenario_id": "pv-freiflaeche", "nutzungsklasse": "pv_anlage", "titel": "PV-Freiflächen-Anlage 20 kWp", "ampel": "gelb", "ampel_faktoren": [], "ausschluss_grund": null, "kennzahlen": { "kwp": 20, "ertrag_kwh_jahr": 22800, "ertrag_eur_jahr": 6100, "strompreis_ct_kwh": 26.8 }, "cost": { "low": 26000, "expected": 30000, "high": 34500, "unit": "EUR", "bandwidth_pct": 20 }, "cost_components": [ { "label": "PV-Module + Wechselrichter (20 kWp)", "amount": 26000, "source": "Konfig: PV 1.300 EUR/kWp installiert (ADR-0015 Säule 3)" }, { "label": "Montage Freifläche + Unterkonstruktion", "amount": 3000, "source": "Konfig: Freiflächen-Aufschlag" }, { "label": "Netzanschluss + Zählertausch", "amount": 1000, "source": "Konfig: Standard-Netzanschluss NÖ" } ], "methodology": "ÖNORM B 1802-1:2022 + Heft 3/2025 + BPI Q-aktuell", "as_of_date": "2026-05-14", "disclaimer": "Grob-Kostenschätzung, ersetzt keine Detailkalkulation durch Sachverständigen/Planer", "foerder_trigger": ["kpc-pv"], "narrativ": "PVGIS liefert für die Beispielkonfiguration einen rechnerischen Jahresertrag von etwa 22.800 kWh. Daraus folgt keine Eignungsentscheidung; Verschattung, Dach- beziehungsweise Flächennutzung, Netzanschluss und Genehmigungen sind gesondert zu prüfen. Die Investitionskosten liegen bei etwa 30.000 EUR (±20 %)." }, { "scenario_id": "wohnen-sanierung", "nutzungsklasse": "wohnen", "titel": "Sanierung Bestandsgebäude für Wohnnutzung", "ampel": "gelb", "ampel_faktoren": [ { "faktor": "Widmung Bauland-Wohngebiet", "bewertung": "+", "quelle_ref": "noegis-widmung" }, { "faktor": "Bestand vorhanden", "bewertung": "+", "quelle_ref": "ms-footprints" }, { "faktor": "Erdbeben mittel (HORA)", "bewertung": "-", "quelle_ref": "hora-pass" } ], "ausschluss_grund": null, "kennzahlen": { "wohnnutzflaeche_m2": 110 }, "cost": { "low": 232000, "expected": 290000, "high": 348000, "unit": "EUR", "bandwidth_pct": 20 }, "cost_components": [ { "label": "Gebäude-Bestand-Sanierung", "amount": 255000, "source": "Heft 3/2025 NÖ gehoben 3.200 EUR/m² × 110 m² × 0.72 (Sanierungs-Faktor)" }, { "label": "Heizungstausch (KPC-förderfähig)", "amount": 25000, "source": "Konfig: HVAC-Wärmepumpe Standard" }, { "label": "Außenanlagen-Aufschlag", "amount": 10000, "source": "Heft 3/2025: ~2 % Aufschlag (einfach)" } ], "methodology": "ÖNORM B 1802-1:2022 + Heft 3/2025 + BPI Q-aktuell", "as_of_date": "2026-05-14", "disclaimer": "Grob-Kostenschätzung, ersetzt keine Detailkalkulation durch Sachverständigen/Planer", "foerder_trigger": ["kpc-waermepumpe", "kpc-flaechenrecycling"], "narrativ": "Das Bestandsgebäude lässt sich für Wohnnutzung sanieren. Der Sanierungsaufwand wird auf rund 290.000 EUR geschätzt (±20 %), basierend auf dem Heft-3/2025-Wert für NÖ gehobene Ausstattung und einem Sanierungs-Faktor. Der Heizungstausch ist KPC-förderfähig." }, { "scenario_id": "lager-logistik", "nutzungsklasse": "lager", "titel": "Lager-/Logistiknutzung", "ampel": "rot", "ampel_faktoren": [ { "faktor": "Widmung Wohngebiet schließt Lager aus", "bewertung": "-", "quelle_ref": "noegis-widmung" } ], "ausschluss_grund": "Harte Ausschlussregel: Widmung Bauland-Wohngebiet lässt gewerbliche Lagernutzung nicht zu.", "kennzahlen": {}, "cost": { "low": 0, "expected": 0, "high": 0, "unit": "EUR", "bandwidth_pct": 0 }, "cost_components": [], "methodology": "ÖNORM B 1802-1:2022 + Heft 3/2025 + BPI Q-aktuell", "as_of_date": "2026-05-14", "disclaimer": "Grob-Kostenschätzung, ersetzt keine Detailkalkulation durch Sachverständigen/Planer", "foerder_trigger": [], "narrativ": "Eine Lager-/Logistiknutzung ist an diesem Standort aufgrund der Widmung Bauland-Wohngebiet rechtlich ausgeschlossen." } ], "foerderungen": [ { "foerder_id": "kpc-pv", "programm": "KPC Photovoltaik", "traeger": "KPC", "max_betrag_eur": null, "max_satz_pct": 30, "bedingung": "Investitionszuschuss für Photovoltaik-Anlagen, Antrag vor Umsetzung", "match_score": 0.88, "match_begruendung": "Das PV-Freiflächen-Szenario fällt in den förderfähigen Anlagentyp der KPC-Photovoltaik-Richtlinie.", "quelle": "KPC-Richtlinie PV, Stand Q1/2026 (quartalsweise validiert)" }, { "foerder_id": "kpc-flaechenrecycling", "programm": "KPC Flächenrecycling", "traeger": "KPC", "max_betrag_eur": 60000, "max_satz_pct": 75, "bedingung": "Bis 75 % bzw. max. 60.000 EUR für Konzept, 50.000 EUR Detailuntersuchung", "match_score": 0.71, "match_begruendung": "Reaktivierung einer untergenutzten Baufläche — passt zum Flächenrecycling-Zweck.", "quelle": "KPC-Richtlinie Flächenrecycling, Stand Q1/2026" } ], "sources": [ { "layer": "parcel", "quelle": "Datenquelle: BEV – Bundesamt für Eich- und Vermessungswesen (data.bev.gv.at), CC BY 4.0", "lizenz": "CC BY 4.0", "stand": "2026-04-01", "source_release_id": "cb735e6f-6c32-5de0-87fd-594aa34a1903", "quelle_url": "https://data.bev.gv.at/geonetwork/srv/api/records/ac633f87-19f4-4f78-9052-74c63ff88331", "lizenz_url": "https://creativecommons.org/licenses/by/4.0/", "aenderungshinweis": "LandNutzen normalisiert BEV-KG und Grundstücksnummer zu einer stabilen internen Parzellen-ID und berechnet den Punkt-in-Polygon-Treffer.", "non_endorsement_hinweis": "Das BEV bestätigt oder unterstützt diese LandNutzen-Auswertung nicht." }, { "layer": "widmung", "quelle": "Land NÖ — Widmungsumhüllende (OGD, gespiegelt nach PostGIS)", "lizenz": "CC BY 4.0", "stand": "2026-05-12" }, { "layer": "klima", "quelle": "Datenquelle: GeoSphere Austria - https://data.hub.geosphere.at", "lizenz": "CC-BY-4.0", "stand": "2025", "source_release_id": "81ef8803-2d64-5d30-9d7f-cd526ce57e69", "quelle_url": "https://data.hub.geosphere.at/en/dataset/spartacus-v3-1y-1km", "lizenz_url": "https://dataset.api.hub.geosphere.at/v1/docs/user-guide/license.html", "aenderungshinweis": "LandNutzen hat die gelieferten Integer-Skalierungen ausschließlich für die Anzeige umgerechnet: TM durch 10 in °C, RR durch 10 in mm und SA durch 3600 in Stunden.", "non_endorsement_hinweis": "GeoSphere Austria bestätigt oder unterstützt diese LandNutzen-Auswertung nicht.", "modellhinweis": "Das 1-km-Raster ist ein regionaler Klimaindikator und weder Messung noch Prognose für eine einzelne Parzelle." }, { "layer": "altlasten", "quelle": "Datenquelle: Umweltbundesamt", "lizenz": "Weiterverwendung gemäß UBA-Bestätigung vom 11.08.2026", "stand": "Techn. Abruf 2026-08-26", "source_release_id": "8ed8d136-f3f8-5a78-946b-4b419a050269", "quelle_url": "https://www.altlasten.gv.at/atlas/altlasten-gis.html", "lizenz_url": "https://github.com/ostheimer/landnutzen/issues/351#issuecomment-5249657094", "aenderungshinweis": "LandNutzen beschränkt den WFS auf veröffentlichte Altlasten, schließt den Status „Altlast vorgeschlagen“ aus und prüft nur den Adresspunkt.", "modellhinweis": "Der technische Abrufzeitpunkt ist kein fachlicher Datenstand. Die Punktprüfung ist ein Screening und keine Altlastenfreiheitsbestätigung." } ], "confidence": { "overall": "hoch", "notes": ["SPARTACUS v3 · Datenstand 2025 · 1-km-Raster (regionaler Klimaindikator)"], "poc_min_layers_met": true, "cost_bandwidth_default_pct": 20 }, "disclaimer": { "text": "Dieser Report ist eine unverbindliche Ersteinschätzung des Nutzungs-Potenzials. Er ersetzt keine rechtsverbindliche Vermessung, keine Sachverständigenbewertung und keine Detailkalkulation. Alle Werte sind Grob-Schätzungen mit angegebener Bandbreite.", "opt_in_required": true, "opt_in_recorded_at": "2026-05-16T09:13:55Z", "bausteine": { "widmung_nicht_rechtsverbindlich": "Widmungsangaben sind nur Indikation, keine rechtsverbindliche Auskunft der Gemeinde.", "alsag_keine_freiheitsgarantie": "Nicht-Listung im Altlastenkataster garantiert keine Schadstofffreiheit.", "bev_keine_vermessung": "Errechnete Flächen/Szenarien ersetzen keine rechtsverbindliche Vermessung.", "cost_keine_sachverstaendigenbewertung": "Grob-Kostenschätzung nach ÖNORM B 1802-1, keine Sachverständigenbewertung im Sinne der GewO.", "grundbuch_stichtag": "Grundbuch-Daten Stand 2026-04-02. Spätere Änderungen können bestehen." } }, "narrative": { "summary": "Die Liegenschaft in 2230 Gänserndorf liegt in Bauland-Wohngebiet ohne harte Restriktionen. Drei Nutzungsszenarien wurden geprüft: eine PV-Freiflächen-Anlage (empfohlen, ca. 6.100 EUR Jahresertrag), eine Wohn-Sanierung des Bestands (rund 290.000 EUR) sowie eine Lagernutzung (widmungsrechtlich ausgeschlossen). Die Eigentumsverhältnisse (zwei Miteigentümer) erlauben eine Aktivierung mit überschaubarem Koordinationsaufwand.", "empfehlung": "Kurzfristig größter Hebel: PV-Freiflächen-Anlage mit KPC-Photovoltaik-Förderung als niederschwelliger Einstieg, parallel Klärung der Hypothek für ein späteres Sanierungsszenario.", "modell": "anthropic/claude-opus-4.7", "guard_passed": true } } ```
18. Offene Contract- und Fachentscheidungen
- Harte Mindestanforderungen + Cost-Bausteine pro Nutzungsklasse (Wohnen/Gewerbe/PV/Co-Working/Lager/Tourismus) — dieser Spec fixiert nur die Struktur (ADR-0015 Säule 3, POC Woche 3).
- Ampel-Schwellen: Welche weichen Faktoren kippen `gruen`→`gelb`, welche sind harte `rot`-Ausschlüsse je Klasse?
- Tier-/Billing-Modell:
organizations.tier,standard-Zuordnung, Preise und Produktrechte sind noch nicht implementiert; das provisorische Org-Type-Mapping ist keine Abrechnungszusage. - Narrativ-Zahlenschutz: Soll der im Prompt gesetzte Zahlenvertrag um einen tatsächlichen strukturellen Zahlen-Gegencheck und klaren Fail-closed-/Retry-Vertrag ergänzt werden?
- Grundbuch: Wenn dieses historische Ziel wieder aufgenommen wird, müssen Rechtsgrundlage, Klarnamen-/Rollenmodell, Provider/AVV, Retention und ein neuer Contract gemeinsam entschieden werden.
- Förder-Katalog-Pflege: Welche Programme im MVP-Katalog, wer validiert quartalsweise die Beträge/Sätze (DATA-SOURCES §Förder).
- PV-Darstellung #562: Variante A; spezifischer Standortertrag ohne automatische Anlagen-/Eurohochrechnung. Fachliche Eignungsfreigabe bleibt #291.
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.