Commit graph

7162 commits

Author SHA1 Message Date
Khalim Conn-Kowlessar
2b0b7eff91 Add Electric room heaters as a canonical landlord heating archetype 🟥
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 16:00:36 +00:00
Khalim Conn-Kowlessar
ada2bc078e Map landlord electric room heaters to the direct-acting room-heater SAP code 🟩
'Electric room heaters' archetype -> SAP Table 4a code 691 (panel/convector/
radiant), on a single-rate meter (691 is not off-peak-implying, unlike storage).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 15:59:25 +00:00
Khalim Conn-Kowlessar
ce59413f46 Map landlord electric room heaters to the direct-acting room-heater SAP code 🟥
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 15:58:34 +00:00
Daniel Roth
597510b996
Merge pull request #1353 from Hestia-Homes/feature/dont-resave-epc-if-refetch-false
Don't re-save EPC data which has been read from database rather than fetched. Also turn on database insights
2026-06-29 15:14:14 +01:00
Daniel Roth
0718533d3a turn on rds performance insights 2026-06-29 14:09:24 +00:00
KhalimCK
8b0b5c0387
Merge pull request #1352 from Hestia-Homes/fix/ground-floor-flat-default-heating
fix(sap): credit PV only when connected to the dwelling's meter
2026-06-29 15:06:29 +01:00
Daniel Roth
b1406bb482 Skip re-saving EPC data when it was read from DB unchanged
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-29 14:03:23 +00:00
Khalim Conn-Kowlessar
08d9e46481 docs(pv): document gov-API pv_connection enum on the domain field 🟪
0 = no PV, 1 = not connected to dwelling meter, 2 = connected. Decoded
empirically from the corpus + accredited Elmhurst (the gov RdSAP schema
types it as a bare int). Points readers to the crediting gate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 13:50:01 +00:00
Khalim Conn-Kowlessar
9927169e23 test(accuracy): ratchet SAP gauge after PV connection gate 🟪
within-0.5 floor 0.73->0.74 (now 0.741), MAE ceiling 0.762->0.740 (now
0.7397) on the fixed RdSAP-21.0.1 corpus. Log entry appended.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 13:43:30 +00:00
Khalim Conn-Kowlessar
8606cab5f0 fix(pv): credit PV only when connected to the dwelling's meter 🟩
Gate PV generation/credit in cert_to_inputs on gov-API pv_connection:
credit only when ==2 ('connected'); ==1 ('present but not connected to the
dwelling's meter') contributes zero to the dwelling's cost/CO2/PE per
RdSAP 10 §11.1 / SAP 10.2 Appendix M. Non-int (None / site-notes str) keeps
the credit-if-array behaviour, so the Elmhurst/Summary + synthetic paths are
unchanged (no regression).

Corpus: all 5 pv_connection=1 PV certs move inside ±0.5 (e.g. 100051118081
+6.5→+0.5); MAE 0.760→0.740, within-0.5 73.8→74.3%, no regression
(pv_connection=2 certs keep their credit).

Also corrects a now-load-bearing latent bug: the solar-recommendation
overlay tagged recommended arrays pv_connection=1 ('not connected') — which
the new gate would zero. A new install connects to the dwelling's meter, so
it must be 2; pinned by the overlay test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 13:40:51 +00:00
Daniel Roth
b426940136
Merge pull request #1350 from Hestia-Homes/modelling-e2e-12-concurrency
Decrease e2e modelling concurrency from 32 to 12
2026-06-29 14:36:01 +01:00
Daniel Roth
66fa08af19 update comment 2026-06-29 13:33:48 +00:00
Daniel Roth
71b304fa47 decrease lambda concurrency from 32 to 12 2026-06-29 13:32:53 +00:00
Khalim Conn-Kowlessar
775232b4e7 test(pv): credit PV only when connected to dwelling meter (pv_connection) 🟥
RdSAP 10 §11.1 / SAP 10.2 Appendix M: PV is included in a dwelling's
assessment only if connected to the dwelling's own electricity meter. The
gov-API pv_connection enum encodes this — 0=no PV, 1=present-but-not-
connected, 2=connected. Corpus-validated (57 PV certs: pv_connection=1 MAE
4.48->1.22 without credit, 0/5 need it; pv_connection=2 needs it, MAE 0.98
vs 10.29) and Elmhurst-proven (connected SAP 87 vs not-connected 74).

