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>
5.1 KiB
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 Comparables for the domain
select_comparables to filter.
Consequences
- A schema change: the EPC store gains a
sourcediscriminator (or equivalent), and the Property repository aget_predicted_for_propertyread. - Slice 5's reach is bounded to EPC-less Properties with a landlord-supplied
property type. The
postcode_searchintegration is the lever to broaden it. EpcPropertyDatastays unchanged, so no downstream consumer is touched.