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>
Slice 3 of Bill Derivation. sap_code_to_fuel(code) maps a SAP 10.2 / Table 32
fuel code to the canonical billing Fuel — bounded to the ~47 Table 32 codes (the
carrier, orthogonal to the PCDB product index, so all PCDB heat pumps share one
electricity code). Mains gas / LPG / oil+bioliquids / coal / smokeless / wood /
electricity (standard + off-peak) / heat-network groupings; an unmapped code
(dual fuel, grid-export) raises UnmappedSapCode rather than guessing.
Also: ADR-0014 deferred/TODO section records the stubbed appliances+cooking
(pending the SapResult fields), the off-peak day/night split, the heat-network
rate gap, and regional rates / ETL.
The SapResult -> EnergyBreakdown adapter (next slice) is gated on the
appliances/cooking fields landing on SapResult.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Pin the bills design from a /grill-with-docs session:
- ADR-0014: whole-home annual bill from SAP10 Calculation's delivered kWh per
end use, re-priced at real Fuel Rates (NOT the calculator's SAP-notional
total_fuel_cost_gbp, which is RdSAP Table 32 standardised prices ~half real
electricity). Fuel enum + FuelRates + FuelRatesRepository static snapshot;
per-section + total flat columns; raise on unpriced fuel (house coal /
heat network are the named gaps).
- ADR-0013 amendment: the shadow stepping-stone is collapsed — the calculator
is load-bearing now. effective=calculated for sap_version<10.2 (StubRebaseliner
floor 10.0->10.2); >=10.2 keeps lodged + logs divergence; a strict-raise
aborts the batch (load-bearing for bills regardless of version).
- CONTEXT: EPC Energy Derivation -> Bill Derivation (no "service" suffix);
Baseline Performance energy block = per-end-use kWh + per-section bill + total;
Fuel Rates = committed static snapshot; Rebaselining trigger threshold 10.2.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Wire Sap10Calculator into PropertyBaselineOrchestrator as a non-load-bearing
shadow runner. For each property it scores the Effective EPC beside the
load-bearing Lodged/Effective write, catches any strict-raise -> log.error
(never aborts the batch), and on success log.warning's divergence from Lodged:
SAP |continuous - lodged| > 0.5; PEUI/CO2 > 1% relative (CO2 after kg->tonnes).
Every line is tagged with sap_version so SAP-10.2 signal separates from
older-spec drift (ADR-0010 Validation Cohort).
Per ADR-0013, Calculated SAP10 Performance is not a persisted third value-set:
effective = calculated in every baselining scenario, so the calculator IS the
mechanism that produces Effective Performance (the Rebaseliner). It runs in
shadow only while being hardened; when overrides/estimation land it is promoted
to drive Effective and the failure posture flips to abort (ADR-0012, calculator
now load-bearing). No table change.
- ADR-0013 + CONTEXT (Calculated SAP10 Performance / Effective Performance /
Rebaselining) record the decision.
- CalculatorShadow port + LoggingCalculatorShadow + Calculator protocol.
- FakeCalculatorShadow for orchestrator unit tests.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Slice 2a. New recommend_solid_wall emits one Main-wall Recommendation carrying
External + Internal wall-insulation Options for an uninsulated (wall_insulation
_type=4) solid-brick (wall_construction=3) main wall, each priced at the heat-
loss wall area. Cascade pin: the generator's EWI and IWI Options reproduce
their respective re-lodged afters at abs(diff) <= 1e-4.
Detection keys on wall_construction code, not description (ADR-0019 note
corrected): the Elmhurst ingestion path leaves walls[].description empty, so
the code is the only cross-path signal; codes 1-5 are consistent.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Captures the grill-with-docs session for solid-wall insulation: CONTEXT.md
gains EWI/IWI Measure Types + the Wall Insulation Eligibility rule (+ a
flagged-ambiguity that the three planning flags stay distinct, never recollapsed
to legacy restricted_measures). ADR-0019 records the eligibility policy (cavity
-> cavity only; brick/system -> IWI+EWI; timber -> IWI only; cob/stone -> none;
conservation/flat block EWI, listed/heritage block both). ADR-0020 records
conservation/listed/heritage as three distinct Property attributes sourced by
extending the geospatial S3 repo (flags co-located with lat/long).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
From the grill-with-docs pass on the depth+scale phase. Splits the
overloaded "valuation" into two glossary terms — Property Valuation
(current market value, a Baseline attribute, mostly missing) and
Valuation Uplift (plan-conditional, percentage-primary; absolute £ only
when a Property Valuation exists, 2x ROI cap on the £ form). ADR-0018
records the percentage-primary decision and why (the EPC scale corpus
has no market values, so a value-primary model produces nothing), plus
the deferred sourcing / per-measure / rental-yield items.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rewrite the migration spec into the full expand/contract sequence (add plan_id
→ backfill → dual-write → cut reads → drop) with the two load-bearing rules:
backfill before any read cuts over, and dual-write the m2m until all reads are
off it (the Drizzle FE reads the tables directly, so the repos can't deploy
atomically). Amend ADR-0017 from "m2m retired for new writes" to "m2m dropped +
one SQLModel definition per table under infrastructure/postgres/modelling/".
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Records the /grill-with-docs design for the Modelling Bill-Derivation slice:
Bill Derivation is cross-stage (relocate Bill/EnergyBreakdown/BillDerivation/
sap_fuel to a neutral domain/billing/); Modelling bills the fully-overlaid
post-package SapResult (so fuel-switch measures price at the new fuel for free),
diffing against the baseline at the same FuelRates snapshot; the post-package
and baseline SapResults are captured from scores the optimiser/orchestrator
already compute (Score.sap_result), so no second calculate; FuelRatesRepository
is constructor-injected into ModellingOrchestrator mirroring Baseline; plan-level
columns this slice, per-measure telescoping bill cascade next (energy_savings is
vestigial, left NULL).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The warm-start (and max-gain fallback) now price each forced Measure Dependency
the candidate triggers, not just inject it afterwards: optimise/optimise_min_cost
fold dependencies into each candidate's cost+gain via _augmented_cost_gain, and
optimise_package scores each dependency's true role-1 signal (_with_role1_signals)
instead of the 0.0 placeholder. This stops the min-cost objective (i) ignoring the
~£900 a wall drags in (a wall-free package reaching target can be cheaper) and
(ii) picking a small-gain wall whose mandatory ventilation (down to -5 SAP) makes
it net-negative, which repair cannot un-pick.
Budget is now a hard envelope: the constraint applies to the augmented (measure +
its ventilation) cost, so a wall that fits alone but whose ventilation would bust
the budget is DROPPED rather than forced over budget. This reverses the earlier
'forced regardless of budget' call (which made sense when selection was
ventilation-blind). Safety invariant intact — presence still injected on every
path; we just never recommend a wall we can't afford to ventilate. ADR-0016
amendment updated. 94 modelling+orchestration tests pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The original ADR-0016 mis-specified the warm-start objective as maximise-gain-
subject-to-budget (with the target a repair floor); the rebuild faithfully
implemented that wrong objective. The intended behaviour is the legacy
StrategicOptimiser Case 1: minimise cost subject to (true) SAP gain >= target and
cost <= budget, falling back to max-gain-within-budget only when the target is
unreachable. For Increasing EPC this is least-cost-to-target: cheapest package
reaching the band, stops at the target (no overshoot into a higher band), surplus
budget unspent.
Also records: target predicate sap_continuous >= band floor (conservative, no
legacy slack — re-score+repair supersede it); ventilation-aware selection (the
forced dependency, -1 to -5 SAP, is folded into candidate evaluation with a real
negative role-1 signal, not just injected afterwards); presence-vs-awareness
enforcement; warm-start+re-score+repair structure and scalability rationale kept.
Sharpened the CONTEXT.md Optimised Package definition to match.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Outcome of the /grill-with-docs session scoping #1157.
- CONTEXT.md: add **Plan Measure** (the persisted selected Option +
role-3 attribution + cost); Recommendation stays the candidate.
Remove Scenario Phase / Plan Phase / Rolled-over Options — multi-phase
is deferred. Reshape Scenario + Plan to single-phase; fix relationships,
dialogue, and the "phase" ambiguity note.
- ADR-0005: rewritten to Deferred (multi-phase was speculative
prospective-client work; single-phase now; future plan_phase back-fill
path preserved). Stray phase refs cleaned in ADR-0016 / ADR-0009.
- ADR-0017 (new): Plan persistence — reuse the live plan/recommendation
tables via SQLModel mirrors + a PlanRepository on the UoW; add
recommendation.plan_id, retire the plan_recommendations m2m; flat
post-retrofit on plan; idempotent replace; CO2 in tonnes. Unselected
alternatives + bills noted as deferred directions.
- docs/migrations/recommendation-plan-id.md: the FE-owned Drizzle change.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reframe Recommendation as a target surface (partitions the EpcPropertyData
surface, so selected overlays never collide); add Measure Option,
Simulation Overlay (EpcSimulation), Product, Cost, Contingency, and
Measure Dependency. ADR-0016 fixes the scoring/optimisation approach
(warm-start grouped-knapsack MILP -> deterministic package re-score ->
greedy repair, with a final-package marginal cascade for display
attribution), resolving the open question in ADR-0005 §14.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The SapResult -> EnergyBreakdown adapter (ADR-0014), a classmethod on the
target mirroring Performance.from_sap_result. Folds each positive per-end-use
delivered kWh into a billable EnergyLine: main/main-2/secondary heating and
hot water at their resolved fuel (sap_code_to_fuel); lighting/pumps-fans/
appliances/cooking/cooling as electricity. PV export carries to exported_kwh
for the SEG credit. Zero-kWh end uses emit no line; a positive kWh with no
fuel code raises rather than billing at a default (strict, mirrors the
calculator).
Adds BillSection.COOLING (electricity, from space_cooling_fuel_kwh_per_yr).
BillDerivation already prices any section it is given, so no change there.
Also corrects the ADR-0014 amendment: SapResult carries the calculator's own
fuel codes (raw API or Table-32 per mapper, ADR-0015); sap_fuel normalizes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Captures a /grill-with-docs session resolving how BillDerivation gets the
fuel each end use burns, and what Rebaselining actually is.
- ADR-0014 amendment: per-end-use fuel is a calculator OUTPUT (resolved
Table-32 codes on SapResult: main-1/main-2/secondary/HW + pv_exported_kwh);
the adapter is a pure SapResult->EnergyBreakdown map. Corrects stale §3
(is_gas_code... -> sap_fuel.sap_code_to_fuel). Adds COOLING section.
Interim, pending ADR-0015.
- ADR-0013 amendment: the calculator is the SCORING ENGINE within
Rebaselining (assemble the Effective EPC picture, then score), not the
whole of it; the Rebaseliner exposes its SapResult so the orchestrator
composes Effective Performance AND the Bill from one scoring.
- ADR-0015 (new): mappers own cert normalization; EpcPropertyData becomes a
strict type. Explains why fuel resolution sits in the calculator today.
- CONTEXT.md: Effective EPC = the assembled picture; Rebaselining = assemble
(overrides / neighbour-estimation / old-schema remap) then score.
- EpcPropertyData docstring points at ADR-0015.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Slice 3 of Bill Derivation. sap_code_to_fuel(code) maps a SAP 10.2 / Table 32
fuel code to the canonical billing Fuel — bounded to the ~47 Table 32 codes (the
carrier, orthogonal to the PCDB product index, so all PCDB heat pumps share one
electricity code). Mains gas / LPG / oil+bioliquids / coal / smokeless / wood /
electricity (standard + off-peak) / heat-network groupings; an unmapped code
(dual fuel, grid-export) raises UnmappedSapCode rather than guessing.
Also: ADR-0014 deferred/TODO section records the stubbed appliances+cooking
(pending the SapResult fields), the off-peak day/night split, the heat-network
rate gap, and regional rates / ETL.
The SapResult -> EnergyBreakdown adapter (next slice) is gated on the
appliances/cooking fields landing on SapResult.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Pin the bills design from a /grill-with-docs session:
- ADR-0014: whole-home annual bill from SAP10 Calculation's delivered kWh per
end use, re-priced at real Fuel Rates (NOT the calculator's SAP-notional
total_fuel_cost_gbp, which is RdSAP Table 32 standardised prices ~half real
electricity). Fuel enum + FuelRates + FuelRatesRepository static snapshot;
per-section + total flat columns; raise on unpriced fuel (house coal /
heat network are the named gaps).
- ADR-0013 amendment: the shadow stepping-stone is collapsed — the calculator
is load-bearing now. effective=calculated for sap_version<10.2 (StubRebaseliner
floor 10.0->10.2); >=10.2 keeps lodged + logs divergence; a strict-raise
aborts the batch (load-bearing for bills regardless of version).
- CONTEXT: EPC Energy Derivation -> Bill Derivation (no "service" suffix);
Baseline Performance energy block = per-end-use kWh + per-section bill + total;
Fuel Rates = committed static snapshot; Rebaselining trigger threshold 10.2.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Wire Sap10Calculator into PropertyBaselineOrchestrator as a non-load-bearing
shadow runner. For each property it scores the Effective EPC beside the
load-bearing Lodged/Effective write, catches any strict-raise -> log.error
(never aborts the batch), and on success log.warning's divergence from Lodged:
SAP |continuous - lodged| > 0.5; PEUI/CO2 > 1% relative (CO2 after kg->tonnes).
Every line is tagged with sap_version so SAP-10.2 signal separates from
older-spec drift (ADR-0010 Validation Cohort).
Per ADR-0013, Calculated SAP10 Performance is not a persisted third value-set:
effective = calculated in every baselining scenario, so the calculator IS the
mechanism that produces Effective Performance (the Rebaseliner). It runs in
shadow only while being hardened; when overrides/estimation land it is promoted
to drive Effective and the failure posture flips to abort (ADR-0012, calculator
now load-bearing). No table change.
- ADR-0013 + CONTEXT (Calculated SAP10 Performance / Effective Performance /
Rebaselining) record the decision.
- CalculatorShadow port + LoggingCalculatorShadow + Calculator protocol.
- FakeCalculatorShadow for orchestrator unit tests.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>