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>
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>
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 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>
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>
Tier-2 (full national bulk streaming) is deferred. The near-term scale
validation is a Tier-1.5: a few-thousand-cert anonymised corpus stored in
S3 (too large to commit, far more stable than the 36-target gate fixture),
pulled to a temp dir and run through the same load_corpus +
evaluate_component_accuracy. Reuses the committed-fixture machinery wholesale
— only the data source differs. One scorer, three data sources (committed
fixture / S3 corpus / bulk stream).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Records the grilling-session decisions amending ADR-0029's validation:
- Source cohort keeps all cert vintages (components are agnostic of the SAP
methodology that rated them); only the held-out validation TARGET is
restricted to SAP 10.2. Amends ADR-0029 decision 5 ("pre-SAP10 dropped").
- Component Accuracy (predicted vs API actual components) is the primary,
calculator-independent signal. calc(predicted) vs calc(actual) rejected
(circular ground truth, hides calculator error); neighbour-mean-lodged-SAP
baseline rejected (mixes SAP versions). calc(predicted) vs API-lodged
SAP/carbon/PE kept as a secondary, calculator-floored guard.
- Two tiers: committed anonymized fixture (ratcheting CI gate) + bulk-export
national battle-test on harness/epc_bulk.py + harness/cohort.py, emitting
accuracy + a failure taxonomy, re-baselining the gate floors.
CONTEXT.md: Comparable Properties corrected to all-vintage source; new
Component Accuracy term. ADR-0029 Validation section marked superseded.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Records the four load-bearing design decisions from the grill-with-docs session:
standalone co-selectable rec; eligibility = lodged-only (no effectiveness gate,
electric-storage §A.2.2 no-op is the Optimiser's call); dedicated clearing
SecondaryHeatingOverlay; flat per-dwelling cost (a lodged secondary is fixed per
RdSAP, so a real decommission job, not room-scaled).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Documents the inherit-and-validate decision for 18.0: reuse 20.0.0's 0.148 +
band multipliers (the corpus can't self-fit — 958/1000 band-1 with no measured
band-1 windows), validated against 18.0's own band-4 rich certs (0.223 obs vs
0.148 x 1.51 pred). References ADR-0027 one-way (keeps the accepted ADR immutable).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the flat placeholder scalars (boiler £3000; tune-up £500/£900) with a
per-dwelling composite cost, mirroring the ASHP architecture (ADR-0025): a
`HeatingRates` table (data, `heating_rates.json`), typed `BoilerCostInputs` /
`TuneUpCostInputs`, pure `Products.boiler_bundle_cost` / `tune_up_cost`, and
modelling-layer interpreters that read the dwelling into those inputs.
The cost mirrors the Simulation Overlay component-for-component, sharing the
controls + cylinder pricing across both options:
- tune-up (standard) = standard controls + cylinder fixes
- tune-up (zone) = zone controls + cylinder fixes
- boiler upgrade = £3200 all-in + standard controls (only when the upgrade
fired a controls change) + cylinder fixes
Standard controls are priced INCREMENTALLY — only the parts missing to reach
SAP 2106 (programmer £120 / room thermostat £150 / TRV £35×radiators), read
from a Table 4e Group-1 feature map so a dwelling that already has a room
thermostat + TRVs is only charged the programmer. Zone controls are a full
smart kit (hub £205 + smart TRV £50×radiators) — the smart TRV is itself the
room sensor, so there is no separate per-room sensor line. Cylinder fixes:
jacket £50 (when under-insulated) + thermostat £150 (when absent). The boiler
is a like-for-like wet swap (no radiators/flue/pipework — eligibility already
requires an existing wet boiler), so those dead-code extras are not modelled.
Figures are research-validated 2025/26 UK installed costs (legacy Costs.py
lineage); fully-loaded totals with one contingency on top (Model B, not the
legacy VAT/preliminaries engine). Contingency: boiler 0.26; tune-ups 0.10
(was a 0.15 placeholder). ADR-0027 records the design; CONTEXT.md's Heating
Eligibility entry updated to cover the partial boiler/tune-up family + composed
cost. Products cost pins (delta<=1e-9) + interpreter tests + generator
composite-cost assertions.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sharpen the glossary to decouple deterministic old-schema re-mapping from
neighbour-prediction gap-fill (a separate, unimplemented ML path), and add
the Reduced-Field Synthesis term. ADR-0027 records the pre-SAP10 20.0.0
mapper's best-attempt synthesis (corpus-fit glazing 0.148xTFAxband, 4-way
orientation) and its trade-offs. Grill resume doc captures every resolved branch.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Designed via grill-with-docs. API-first (Google Solar -> typed SolarPotential):
one Solar PV Recommendation, up to 5 conservatively-sized configs x battery
on/off; new PV overlay surface (per-roof-segment arrays, diverter conditional on
cylinder, export-capable ensured); overshading generation-calibrated from the
API's expected generation; eligibility offers conservation areas (only listed/
heritage block); composite costing from EA rates (contingency 0.15). CONTEXT
gains Solar Potential, Solar PV Recommendation, Solar PV Eligibility.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Slice 7 of ADR-0025 costing: the modelling-layer interpretation half of the
split. ashp_cost_inputs derives existing system (mains_gas/fuel/SAP-code),
size band (floor area <= 75 m2), design heat loss (floor_area x 0.05 -- the
chosen proxy over HLC, ADR updated), radiator count (habitable + 3, floor-area
fallback) and reusable-wet-system flag. Catalogue math (Products) stays
EPC-free. ADR-0025 updated to record the floor-area pump-sizing choice.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Designs the heating Recommendation Generator via /grill-with-docs. Load-bearing
decisions: HW + controls + fuel + meter fold into each competing whole-system
bundle (the legacy heating-vs-HW split double-counted); each bundle is a fixed,
real, contractor-installable end-state (ASHP via PCDB index, HHR storage via
sap_main_heating_code=409), Product stays cost-only; eligibility encodes only
physical/planning installability since the Optimiser owns the economics (the
legacy ASHP built-form / 120 m² rule is dropped — research found no
authoritative basis); the Simulation Overlay is the deepest surface yet,
spanning main_heating_details[0] + sap_heating + top-level EpcPropertyData +
sap_energy_source. Build order HHRSH -> ASHP -> boiler (deferred).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Resolved in a grill-with-docs pass. recommend_lighting converts ALL non-LED
bulbs (incandescent + CFL + low-energy-unknown) to LED — all the way to LED, not
the legacy "fill to low energy", because SAP §12-1 rates LED efficacy (100)
above LEL (80) / CFL (55). A free Optimiser candidate (it improves SAP), unlike
ventilation's forced dependency. Its overlay is the first whole-dwelling,
top-level surface: a LightingOverlay carrying the four bulb-count fields by
their exact EPC names, folded directly onto EpcPropertyData (led=total, others
0). Priced per-bulb x non-LED count, contingency 0.26, measure_type
low_energy_lighting (MEASURE_MAP-aligned; "LED" in the description). Validation:
real before/after cascade pins (zero-existing-LEDs + some-existing-LEDs) at 1e-4,
clean (no fabric coupling). Ground-truth confirmed: 20 incandescent -> 20 LED
drops lighting (232) 783.7 -> 232.7 kWh/yr.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Grill-with-docs outcome for the glazing generator. Single planning-picked
Measure (double when unrestricted, secondary for conservation/listed/heritage),
upgrading all single-glazed windows together. The overlay writes lodged U-value
+ solar-g directly into WindowTransmissionDetails (our calculator consumes those
as inputs — it does NOT derive them from glazing_type, unlike Elmhurst) plus
glazing_type for the §5 daylight factor; EpcSimulation gains a per-window
`windows` surface. Priced flat average-per-window × single-glazed count.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Grill-with-docs outcome for the roof-insulation generator. One dispatching
recommend_roof_insulation, one Measure per roof by type (loft 300mm incl.
thatch / sloping-ceiling 100mm / flat-roof 200mm; no-access → none),
MAIN-only, room-in-roof deferred. Detection keys on the roof_construction_type
string (populated on both paths; the calculator already dispatches on it) with
sloping→flat→no-access→loft ordering; the roof_construction-int cross-mapper
parity is the follow-up Hestia-Homes/Model#1178. Thatch is not excluded — it
takes loft insulation.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reference the calculator-side wall_construction=6 disambiguation issue from the
strict-xfail tripwire and ADR-0019, so the blocker is traceable both ways.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
System-built (precast/no-fines concrete) takes both solid-wall Options like
solid brick (ADR-0019), keyed on `wall_construction == 6` (WALL_SYSTEM_BUILT,
Elmhurst `SY`). A basement-suitability guard (`main_wall_is_basement`) is added
since a below-ground basement wall is never EWI/IWI-suitable.
This is currently inert: `B Basement wall` also maps to 6 (mapper.py:2100) and
`main_wall_is_basement` is derived as `wall_construction == 6`, so every code-6
wall reads as basement and is guarded out — the live cohort is unchanged. The
system-built EWI/IWI cascade pin is committed as a strict-xfail tripwire that
flips green the moment the calculator disambiguates system-built from basement
(MAIN wall_construction==6 with main_wall_is_basement False). `wall_construction
== 8` is Park home, not system-built — not keyed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>