cert_to_inputs currently credits a pv_connection=1 array; the test pins that
it must contribute zero generation. Adds pv_connection to make_minimal_sap10_epc.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 13:32:20 +00:00
KhalimCK
98ba8d89dc
Merge pull request #1343 from Hestia-Homes/fix/effective-baseline-overrides-and-epc-fidelity
fix(baseline): honour EPC + overrides as the authoritative Effective baseline
2026-06-29 11:19:52 +01:00
KhalimCK
29a0cc7f47
Merge pull request #1344 from Hestia-Homes/tooling/modelling-anomaly-audit
feat(audit): pluggable modelling-anomaly audit + audit-ara-portfolio skill
2026-06-29 11:18:26 +01:00
KhalimCK
31e8b93ea0
Merge pull request #1345 from Hestia-Homes/fix/storage-heater-space-heating-demand
fix(sap): expose roof for mislabelled top-floor flats via roof_insulation_location
2026-06-29 11:18:03 +01:00
KhalimCK
d55f520f83
Merge pull request #1346 from Hestia-Homes/fix/elmhurst-summary-roof-window-classification
fix(elmhurst): party-ceiling Main BP window is vertical, not a rooflight
2026-06-29 11:17:49 +01:00
Khalim Conn-Kowlessar
02ef67fd8f chore(debug): summary_to_sap.py — Elmhurst Summary PDF -> our SAP + trail 🟪
Runs a Summary PDF through the chain-test path (pdftotext ->
ElmhurstSiteNotesExtractor -> from_elmhurst_site_notes) into Sap10Calculator
and dumps SAP + per-end-use kWh + the intermediate worksheet trail, for
diffing our calc against the accompanying Elmhurst U985 worksheet PDF.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 21:26:30 +00:00
Khalim Conn-Kowlessar
0851b48807 fix(elmhurst): party-ceiling Main BP window is vertical, not a rooflight 🟩
_is_elmhurst_roof_window routed a vertical window (Location "External wall")
to sap_roof_windows whenever its building part's roof type was a party
ceiling (A "Another dwelling above" / NR "Non-residential space above").
That is right for an EXTENSION (cert 000565 Ext2/Ext4 score on worksheet
(27a)) but wrong for the MAIN BP: a Main "A"/"NR" roof is the dwelling's own
party ceiling — no external roof — so its windows are vertical.

Gate the BP-roof-type rooflight rule to non-Main building parts. On a mid-
floor flat (case 53 / cert 000565 re-keyed) the Main External-wall window is
now vertical: its area deducts from the wall (was over-counted ~7 W/K) and it
prices as a window. Summary-path SAP 74.0 -> 75.17, matching Elmhurst's
accredited worksheet (75; space-heat 3408.6 vs 3408.58) — confirming the
calculator is correct; the gap was the misclassification.

