#1444 Parts A+B for codes 191/192, diagnosed on portfolio 796: 30 of the
first 40 live overrides inherit a replaced system's category (6/4/7) —
suppressing HHRSH or billing direct-acting heat as storage.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
WS2 of PRD #1435, diagnosed on property 711795: an electric-underfloor
override inherits the replaced community-heating category (no pathway,
baseline mis-rated 51 vs 67), and wall generators offer £4k insulation
that cannot change an age-band-L wall's derived U (0.280 → 0.280).
Glossary: Wall U-Value Gate.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ADR-0048 records why a system-replacing heating override must emit a
complete companion set — category and control archetype-forced for
coherence, fuel and meter deferred where multiple values are valid, and
an unmapped companion logged rather than silently inherited. Adds the
Heating Companion Set glossary term to CONTEXT.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Per-window assignment from a landlord aggregate is strictly worse than the cert's
existing per-window sap_windows, so a mixed override always defers — the proportion
comparison only fed a 'flag when far' branch not worth a new schema field. Realized
decision: a GlazingType.MIXED sentinel (LLM classifies mixes to it) resolves to no
overlay, preserving the cert glazing; uniform assertions still apply.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ADR-0041: flat roofs ARE scored by insulation thickness via RdSAP Table 16
col (1) ('joists at ceiling level and flat roof'); the RoofType taxonomy lacks
flat-thickness members so the depth is discarded — fix needs new FLAT_*MM members
(FE-owned pgEnum). ADR-0042: a landlord glazing override reconciles against the
cert's per-window sap_windows composition rather than flattening every window to
one type; mixed-and-close defers to the cert, mixed-and-far is flagged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Specifies the ALTER ... DROP NOT NULL on the four lodged_* columns (#1361) and
the ordering constraint: it must land before the backend writes NULL or runs the
backfill, else the predicted-Property INSERT aborts the batch.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Amends ADR-0004's both-halves-always-populated invariant: EPC Prediction
borrows a neighbour's recorded figures, so a predicted Property has no Lodged
Performance — its lodged half is absent (NULL), only the Effective half is
persisted. Sharpens the CONTEXT.md glossary for Lodged / Baseline Performance.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Solid fuel is ambiguous across coal/wood/smokeless/pellets, so the overlay
defaults to house coal (33, the most common) when no main_fuel override is
given; a specific solid-fuel override still wins by last-wins composition.
Updates the ADR-0041 natural-fuel amendment accordingly (every archetype drags
a natural fuel; solid defaults to coal).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Record the natural-fuel drag-along rule the overlay now implements: drag a fuel
only where the archetype is unambiguous (electric/gas), leave ambiguous
solid/oil room heaters to the main_fuel override, let a present main_fuel
override win by last-wins composition, and LOG (not raise) a landlord fuel that
contradicts the archetype's natural fuel.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
grill-with-docs output for PRD #1361 Class A. The band-G baselines are not a
fabric/rebaseline bug (walls are correct — landlord asserted as-built); they are
a target-vocabulary gap in the landlord-description heating classifier. The LLM
maps unbounded free-text onto a closed internal taxonomy, but that taxonomy had
9 archetypes vs ~13 RdSAP families, so it overflowed into "Gas CPSU"/storage
(the "convector" collision sent direct-acting room heaters to off-peak storage
403 → SAP ~10).
- ADR-0041: fix the taxonomy not the mapper; unmapped input -> None (keep lodged
EPC, surface to user), never a forced archetype; heat pumps modellable via a
default Table 4a code (no PCDB id); community heating via codes 301/302/304
with a documented DLF-1.50 / boiler-source default.
- CONTEXT.md: sharpen Landlord-Description Classification and the four distinct
vocabularies (landlord / recognised-internal / lodged / SAP code).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Record the two load-bearing decisions and correct the CONTEXT.md
'Calculated SAP10 Performance' entry: the lodged figure is kept at >=10.2
only for a pristine lodged cert; overrides/prediction adopt the calculator.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Document Slice 2 in the persistence-fidelity migration log, plus the 7
calculator-read nested gaps the recursive guard newly surfaced (tracked
follow-up).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A Landlord heating-system override was applied as a sparse patch, so the
replaced system's fields bled through. A storage flat reclassified as a gas
combi (property 728513) kept mains_gas=False, heating category 7, the 2401
storage charge control, a Dual meter and an electric-immersion cylinder — an
incoherent record that gated out the gas-boiler-upgrade Measure and made the
heating Generator read the dwelling as off-gas (offering HHRSH storage).
Extend the ADR-0035 drag-along to gas boilers (Table 4b 102/104/120): the
overlay now sets the whole coherent companion set — mains_gas, gas main fuel,
heating category 2, fanned flue, full modern controls (2106), a single-rate
meter, and hot water from the main system with the cylinder set from the boiler
type (combi → none, regular/CPSU → cylinder). The main_fuel overlay also flips
mains_gas=True for a "mains gas" fuel. Non-off-peak archetypes now drag an
explicit Single meter so a system switch never leaves a stale Dual.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ADR-0026 sized PV at 0.70 x Google maxArrayPanelsCount, assuming Google's roof
is the dwelling's. Medium-imagery footprint conflation (semi-detached/terraced)
returns the whole building's roof -> oversized arrays (property 742003: 55m2
bungalow -> ~16 kWp -> effective E/49.7 -> A/92.4 on solar alone). ADR-0038
bounds the array to the dwelling's own usable roof (roof_area-based budget,
proximity segment selection anchored on the dwelling), subsuming the legacy
GoogleSolarApi guards the rewrite dropped. CONTEXT: add Footprint Conflation +
Dwelling-Roof Cap, extend Solar Potential (per-segment centre + area).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Captures the calc-facing ventilation-type gap (older RdSAP mappers drop
mechanical_ventilation_kind), the FE-side Main Fuel display, the sweep survivor
clusters, and the predicted-property display path — all separate from ADR-0037.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Captures the grill-with-docs decisions for the full-SAP-17.1 mapper completion:
rebaseline-to-10.2 (restores Rebaselining trigger (a)), calc-affecting fields
coupled with the sap_version flip, and growing Elmhurst-anchored validation.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Resolves the ADR-0014 off-peak deferral: off-peak is a property of the
meter (every electric end use splits day/night by its own High-Rate
Fraction, a calculator output), not a per-end-use fuel. Adds the
Off-Peak Meter and High-Rate Fraction glossary terms.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CONTEXT.md gains Non-Separated / Separated Conservatory glossary terms; ADR-0036
records the mapper-owned §6.1 split + 1:1 persistence + round-trip contract; the
migration note lists the five new epc_property columns for the FE schema repo.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A dwelling's heating is one conceptual system, but its fields are scattered
across EpcPropertyData (a gov-API schema mirror): the cluster on sap_heating, the
electricity tariff on sap_energy_source.meter_type, hot-water flags loose at top
level. Three places synthesise a heating system — Measure Options, Landlord
Overrides, EPC Prediction's donor — and each hand-copied a different ad-hoc
subset. The override and donor both dropped meter_type, so an electric-storage
system landed on the template's single-rate meter and billed overnight heat at
the peak rate: property 713406 scored SAP 13 (G) vs ~50 (E), inflating the HHRSH
measure to +45.8 and overshooting the plan to band A.
Establish a single Coherent Heating System boundary (CONTEXT.md) that every
synthesiser must cover, with a source-appropriate fill policy (ADR-0035):
- Override overlay *completes* the partial system the landlord named. Companion
fields are now DERIVED from the SAP code, not hand-attached per archetype: the
off-peak meter from the calculator's single off-peak classification (new
OFF_PEAK_IMPLYING_HEATING_CODES = SAP §12 Rules 1-2), and an unobserved storage
charge control defaults to the conservative manual control (Table 4e 2401). So
adding a heating archetype is just adding its code — companions can't be
forgotten. A contract test guards it (every off-peak code drags a Dual meter).
- Prediction's heating donor now *carries* the donor's meter_type alongside its
sap_heating cluster — the donor is already coherent.
Coherence is a synthesis-time obligation only; the calculator still scores a real
lodged cert exactly as lodged.
Verified on 713406: baseline 13 -> 47.8 (E), matching its recorded rating; the
phantom HHRSH recommendation is gone and the plan no longer overshoots to A.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Closes out the cohort-broadening work with its decision record and consolidates
the retry plumbing.
ADR-0034 documents broadening the EPC-Prediction cohort to the real unit
postcodes nearest the target (via postcodes.io) when its own postcode holds no
same-type comparable — extending ADR-0031 decision 5. Records why postcodes.io
was chosen over council[] (whole-LA, no property_type in rows), a bulk Code-Point
Open / ONSPD dataset, and the OS Places radius API, and the lazy / nearest-first
early-stop / soft-fail policy. Broadening-specific docstrings now cite 0034.
Retry consolidation: extract the EPC client's call_with_retry into a shared
infrastructure/http_retry.py keyed off a generic TransientHttpError marker, so
the mechanism (exponential backoff, Retry-After) is shared while each client
keeps its own transient policy. EpcRateLimitError now subclasses TransientHttpError
(still an EpcApiError); PostcodesIoClient routes through the same helper, raising
TransientHttpError on 429/5xx and soft-failing to the seed once exhausted (the EPC
client propagates instead). Direct tests for the shared helper; EPC + postcodes.io
suites repointed at the shared sleep.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Routes run_modelling through prop.effective_epc and dumps each target's
property_overrides before the run, so a landlord wall override moves the
calculated SAP. Records the overlay design in ADR-0032.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
build_first_run_pipeline now constructs epc_prediction=EpcPrediction() and accepts
comparables_repo + prediction_attributes_reader as optional params, threading them
into IngestionOrchestrator (ADR-0031). The on-switch is now just supplying those
two arguments — no orchestrator/handler edits — once they exist: the cohort repo
(its EPC client is the source client pending #1136) and the property_overrides
attributes reader (built separately). Both default None, so the feature stays OFF
and ingestion is unchanged until they're passed.
The epc_property.source migration is live, so the predicted-EPC persistence slot
(slice-5c) now works against the real DB. Handover updated to reflect the simpler
composition-root step.
pyright strict clean; handler + pipeline + ingestion-prediction tests pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The gap-fill is wired end-to-end (slices 5a-5e) behind seams; this note is what's
left to switch it on in production: (1) implement the PredictionTargetAttributesReader
stub over property_overrides — with the override-value → API-code mapping
select_comparables needs; (2) run the epc_property.source Drizzle migration; (3)
pass the three optional collaborators at the IngestionOrchestrator composition
root. Plus the open Validation-Cohort exclusion (no code path exists yet — exclude
on source_path == "predicted" when one is built) and the anomaly dual-use pointer.
No code change: the validation exclusion has no consumer to attach to today, and
the structural signal (source_path == "predicted") already exists from slice-5a.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a `source` discriminator (lodged | predicted) to the EPC store so a Property
holds a lodged EPC and a predicted one (EPC Prediction gap-fill) at once
(ADR-0031). EpcRepository.save gains source="lodged"; idempotent delete is now
per-source (a predicted save no longer wipes lodged, and vice versa);
get_for_property/get_for_properties filter lodged; new get_predicted_for_property
/ get_predicted_for_properties read predicted. PropertyPostgresRepository.get +
get_many hydrate Property.predicted_epc, so the predicted picture reaches the
modelling read (both load via get_many). FakeEpcRepo mirrors the dual slot.
EpcPropertyModel gains `source` (default "lodged"); the test DB builds from the
SQLModel mirror so this is exercised without the prod migration. The matching
Drizzle change (column + per-(property_id,source) uniqueness) is the team's to
action before merge — docs/MIGRATION_NOTE_predicted_epc_source.md.
3 store tests (coexist, idempotent predicted re-save leaves lodged, lodged-only
has no predicted) + property-repo wiring; 85 pass across affected suites; new
code pyright-clean (2 pre-existing wwhrs errors in epc_property_table untouched).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>