mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-07-22 08:48:38 +00:00
Resolve the slice-5 design tree (grill-with-docs): estimation runs in Ingestion (refines ADR-0029 dec-3; drops the #1227 "shift to Modelling" — no surviving rationale, and stages communicate only via persisted state); predicted EPC is persisted in a DISTINCT slot (EPC table + source discriminator) so lodged + predicted coexist (enables EPC Anomaly Flags); provenance is structural (the slot), not a field on EpcPropertyData; effective_epc/source_path gain a "predicted" branch; slice-5 is gap-fill only; property_type is a REQUIRED input (hard cohort filter) from Landlord Overrides, and Properties with unknown type are gated out (no national defaults). OS postcode_search as a broader type source is a noted follow-on. CONTEXT EPC Prediction entry gains the gating rule. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
87 lines
5.1 KiB
Markdown
87 lines
5.1 KiB
Markdown
# EPC Prediction production wiring
|
||
|
||
ADR-0029 settled *how* EPC Prediction synthesises a Property's `EpcPropertyData`
|
||
from its **Comparable Properties**; this records *how it wires into the running
|
||
pipeline* — where estimation runs, how a predicted EPC is stored and told apart
|
||
from a lodged one, and which Properties are eligible. Resolved in a
|
||
grill-with-docs session after the algorithm + validation harness were built and
|
||
the accuracy backlog (#1222–1228) closed.
|
||
|
||
## Status
|
||
|
||
Accepted (design). Refines ADR-0029 decision 3. Implementation pending (slice 5).
|
||
|
||
## Decisions
|
||
|
||
### 1. Estimation runs in Ingestion — the #1227 "shift to Modelling" is dropped
|
||
|
||
The cohort fetch + predict happens in `IngestionOrchestrator`, when
|
||
`epc_fetcher.get_by_uprn` returns `None` — upholding ADR-0029 decision 3. A
|
||
design note from issue #1227 had proposed moving estimation (and its distance
|
||
calcs) into `ModellingOrchestrator`; that is reversed here. Ingestion is already
|
||
the EPC-acquisition phase and *already resolves the Property's coordinates*
|
||
(`spatial`), so it can run the geo-weighted prediction with no new IO surface;
|
||
the First Run stages communicate **only through persisted state** (the pipeline
|
||
threads just `property_ids`, each stage reloads the `Property`), so a prediction
|
||
produced in Modelling would either have to be persisted there anyway or
|
||
recomputed every run. No rationale for the Modelling shift survived review.
|
||
Baseline and Modelling stay untouched — they read a populated `effective_epc`.
|
||
|
||
### 2. The predicted EPC is persisted in a distinct slot, never overwriting the lodged one
|
||
|
||
Because stages communicate via persisted state, the prediction **must be saved**
|
||
for Modelling to see it — in-memory-only would never reach stage 3. It is stored
|
||
as a **distinct predicted-EPC slot** on the Property (the EPC table reused with a
|
||
`source` discriminator — `lodged` / `predicted`), so a lodged EPC and a predicted
|
||
EPC can **coexist** on one Property. Coexistence is load-bearing: it is what lets
|
||
the same cohort machinery produce **EPC Anomaly Flags** for Properties that *do*
|
||
have an EPC (the dual-use named in ADR-0029), and it means a later real-EPC fetch
|
||
fills its own slot without the predicted one muddying provenance. Rejected: a
|
||
single EPC slot with an `is_predicted` flag — it cannot hold both, so it forecloses
|
||
anomaly detection and makes "lodged later arrives" ambiguous.
|
||
|
||
### 3. Provenance is structural — on the Property, not on `EpcPropertyData`
|
||
|
||
`EpcPropertyData` gains no `predicted` / `source` field. Which slot the picture
|
||
came from *is* its provenance. `Property.effective_epc` / `source_path` gain a
|
||
`"predicted"` branch, used only when there is no lodged EPC **and** no Site Notes
|
||
(the existing precedence is unchanged; a real source always wins). The
|
||
**Validation Cohort** then excludes any Property whose `effective_epc` resolves
|
||
via the predicted slot — it has no same-spec lodged ground truth — and the UI
|
||
flags it as predicted. Keeping `EpcPropertyData` clean means every downstream
|
||
consumer (calculator, generators, bill derivation) is unchanged and oblivious to
|
||
how the picture was sourced, exactly as for Landlord-Override and Reduced-Field
|
||
pictures.
|
||
|
||
### 4. Slice 5 is gap-fill only; always-predict (anomaly) is a follow-on
|
||
|
||
Prediction runs only when `epc is None`. Predicting for *every* Property to
|
||
compare against its lodged EPC (EPC Anomaly Flags) is real and the slot model
|
||
supports it, but it triples the ingestion cohort IO and needs its own
|
||
comparison + divergence-threshold + UI surface — so it does not ride in on the
|
||
wiring slice. The predicted-EPC slot and the `ComparableProperties` repository
|
||
this slice introduces are exactly what the anomaly capability reuses.
|
||
|
||
### 5. A known property type is required — eligibility is gated, never defaulted
|
||
|
||
A `PredictionTarget` needs `postcode` (from `PropertyIdentity`), `coordinates`
|
||
(geospatial), and `property_type` + `built_form` + `wall_construction` from
|
||
**Landlord Overrides**. `property_type` is the **hard** cohort filter (a flat
|
||
must not be sized from houses), so it is a **required input**: a Property whose
|
||
property type is genuinely unknown is **gated out** before prediction — flagged
|
||
un-predictable, never predicted from a mixed-type cohort and never given a
|
||
national default. (An Ordnance Survey `postcode_search` source can supply
|
||
property type more broadly than landlord input does; wiring it is a later
|
||
enhancement that widens the eligible population — out of scope here.) The
|
||
`ComparableProperties` repository port deferred by ADR-0029 is built in this
|
||
slice: it owns the cohort IO (postcode search → per-cert fetch → UPRN→coordinate
|
||
resolution) and returns candidate `Comparable`s for the domain
|
||
`select_comparables` to filter.
|
||
|
||
## Consequences
|
||
|
||
- A schema change: the EPC store gains a `source` discriminator (or equivalent),
|
||
and the Property repository a `get_predicted_for_property` read.
|
||
- Slice 5's reach is bounded to EPC-less Properties with a landlord-supplied
|
||
property type. The `postcode_search` integration is the lever to broaden it.
|
||
- `EpcPropertyData` stays unchanged, so no downstream consumer is touched.
|