Report-Spezifikation — JSON-Contract
Dieses Dokument definiert den vollständigen Report-JSON-Contract von LandNutzen. Ziel: ein Entwickler kann danach die deterministische Szenario-/Cost-Engine, den LLM-Prompt und die beiden PDF-Templates (React-PDF Standard, Puppeteer Premium) bauen, ohne raten zu müssen.
Querverweise: PRODUCT.md · ARCHITECTURE.md · ADR-0015 Kostenschätzung · ADR-0013 PDF · ADR-0006 Auth/Multitenant · ADR-0016 Grundbuch · ADR-0014 HORA-Pass · DATA-SOURCES.md · POC-PLAN.md
1. Grundprinzipien
1.1 Trennung Engine ↔ LLM (verbindlich)
Konsistent mit ADR-0015:
``` [Deterministische Engine berechnet ALLE Zahlen + Ampeln + Scores] ↓ [strukturiertes Report-JSON (dieser Contract)] ↓ [LLM erhält Report-JSON + Quellenliste + Schreib-Anweisung] ↓ [LLM rendert NUR narrative Felder, zitiert Engine-Werte 1:1] ↓ [Zahlen-Konsistenz-Guard: Regex auf €/kWh/m² → Abgleich mit JSON] ```
Der LLM erfindet, rechnet oder rundet keine Zahlen. Er befüllt ausschließlich Felder, die unten als Herkunft `llm` markiert sind, und darf darin keine Zahl nennen, die nicht 1:1 aus einem `engine`-Feld stammt. Bei Diskrepanz: Auto-Retry mit härterer Anweisung, sonst Fallback auf Template-Render (statischer Satzbaustein).
1.2 Herkunfts-Klassen pro Feld
| Herkunft | Bedeutung |
|---|---|
| `engine` | Deterministische Regel-/Geo-/Cost-Engine. Reproduzierbar bei gleichem Input. Nie LLM. |
| `llm` | LLM-narrativer Text (Sonnet 4.6 Standard / Opus 4.7 Premium). Keine neuen Zahlen. |
| `static` | Konstante aus Konfig/Recht (Disclaimer-Boilerplate, Lizenz-Strings, Tarif-Mapping). |
| `external` | 1:1 von externer Quelle übernommen (BEV-Geometrie-Ref, HORA-Pass-PDF-Key). |
1.3 Determinismus & Caching
Gleicher `(parcel_id, region, report_tier, engine_version, config_version)` → identische `engine`/`external`-Felder. `llm`-Felder dürfen variieren (Temperatur > 0), müssen aber den Zahlen-Guard bestehen. `meta.as_of` und `meta.generated_at` machen jeden Report stichtagsfähig.
2. Top-Level-Schema
```jsonc { "schema_version": "1.0.0", // SemVer dieses Contracts; static "report_id": "uuid", // engine "meta": { ... }, // §3 "parcel": { ... }, // §4 "steckbrief": [ { ... } ], // Ampel-Übersicht aller Indikatoren (Aggregat; engine) "standort_bilanz": { ... } | null,// Risiko-Rollup aus dem Steckbrief (Szenario-Kontext; engine) "widmung": { ... }, // §5 "risiko": { ... }, // §6 "bestand": { ... }, // §7 "grundbuch": { ... } | null, // §8 — nur Premium, sonst null "scenarios": [ { ... } ], // §9 — 3..5 Elemente "foerderungen": [ { ... } ], // §10 "sources": [ { ... } ], // §11 — Attribution-Pflicht "confidence": { ... }, // §12 "disclaimer": { ... }, // §13 "narrative": { ... } // §14 — die einzigen rein-llm-Blöcke } ```
Pflicht-Top-Level-Keys für alle Tiers: `schema_version`, `report_id`, `meta`, `parcel`, `widmung`, `risiko`, `bestand`, `scenarios`, `foerderungen`, `sources`, `confidence`, `disclaimer`, `narrative`. `grundbuch` ist immer vorhanden, aber `null` außerhalb des Premium-Pfads (siehe §8 und §15).
`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, Magnus 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 (roh). Wird NICHT an LLM gegeben (PII-Minimierung, ARCHITECTURE §Compliance). |
| `meta.address.normalized` | string | ja | engine | Normalisierte Adresse (Geocoder-Output). |
| `meta.coordinate.lat` | number | ja | engine | WGS84, Geocoding (Nominatim/BEV). |
| `meta.coordinate.lon` | number | ja | engine | WGS84. |
| `meta.coordinate.source` | enum `nominatim`|`bev` | ja | engine | Geocoder. |
| `meta.region.slug` | string | ja | engine | z. B. `at-noe` (ADR-0006 Region-Achse). |
| `meta.region.name` | string | ja | static | „Niederösterreich". |
| `meta.report_tier` | enum `free`|`standard`|`premium`|`planer` | ja | engine | Steuert Sektions-Sichtbarkeit (§15). `free`=anonym, `standard`=registriert, `premium`=490-€-Beratung, `planer`=Abo-Export. |
| `meta.org_id` | int | null | ja | engine | Org-Kontext (RLS, ADR-0006). `null` bei anonym (`_public`-Org). |
| `meta.requested_by` | enum `anonymous`|`user`|`platform_admin_impersonating` | ja | engine | Audit-Hinweis (ADR-0006/0007). |
| `meta.as_of` | ISO-8601 date | ja | engine | Fachlicher Daten-Stichtag (max. Alter aller verwendeten Layer). |
| `meta.generated_at` | ISO-8601 datetime | ja | engine | Generierungs-Zeitpunkt. |
| `meta.engine_version` | string | ja | static | Version der Szenario-/Cost-Engine. |
| `meta.config_version` | string | ja | static | Version `bodenwerte.json` + `herstellungskosten.json` + BPI-Stand. |
| `meta.degraded` | object[] | ja | engine | Liste ausgefallener Layer mit Defensive-Default-Hinweis (ARCHITECTURE §Failure-Modi). Leer wenn alles ok. |
| `meta.timings` | object[] | optional | engine | Diagnostische Layer- und Phasenlatenzen, absteigend sortiert. Enthält Einzel-Layer sowie `layers_total`, `scenario_engine`, tierabhängig `narrative` und `report_total`. Werte überlappen und dürfen nicht summiert werden. Nicht deterministisch — kein Teil der fachlichen Aussage. |
| `meta.report_id` | string | optional | engine | Permalink-ID, falls der Report persistiert wurde (nur POST, ADR-0022). Fehlt bei fail-open-Persistenzfehler. |
| `meta.permalink` | string | optional | engine | Teilbarer Pfad `/r/{report_id}`, falls persistiert. |
| `meta.saved` | bool | optional | engine | `true`, wenn der Report einem Konto fest zugeordnet ist (eingeloggt erstellt oder per `?claim=1` beansprucht, Migration 0021) → kein 90-Tage-Ablauf. Fehlend/`false` = anonym (90-Tage-Retention) oder nicht persistiert. Steuert „gespeichert"-/Registrierungs-Nudge im UI. |
`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 — Magnus-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); 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
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.
Die Report-PDF verwendet ausschließlich pdf_parcel_outline. Nur ein
bestätigter hit zeichnet die Parzellenoutline; 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: NÖGIS Widmung, wöchentlich gespiegelt, `ST_Intersects`.
| Feld | Typ | Pflicht | Herkunft | Beschreibung |
|---|---|---|---|---|
| `widmung.klasse` | enum `bauland`|`gruenland`|`industrie`|`sonder`|`gemischt`|`unbekannt` | ja | engine | Aggregierte Hauptklasse. |
| `widmung.detail` | string | optional | external | Original-Widmungstext NÖGIS. |
| `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.restriktionen` | string[] | ja | engine | Harte Einschränkungen aus Zusatz-Layern (NÖGIS Wasserrecht Schutz-/Schongebiet, Lärminfo-Klasse). Leer = keine. |
| `widmung.rechtsverbindlich` | boolean | ja | static | Immer `false` — nur Indikation, keine rechtsverbindliche Auskunft (DATA-SOURCES §NÖGIS). |
| `widmung.quelle_stand` | ISO-8601 date | ja | engine | Stand der Widmung (Mirror-Sync bzw. LAND_DATUM aus dem Live-Layer). |
| `widmung.source` | enum `dkm_mirror`|`wms` | optional | engine | Herkunft: dkm_mirror = exakte gespiegelte Geometrie (PostGIS); wms = NÖ-weiter Live-Fallback über RRU_WI_HUELLE (WMS-GetFeatureInfo), greift bei Mirror-Lücke/DB-Ausfall — gleiche Klassifikation, keine exakte Fläche. |
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.hora_pass.r2_key` | string | null | optional | external | R2-Key des HORA-Pass-PDF (ADR-0014). `null` wenn Layer aus = Lizenz nicht geklärt. |
| `risiko.hora_pass.gefahren` | object | ja | external | Map der 10 HORA-Gefahren → Stufe (`keine`/`niedrig`/`mittel`/`hoch`): `hochwasser`, `oberflaechenabfluss`, `lawine`, `erdbeben`, `rutschung`, `windspitzen`, `blitzdichte`, `hagel`, `schneelast`, `hitzeepisoden`. Aus Pfad-A-Parse; leeres Objekt wenn Pass nicht abrufbar. |
| `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.geogen.rutschung` | enum `keine`|`vorbegutachtung`|`erkundung`|`unbekannt` | ja | engine | NÖGIS Geogene Gefahren `ST_Intersects` (Hanglagen). |
| `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). `null` bei PVGIS-Ausfall → PV-Szenario nutzt die Konfig-Pauschale. |
| `solar.spez_ertrag_kwh_pro_kwp` | number | ja | external | PVGIS spez. Jahresertrag [kWh/kWp] bei optimaler Ausrichtung (AC); Engine-Override der Pauschale `1140`. |
| `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) | external | Dachgenaue Google-Anreicherung (Building Insights). DC-Werte, Live-Overlay — vor Persistenz entfernt (`stripGoogleSolar`, ToS R-07); auf Permalinks abwesend. `null` = keine Coverage / Free-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`. |
| `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 | Klassen-Freitext der Quelle (z. B. „≥ 1 Gbit/s"). |
| `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 von Magnus 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.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; Magnus-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 Magnus-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.
7. `bestand` — Gebäude-Footprint
Quelle: Microsoft Building Footprints (Bulk, statisch) + optional Copernicus Versiegelung / Sentinel-2 NDVI (Phase 2).
| Feld | Typ | Pflicht | Herkunft | Beschreibung |
|---|---|---|---|---|
| `bestand.bebaut` | boolean | ja | engine | Ob ein Footprint die Parzelle schneidet. |
| `bestand.gebaeude_anzahl` | int | ja | engine | Anzahl geschnittener Footprints. |
| `bestand.footprint_flaeche_m2` | number | ja | engine | Summe Footprint-Fläche auf der Parzelle. |
| `bestand.versiegelungsgrad_pct` | number | null | optional | engine | Copernicus (Phase 2), sonst `null`. |
| `bestand.ndvi_index` | number | null | optional | engine | Sentinel-2 „überwuchert?" (Phase 2), sonst `null`. |
| `bestand.footprint_stand` | ISO-8601 date | ja | engine | Datensatz-Stand (statischer Bulk). |
8. `grundbuch` — nur Premium (ADR-0016)
`null` für `free`/`standard`. Nur befüllt im `premium`-Pfad nach explizitem User-Trigger („Eigentumsverhältnisse abfragen") oder durch Magnus' Beratungsvorbereitung. Alle Personen pseudonymisiert — Klarnamen nie im Report-JSON, nie an LLM (ADR-0016 §Pseudonymisierung).
| Feld | Typ | Pflicht | Herkunft | Beschreibung |
|---|---|---|---|---|
| `grundbuch.bezirksgericht` | string | ja | external | BG aus JustizOnline-Auszug. |
| `grundbuch.katastralgemeinde` | string | ja | external | KG-Code + Name. |
| `grundbuch.einlagezahl` | string | ja | external | EZ. |
| `grundbuch.as_of` | ISO-8601 datetime | ja | external | Stichtag des Auszugs (max. 90 Tage Cache, ADR-0016). |
| `grundbuch.eigentum.art` | string | ja | external | z. B. „Schlichtes Miteigentum". |
| `grundbuch.eigentum.personen[]` | object[] | ja | engine | `{ anteil, name:"[PSEUDONYMISIERT]", wohnort_grob }` — `wohnort_grob` nur Land/Bundesland-Granularität. |
| `grundbuch.eigentum.aktivierungs_komplexitaet` | enum `gering`|`mittel`|`hoch` | ja | engine | Aus B-Blatt abgeleitet. |
| `grundbuch.belastungen[]` | object[] | ja | engine | `{ art, berechtigter:"[PSEUDONYMISIERT]", impact }` — C-Blatt. |
| `grundbuch.dienstbarkeiten_zugunsten[]` | object[] | ja | external | C-Blatt, GSt-Referenzen (keine Personen). |
| `grundbuch.dienstbarkeiten_zulasten[]` | object[] | ja | external | C-Blatt. |
| `grundbuch.score.aktivierungs_faehigkeit` | enum `einfach`|`mittel`|`schwierig`|`sehr_schwierig` | ja | engine | Deterministische Score-Tabelle (ADR-0016, Magnus-kalibrierte Gewichte). Kein LLM. |
| `grundbuch.score.punktwert` | number | ja | engine | Roh-Score (Bandbreiten: +5..+3 einfach … <−3 sehr_schwierig). |
| `grundbuch.score.kommentar` | string | ja | engine | Regelbasierter Kurztext (z. B. realistischer Vorbereitungszeitraum). |
| `grundbuch.original_r2_key` | string | ja | external | R2-Key des Original-PDF (Zugriff nur Auftraggeber, separater Auth-Check). |
| `grundbuch.disclaimer` | string | ja | static | „Stand: {as_of}. Spätere Änderungen können bestehen. Verbindliche Auskunft nur via aktuellem Auszug." |
Offene Frage / Annahme: ADR-0016 fragt, ob eine eigene Rolle „Auftraggeber-mit-Namenseinsicht" nötig ist. Annahme dieses Specs: Klarnamen werden NIE in den Report-JSON-Contract aufgenommen; ein separater, nicht hier spezifizierter Endpoint liefert das Original-PDF an den authentifizierten Auftraggeber. Der Report bleibt damit durchgehend pseudonym → für Magnus' Workshop zu bestätigen.
9. `scenarios[]` — 3 bis 5 Nutzungsszenarien
Mindestens 3, maximal 5 Elemente (PRODUCT §User-Flow Punkt 3). Die Ampel-Bewertung und der gesamte `cost`-Block sind deterministisch (ADR-0015). Der `cost`-Block übernimmt das Schema aus ADR-0015 exakt und unverändert (`scenario`, `cost`, `cost_components`, `methodology`, `as_of_date`, `disclaimer`).
| 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). |
| `ausschluss_grund` | string | null | wenn `rot` | engine | Harte Mindestanforderung verletzt (z. B. Mindestfläche, Schutzgebiet). |
| `kennzahlen` | object | optional | engine | Szenario-spezifische deterministische Kennzahlen. PV-Beispiel: `{ "kwp": 20, "ertrag_kwh_jahr": 22800, "ertrag_eur_jahr": 6100, "strompreis_ct_kwh": 26.8 }`. Alle aus Engine, nie LLM. |
| `cost` | object | ja | engine | Exakt ADR-0015-Schema: `{ low, expected, high, unit:"EUR", bandwidth_pct }`. |
| `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`). |
| `narrativ` | string | nur `standard`/`premium` | llm | 1–3 Absätze Erklärung. Darf nur Zahlen aus `cost`/`kennzahlen` 1:1 zitieren. In `free` vollständig weggelassen; `planer` bleibt laut §15 tabellarisch. |
Offene Frage / Annahme: ADR-0015 listet Nutzungsklassen Wohnen/Gewerbe/PV/Co-Working/Lager/Tourismus, die exakten harten Mindestanforderungen + Cost-Bausteine pro Klasse liefert Magnus' POC- Woche-3-Workshop. Dieser Spec fixiert nur Struktur, nicht die Werte → Workshop-Input erforderlich.
10. `foerderungen[]` — RAG-Matching
Quelle: Förder-RAG (KPC/LEADER/KMU.DIGITAL/aws-Richtlinien als PDFs in pgvector). Das Matching (welche Förderung passt) ist `classification`-LLM (gpt-4o-mini/gemini-flash) mit striktem Tool-Output — Beträge/Sätze sind NICHT generiert, sondern aus dem kuratierten Förder-Katalog (Konfig) gezogen.
| Feld | Typ | Pflicht | Herkunft | Beschreibung |
|---|---|---|---|---|
| `foerder_id` | string | ja | engine | Stabiler Slug, referenziert von `scenarios[].foerder_trigger`. |
| `programm` | string | ja | static | z. B. „KPC Flächenrecycling". |
| `traeger` | string | ja | static | KPC / LEADER / KMU.DIGITAL / aws. |
| `max_betrag_eur` | number | null | ja | static | Aus Förder-Katalog (z. B. 60000). Nie LLM. |
| `max_satz_pct` | number | null | ja | static | z. B. 75. |
| `bedingung` | string | ja | static | Förderbedingung aus Katalog. |
| `match_score` | number | ja | engine | RAG-Relevanz 0..1. |
| `match_begruendung` | string | nur `standard`+ | llm | Warum passend — nur qualitativ, keine neuen Zahlen. |
| `quelle` | string | ja | static | Richtlinien-Dokument + Stand (quartalsweise validiert). |
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.
Ein Report muss ≥ 4 `sources` (BEV, NÖGIS-Widmung, GeoSphere, ALSAG) enthalten, sonst gilt das POC-Mindest-Datenlayer-Kriterium als verfehlt → `confidence.poc_min_layers_met = false`.
12. `confidence` — Unsicherheitshinweise
| Feld | Typ | Pflicht | Herkunft | Beschreibung |
|---|---|---|---|---|
| `confidence.overall` | enum `hoch`|`mittel`|`niedrig` | ja | engine | Aggregierte Konfidenz (fehlende Layer / Cache-Alter / `degraded`). |
| `confidence.notes` | string[] | ja | engine | Konkrete Hinweise (z. B. „SPARTACUS v3 · Datenstand 2025 · 1-km-Raster", „HORA-Layer aus — Lizenz nicht geklärt"). |
| `confidence.poc_min_layers_met` | boolean | ja | engine | ≥ 4 Pflicht-Datenlayer sichtbar verknüpft (POC-Kriterium A). |
| `confidence.cost_bandwidth_default_pct` | number | ja | static | Standard-Unsicherheit Cost (±20 %, ADR-0015 Säule 1). |
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: `widmung_nicht_rechtsverbindlich`, `alsag_keine_freiheitsgarantie`, `bev_keine_vermessung`, `cost_keine_sachverstaendigenbewertung`, `radon_indikator_nicht_messung` (nur wenn `risiko.radon` gesetzt), `breitband_indikator` (nur wenn `breitband` gesetzt), `laerm_indikator` (nur wenn `laerm` gesetzt), `oev_indikator` (nur wenn `oev` gesetzt), `hochwasser_indikator` (nur wenn `risiko.hochwasser` gesetzt), `schutzgebiete_indikator` (nur wenn `schutzgebiete` gesetzt), `denkmal_indikator` (nur wenn `denkmal` gesetzt), `wlv_indikator` (nur wenn `wlv` gesetzt), `pendeln_indikator` (nur wenn `pendeln` gesetzt), `demografie_indikator` (nur wenn `demografie` gesetzt), `boden_indikator` (nur wenn `boden` gesetzt), `bahn_indikator` (nur wenn `bahn` gesetzt), `erdbeben_indikator` (nur wenn `erdbeben` gesetzt), `lcz_indikator` (nur wenn `lcz` gesetzt), `grundbuch_stichtag` (nur Premium). |
14. `narrative` — die einzigen reinen LLM-Blöcke
| Feld | Typ | Pflicht | Herkunft | Beschreibung |
|---|---|---|---|---|
| `narrative.summary` | string | nur `standard`+ | llm | Executive-Summary (3–5 Sätze). Keine Zahl, die nicht in `engine`-Feldern steht. |
| `narrative.empfehlung` | string | nur `premium`/`planer` | llm | Handlungsempfehlung (Opus 4.7). |
| `narrative.modell` | string | ja | engine | Welches Modell den Narrativ erzeugt hat (`anthropic/claude-sonnet-4.6` etc.). Audit/Cost. |
| `narrative.guard_passed` | boolean | ja | engine | Zahlen-Konsistenz-Guard bestanden (ADR-0015). `false` → Fallback-Template wurde gerendert. |
Im `free`-Tier ist `narrative.summary` ein gekürzter Template-Satz (kein LLM-Call → Cost-Cap, POC LLM-Sub-Ziel < $0,10/Report), `narrative.modell = "template"`.
15. Free/anonym vs. Premium 490 € — Sektions-Matrix
| Sektion | `free` (anonym) | `standard` (registriert) | `premium` (490 €) | `planer` (Abo) |
|---|---|---|---|---|
| `meta`, `parcel`, `widmung` | ✅ | ✅ | ✅ | ✅ |
| `risiko` (Klima/ALSAG/Hochwasser-Stufe) | ✅ Kurz | ✅ Voll | ✅ Voll | ✅ Voll |
| `risiko.hora_pass.r2_key` (PDF-Beilage) | ❌ | ❌ | ✅ | ✅ |
| `bestand` | ✅ Basis | ✅ | ✅ + Versiegelung/NDVI | ✅ + Versiegelung/NDVI |
| `scenarios[]` | 6, ohne `narrativ` | 6 + `narrativ` | 6 + `narrativ` + Empfehlung | 6, tabellarisch |
| `scenarios[].cost_components` | nur Summe | ✅ Detail | ✅ Detail | ✅ Detail |
| `foerderungen[]` | Top 1, ohne Begründung | ✅ + Begründung | ✅ + Begründung | ✅ |
| `grundbuch` | `null` | `null` | ✅ (nach Trigger) | `null` (es sei denn explizit) |
| `narrative.summary` | Template | LLM (Sonnet) | LLM (Opus) | LLM (Sonnet) |
| `narrative.empfehlung` | ❌ | ❌ | ✅ (Opus) | ❌ |
| `sources`, `confidence`, `disclaimer` | ✅ | ✅ | ✅ | ✅ |
Die Report-Quota ist vom dargestellten Produkttier getrennt (Report-Quota-Vertrag):
- anonym: 10 Reports/Tag pro pseudonymem Request-IP-Bucket;
- bestehende, sicher aufgelöste Kontositzung: 10 Reports/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 (ADR-0013)
| Tier | PDF-Pfad | Tool | Sektionen im PDF |
|---|---|---|---|
| `free` / `standard` | Standard-Report-PDF | `@react-pdf/renderer` | `meta`, Karte (pre-rendered PNG), `parcel`, `widmung`, `risiko` (kompakt), `bestand`, `scenarios` (Karten-Layout, Cost-Summe), Top-`foerderungen`, `sources`, `disclaimer`. Map als `(parcel_id, zoom, layer_preset)`-PNG-Cache. |
| `planer` | Planer-Export-PDF | `@react-pdf/renderer` | Wie Standard, aber tabellen-lastig, alle `cost_components`, weniger Narrativ, hohe Volumina. |
| `premium` (490 €) | Beratungs-Report-PDF | Puppeteer (Fluid Compute) | Alle Sektionen inkl. `grundbuch`, `narrative.empfehlung`, volle `cost_components`, interaktiv gerenderte MapLibre-Karte. HORA-Pass-PDF (`risiko.hora_pass.r2_key`) als letzte 2 Seiten angehängt (ADR-0014 Variante α — Attribution via BMLUK-Footer automatisch erfüllt). |
Felder mit Herkunft `external`/`engine` werden in beide Templates 1:1 übernommen; `llm`-Felder nur wenn `narrative.guard_passed = true`, sonst Template-Fallback.
17. Vollständiges Beispiel-JSON (fiktive NÖ-Adresse, `premium`)
Fiktive Liegenschaft, Pilotregion NÖ. Enthält das POC-PV-Szenario mit konkreter Euro-Zahl. Der Klimablock verwendet den separat rückgelesenen SPARTACUS-v3-Referenzwert für die angeführte Gänserndorf-Rasterzelle; die übrigen Felder bleiben illustrative Beispieldaten.
```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 Fragen für Magnus' Domain-Workshop
- 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?
- Grundbuch-Klarnamen-Auth: Bestätigung der Annahme in §8 — Klarnamen nie im Report-JSON, separater Auftraggeber-Endpoint (ADR-0016 offene Frage).
- Förder-Katalog-Pflege: Welche Programme im MVP-Katalog, wer validiert quartalsweise die Beträge/Sätze (DATA-SOURCES §Förder).
- PV-Referenz-Annahmen: kWp→kWh-Faktor und Strompreis für die POC-Euro-Zahl — Engine-Konstante oder regionsabhängig?