000565's extension rooflights, the "Roof of Room" branch, and the U>3.0
room-in-roof backstop are unchanged. gov-API path unaffected (it routes roof
windows via window_wall_type==4).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 21:25:20 +00:00
Khalim Conn-Kowlessar
23245a085c test(elmhurst): party-ceiling BP window is vertical, not a rooflight 🟥
_is_elmhurst_roof_window mis-routes a vertical window (Location "External
wall") to sap_roof_windows when the building part's roof is a PARTY ceiling
(A "Another dwelling above" / NR "Non-residential space above"). A party
ceiling has no external roof, so it cannot host a rooflight.

Surfaced by simulated case 53 (cert 000565 re-keyed as a mid-floor electric-
storage flat, roof "A Another dwelling above"): its External-wall window
(U 2.00) routed to sap_roof_windows -> window area not deducted from the wall
(wall over-counted ~7 W/K) + priced as a roof window -> our SAP 74.0 vs
Elmhurst worksheet 75. Elmhurst lodges it Type "Window", Location "External
wall".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 07:06:04 +00:00
Khalim Conn-Kowlessar
0f79de7a35 fix(exposure): honour determined roof_insulation_location for flats 🟩
A gov-API flat can lodge dwelling_type="Mid-floor flat" while carrying its
own exposed roof — a top-floor flat mislabelled mid-floor. _dwelling_exposure
keyed roof exposure on the dwelling_type label alone, dropping the roof
heat-loss term: space-heating demand under-read ~32%, SAP over-read +7.

Fix: when the main building part lodges a *determined* roof_insulation_location
(an RdSAP integer code, not the "ND" Not-Defined party-ceiling sentinel),
expose the roof regardless of a contradictory label. Structured field, not a
description string and not roof_construction (which the gov-API lodges
building-wide on every unit, so it is not a per-unit signal).

On the RdSAP-21.0.1 corpus roof_insulation_location separates the classes with
zero disagreement: all 190 party-ceiling flats lodge "ND"; the 4 mid/ground
flats this exposes all move toward lodged, 0 away. within-0.5 73.3% -> 73.6%,
MAE 0.774 -> 0.761 (ratchets tightened). Verified end-to-end on the same
block: 715363 (location 6, RHI 2694) 81 -> 74 = lodged; genuine mid-floor
sibling 715395 (location ND, RHI 1024) stays party at 83 = lodged.

The override is additive (only ever exposes a label-dropped roof) and reads
the main part, so multi-part flats with a party main ceiling stay party.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 21:34:33 +00:00
Khalim Conn-Kowlessar
7a74aaecb9 test(exposure): mid-floor-labelled flat with a lodged exposed roof 🟥
A gov-API cert can lodge dwelling_type="Mid-floor flat" while carrying a
real exposed roof element (roof_construction != 7 "dwelling above") over a
"(another dwelling below)" floor — i.e. a top-floor flat mislabelled
mid-floor. Property 715363 (uprn 6027561) + sibling 715395 (6027563) do
exactly this; the correctly-labelled top-floor sibling 715871 (6027574),
same block + same flat roof, already computes the lodged SAP 74.

_dwelling_exposure keys roof exposure on the dwelling_type label alone, so
it drops the roof heat-loss term, under-reading space-heating demand ~32%
(calc 1833 vs lodged RHI 2694) and over-reading SAP +7 (81 vs 74).

Pins the fix: a mid-floor label + lodged exposed roof must expose the roof
(floor stays party). Also corrects the existing mid-floor fixture to lodge
the party-ceiling code 7 (the default 4 is an exposed pitched roof).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 20:59:31 +00:00
Khalim Conn-Kowlessar
2d6b078bd8 feat(audit): self-improvement loop in the skill + provenance convention 🟩
Add Phase 6 (self-improve) to audit-ara-portfolio: when a run confirms a
novel systematic problem, codify it as a check — gated on systematic (>=5
props, root-caused), not-already-covered, and /grill-me-pressure-tested.
Each check records provenance (motivating cause + example properties) so the
registry stays sharp and compounds every run.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 19:57:57 +00:00
Khalim Conn-Kowlessar
e3c9107313 feat(audit): --scenario filter + audit-ara-portfolio skill 🟩
Script takes an optional --scenario to restrict to one scenario's plans. New
skill drives the full loop: run the deterministic scan, review groups,
deep-dive samples via run_modelling_e2e, characterise sub-classes, and
cross-reference open PRs/ADRs — then proposes new checks to codify.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 19:49:48 +00:00
Khalim Conn-Kowlessar
37c7a2c186 feat(audit): solar + high-SAP checks; group under scripts/audit/ 🟩
Add recommendation-level rollups (solar SAP points + solar bill saving) and
checks: impossible-sap-over-100 (found 1), excessive-solar-sap (oversized
array, 51), low-solar-bill-savings (SEG/self-consumption pricing, 83),
unusually-high-post-sap (21). Move to scripts/audit/anomalies.py
(python -m scripts.audit.anomalies).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 19:46:23 +00:00
Khalim Conn-Kowlessar
39fd1c9e42 feat(audit): pluggable modelling-anomaly audit over the DB 🟩
A check-registry that reads property/baseline/plan/scenario and flags odd
results (plan-below-baseline, already-meets-goal-with-works, band/score
mismatch, zero-works-post-differs, effective-lodged divergence, negative
bill savings). Writes modelling_audit.md/.csv. Adding a check = one
decorated function.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 19:38:28 +00:00
Khalim Conn-Kowlessar
79b86a38c1 docs: ADR-0039 (override-aware rebaseline) + ADR-0040 (nested persistence) 🟪
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>
2026-06-26 19:25:54 +00:00
Khalim Conn-Kowlessar
c776341e03 test: portfolio-796 override-rebaseline regression (the 7 properties) 🟩
Pin the hit-list properties: each property's real Landlord Override set still
resolves to overlays and trips physical_state_changed, and the rebaseliner
adopts the calculator output (reason physical_state_changed/both) rather than
echoing the lodged headline — so the displayed baseline and the modelled plan
agree. Scores a real cert through the live Sap10Calculator.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 19:22:32 +00:00
Khalim Conn-Kowlessar
4d5504fa10 feat: thread physical-state-change signal into rebaselining 🟩
Add Property.physical_state_changed (true on Site Notes / Landlord Overrides
/ Prediction — trigger (b)/(c)) and pass it from the
PropertyBaselineOrchestrator into the Rebaseliner. So an overridden or
predicted SAP>=10.2 property now stores calc(effective) as its Effective
baseline instead of echoing the lodged headline — closing the "81 in the DB"
divergence between the displayed baseline and the modelled plan.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 19:15:11 +00:00
Khalim Conn-Kowlessar
2f7cfbf446 feat: rebaseliner adopts calc output on physical-state change 🟩
CalculatorRebaseliner uses the calculator output as Effective Performance
whenever a Rebaselining trigger fired — pre-SAP10 (a) OR overrides/prediction
moved the physical state (b)/(c) — tagging pre_sap10 / physical_state_changed
/ both. Only a pristine lodged >=10.2 cert keeps its accredited figure (the
sole case the calculator runs purely to validate). Divergence is logged only
in that pristine case. ABC + StubRebaseliner take the new keyword-only flag.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 19:09:55 +00:00
Khalim Conn-Kowlessar
1e019ea3b3 test: rebaseliner adopts calc output when physical state changed 🟥
A SAP>=10.2 cert whose physical state was changed by Landlord Overrides or
Prediction must rebaseline off the calculator (the accredited lodged figure
no longer describes the dwelling) and tag physical_state_changed / both, and
must NOT log divergence (the calc IS adopted, nothing to validate against).
This is the D1 fix: the stored Effective baseline stops echoing the lodged
headline for overridden properties.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 19:04:32 +00:00
Khalim Conn-Kowlessar
6faa5171af feat: reconstruct top-level extract_fans_count from its persisted mirror 🟩
The deterministic calculator reads sap_ventilation.extract_fans_count (which
already round-trips); the top-level epc.extract_fans_count is its mirror (the
mapper sets both from one source). Reconstruct it from the same column so
EpcPropertyData round-trips complete, dropping the allow-list exception.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 18:54:43 +00:00
Khalim Conn-Kowlessar
1d236e3490 docs: mark the 7 calculator-read round-trip gaps closed 🟪
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 18:48:28 +00:00
Khalim Conn-Kowlessar
513c9b9897 feat: persist 7 calculator-read EPC fields 🟩
Wire community heating fuel + CHP fraction (epc_main_heating_detail),
alt-wall is_sheltered + wall insulation thermal conductivity
(epc_building_part), and pv_diverter_present / measured cylinder volume /
AP50 air permeability (epc_property) through save + _compose/_to_*. All
deep-equal round-trip; coverage guard now enforces their reconstruction.
Columns live (FE migration applied).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 18:46:42 +00:00
Khalim Conn-Kowlessar
5c4a8d9094 test: 7 calculator-read EPC fields must round-trip 🟥
community heating fuel + CHP fraction, alt-wall is_sheltered, wall
insulation thermal conductivity, pv_diverter_present, measured cylinder
volume, AP50 air permeability — all calculator-read, all silently dropped on
save. FE columns now live; assert deep-equal round-trip and drop their
coverage-guard allow-list entries so the guard enforces reconstruction.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 18:42:51 +00:00
Khalim Conn-Kowlessar
bb4f8577fe test: correct multi-measure plan expectation for exposed-floor fix 🟩
The fixture lodges is_exposed_floor=True, which was silently dropped on
save->reload before this branch — so the orchestrator (reads the DB) modelled
the floor as not-exposed. Now it round-trips, the exposed floor is honoured,
and the ASHP-led max-gain fallback on this gas dwelling is bill-neutral
(marginally negative) rather than bill-positive: large energy saving, but the
gas->electricity fuel switch offsets the GBP saving. Same optimal package,
same telescoping; only the bill-savings sign assumption changed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 16:58:48 +00:00
Khalim Conn-Kowlessar
21b25b8b35 docs: record PV-array + floor-flag round-trip closures 🟪
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>
2026-06-26 16:53:42 +00:00
Khalim Conn-Kowlessar
68055364a6 test: recursive persistence-coverage guard sees nested fields 🟪
The ADR-0036 guard only inspected EpcPropertyData's top-level fields, so a
dropped field on a NESTED object (the PV-array list, the floor heat-loss
flags) slipped straight through. Generalise it to walk every domain
dataclass reachable from EpcPropertyData and check each field is
reconstructed by a _compose/_to_* mapper or allow-listed (per-field or
whole-class), keyed by Class.field.

Surfaced 14 pre-existing nested gaps the old guard was blind to: 7 are
calculator-read with no FE column (scoring-relevant silent-drop, same class
as the PV bug — tracked follow-up), the rest dormant or awaiting FE tables.
Each is now explicit and justified.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 16:53:04 +00:00
Khalim Conn-Kowlessar
09ba84edf9 feat: persist solar PV arrays through the EPC round-trip 🟩
Add EpcPhotovoltaicArrayModel (epc_photovoltaic_array child table) and wire
save / delete / read so sap_energy_source.photovoltaic_arrays survives
load->save->load in order. Threaded through both the single get() and the
bulk _for_properties() paths via _compose -> _to_energy_source. Column
names match the FE migration (feature/epc-pv-and-floor-heatloss-schema).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 16:47:37 +00:00
Khalim Conn-Kowlessar
1041c8ed0e test: solar PV arrays must round-trip through persistence 🟥
sap_energy_source.photovoltaic_arrays has no table, so every array is
dropped on save — worth ~12 SAP points on an electrically-heated dwelling
(persist != score). Inject two ordered arrays onto a PV-free fixture.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 16:43:26 +00:00
Khalim Conn-Kowlessar
e5d94adc36 feat: persist floor heat-loss flags through the EPC round-trip 🟩
Add is_exposed_floor / is_above_partially_heated_space to
EpcFloorDimensionModel and wire from_domain + _to_floor_dimension. Column
names match the FE schema (feature/epc-pv-and-floor-heatloss-schema).
Live DB migration is run post-merge (drizzle-kit generate picks them up).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 16:42:20 +00:00
Khalim Conn-Kowlessar
c04692f9f5 test: floor-dimension heat-loss flags must round-trip 🟥
is_exposed_floor / is_above_partially_heated_space have no
epc_floor_dimension column, so a True flag round-trips back to the False
default and silently flips the floor's heat-loss path (persist != score).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 16:39:25 +00:00
KhalimCK
6303343575
Merge pull request #1341 from Hestia-Homes/fix/coherent-gas-boiler-overlay
fix(overlay): coherent gas-boiler heating system on Landlord Override
2026-06-26 14:18:56 +01:00
Jun-te Kim
a680d65188 fix(overlay): synthesise a coherent gas-boiler heating system on Landlord Override
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>
2026-06-26 12:48:33 +00:00
Jun-te Kim
706e0072d0
Merge pull request #1337 from Hestia-Homes/fix/predicted-property-display-unknowns
fix(epc-prediction): populate Heating-Control + Ventilation display for predicted properties
2026-06-26 13:45:22 +01:00
Khalim Conn-Kowlessar
ac94a543b1 Set main_heating on the test_validation partial EPC builder 🟩
The heating-donor display synthesis reads donor.epc.main_heating, which has no
dataclass default — so a partial object.__new__ EpcPropertyData must set it.
test_validation's _comparable builder didn't, failing the two leave-one-out
scorer tests in CI (the full epc_prediction suite wasn't run pre-push).
main_heating_controls / sap_ventilation default to None via class attributes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 12:35:01 +00:00
Jun-te Kim
015ea0a293
Merge pull request #1338 from Hestia-Homes/fix/solar-pv-dwelling-roof-cap
fix(modelling): bound Solar PV array to the dwelling's own roof (ADR-0038)
2026-06-26 13:32:55 +01:00
Jun-te Kim
6cbfa5c0ad
Merge pull request #1335 from Hestia-Homes/feature/e2e-modelling-trigger-optional-refetch
Configure whether lodged EPCs are re-fetched and predicted EPCs are re-predicted during e2e modelling
2026-06-26 13:29:55 +01:00
Khalim Conn-Kowlessar
0a2ed67e94 Harden Dwelling-Roof Cap on real data: positional segments, ground-floor basis 🟩
Three corrections found by re-running property 742003 end-to-end:
- roofSegmentStats are POSITIONAL — real responses omit the segmentIndex field
  the fixture happened to carry; key the centre/area lookup by array position.
- Base the cap on ground_floor_area (the footprint the roof covers), not the
  greatest per-storey area; roof_area is the fallback.
- Clamp the basis by total_floor_area: predicted EPCs borrow the structural
  template's geometry (742003: a 118.62 m² MAIN ground floor) decoupled from
  the predicted 55 m² (ADR-0029), so without the clamp the cap reads the
  template's larger footprint.

Result: 742003 plan A/92.4 (16 kWp) -> C/74.4 (6.4 kWp). 29 solar tests +
orchestration threading + products green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 12:24:52 +00:00