mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-07-19 17:03:02 +00:00
Pin the resolution reached in the grill: planning status persists as a per-UPRN write-through cache in the existing `property_details_spatial` table (not FE-property columns), read back off the Property in Modelling; unknown UPRN defaults to unrestricted, matching legacy `empty_spatial_df` (superseding the earlier "conservative stance" note). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
27 lines
4.3 KiB
Markdown
27 lines
4.3 KiB
Markdown
# Conservation / Listed / Heritage as distinct Property attributes
|
|
|
|
Wall Insulation Eligibility (ADR-0019) — and later Solar-PV and Windows generators — gate on a Property's planning status. That status is **not** on the EPC and **not** in the OS Open-UPRN parquet the geospatial Repo reads today (it only yields coordinates); legacy derives it by spatial-joining separate conservation/listed/heritage layers (`OpenUprnClient.set_spatial_data` → `property_spatial` table). We decided to model it as **three distinct boolean Property attributes** — `in_conservation_area`, `is_listed`, `is_heritage` — resolved through the **geospatial layer** during Ingestion and read off the **Property** (not the EPC), because they describe the building's location/protection, not its energy fabric.
|
|
|
|
## Decision
|
|
|
|
- Keep the three flags **separate**, not legacy's collapsed `restricted_measures` boolean — they gate different Measure Options (conservation blocks EWI only; listed/heritage block both — ADR-0019).
|
|
- Surface them via the geospatial Repo (a `GeospatialRepository` method returning a planning-status record, alongside `coordinates_for`), persist onto the Property in Ingestion, and pass them into the generator as an explicit input.
|
|
- Build this as the **final integrating slice** of the solid-wall feature (build order A), after the calculator mechanics and generator are pinned — it also unlocks the PV/Windows gates.
|
|
|
|
## Source (decided)
|
|
|
|
The conservation/listed/heritage flags are **co-located with longitude/latitude in the same S3 partition** the geospatial Repo already reads — so we **extend the existing `GeospatialS3Repository`** to surface those extra columns alongside the coordinates, rather than porting a separate spatial-join or reading the legacy `property_spatial` table. A further deep-dive into the exact S3 columns/shape precedes slice 3.
|
|
|
|
## Persistence (decided, slice 3c)
|
|
|
|
The OS Open-UPRN reference set is **tens of millions of rows** — too large to host in Postgres — so it lives in S3 and is resolved on demand. The flags must nonetheless reach the front-end (the FE displays them on the `property_details_spatial` view so a user can see *why* a Property did or did not get a given measure), and the FE reads Postgres, not S3. So Ingestion follows a **write-through cache**: fetch the spatial reference row from S3, use the coordinates to drive the Solar fetch, and **persist the whole row (coordinates + three flags) into the existing `property_details_spatial` table, keyed by UPRN** (one shared row per UPRN, not per Property — `uprn` is unique; cf. legacy `bulk_upsert_property_spatial`'s `on_conflict_do_update`). Modelling reads the flags back **off the Property** (`PropertyPostgresRepository` hydrates `Property.planning_restrictions` from that table by UPRN) — the stage boundary stays repo-mediated (ADR-0011); Modelling never touches S3.
|
|
|
|
This is the resolution of the earlier "persist onto the Property" wording: the flags are a Property *attribute* in the domain sense (an enrichment hydrated into the aggregate, exactly like the EPC is hydrated from `epc_property`), persisted in a **backend-written reference table**, not as columns on the FE-owned `property` row.
|
|
|
|
**Unknown default — resolved to *unrestricted* (allow EWI).** When S3 has no row for a UPRN (or the cache has none yet), the Property hydrates to `PlanningRestrictions()` — all flags `False`. This matches what legacy actually does (`OpenUprnClient.empty_spatial_df` sets all three flags `False`; the nearest-UPRN proxy keeps the flags and only nulls coordinates), so the Consequences note below — which read legacy as conservatively *blocking* on missing data — was mistaken about legacy and is superseded: we do **not** suppress a valid measure on absent evidence. UPRN is now a **required** identifier (it stitches the datasets together), so "Property with no UPRN" is an edge handled by the same unrestricted fallback rather than a designed-for case.
|
|
|
|
## Consequences
|
|
|
|
- Generators that need planning status take it as an input or read it off the Property; it never lands on `EpcPropertyData`.
|
|
- Until this slice ships, the ADR-0019 gate defaults to "unrestricted" (offline only).
|
|
- Mirrors legacy's conservative stance where missing data implied restriction — the source slice should decide the "unknown" default explicitly (block EWI vs allow) rather than silently allowing.
|