Commit graph

698 commits

Author SHA1 Message Date
Khalim Conn-Kowlessar
2d6479536c test(accuracy): ratchet SAP MAE 0.740 -> 0.726 after HP PSR-extension 🟪
The heat-pump PSR-extension fix (SAP 10.2 Appendix N2) drops corpus MAE from
0.740 to 0.726 on the 1000-cert RdSAP-21.0.1 sample; within-0.5 holds at
74.1%. Tighten the ceiling and log the slice.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 15:16:07 +00:00
Khalim Conn-Kowlessar
3d93c7b7d5 test(pcdb): PSR-extension toward 100% above largest / below smallest 🟥
SAP 10.2 Appendix N2 (PDF p.101, footnote 44/45): for an air/ground/water
source heat pump whose plant size ratio exceeds the record's largest PSR,
the efficiency is reciprocal-interpolated between the largest-PSR value and
100% at twice the largest PSR (100% beyond that); below the smallest PSR the
efficiency is 100%. Our interpolator instead clamps to the top/bottom row.

Anchored to the accredited Elmhurst worksheet for cert 100110101713 (golden
fixture case 56, PCDB 100061): PSR 3.10665 over the record's largest 2.0
gives eta_space,1 = 147.011 -> (206) = 0.95 x 147.011 = 139.660, vs the
clamped 352.0 -> 334.4% that over-rates the dwelling by +18 SAP.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 15:08:15 +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
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
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
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
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
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
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
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
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
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
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
Khalim Conn-Kowlessar
904c205e3a Dwelling-Roof Cap bounds the PV array to the dwelling's own roof 🟥
select_conservative_configs must accept the dwelling's roof area and cap panels
to its usable roof (ADR-0038) — bounding a 55m² dwelling to ~16 panels under
Google footprint conflation, while staying a no-op on correctly-matched homes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 12:15:28 +00:00
Khalim Conn-Kowlessar
cf2780ed77 SolarPotential carries panel dims + per-segment centre/area 🟥
The Dwelling-Roof Cap (ADR-0038) sizes by usable roof area and ranks segments
by distance from the dwelling, so the projection must carry each panel's
footprint and each segment's centre + area (from roofSegmentStats).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 12:12:12 +00:00
Khalim Conn-Kowlessar
80c5ad0c6c Predict ventilation kind from the cohort mode 🟥
Prediction never synthesises ventilation — it keeps the size-template's
sap_ventilation, so a predicted dwelling in an MEV/MVHR neighbourhood is scored
+ displayed as natural (predicted property 721167 follow-up). Mode the
mechanical_ventilation_kind across the cohort like glazing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 10:49:37 +00:00
Khalim Conn-Kowlessar
5cf5b67420 Carry donor's display heating + control into predicted EPC 🟥
_apply_heating_donor copies the donor's calc sap_heating but leaves the
display rows (main_heating, main_heating_controls) on the structural template
— incoherent, and 'Heating Control: Unknown' when the template lodged no
control (predicted property 721167, ADR-0029 follow-up).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 10:37:36 +00:00
Daniel Roth
17a9f0aafc refetch_epc=False skips API entirely; EPC-less properties go straight to prediction path
When refetch_epc=False and no stored lodged EPC exists, the handler no longer
falls back to a live EPC API call — it treats the property as EPC-less and
hands it to the prediction path. This keeps REFETCH_EPC (lodged path) and
REPREDICT_EPC (prediction path) cleanly independent.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-26 10:24:27 +00:00
Daniel Roth
100a580119 Add tests for repredict_epc flag routing via stored predicted EPC 🟩
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-26 10:15:40 +00:00
Daniel Roth
a940c94b33 Handler pre-fetches stored EPCs and routes per-property via refetch_epc flag 🟩
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-26 10:13:51 +00:00
Daniel Roth
c51ca47467 Rename no_solar → refetch_solar and add refetch_epc, repredict_epc flags to TriggerBody 🟩
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-26 10:12:21 +00:00
Daniel Roth
04b3cb240a
Merge pull request #1333 from Hestia-Homes/fix/baseline-downgrades
fix(epc-mapper): complete full-SAP 17.1 mapper so pre-SAP10 certs rebaseline (no more impossible downgrade)
2026-06-26 10:44:52 +01:00
Daniel Roth
d77232c333
Merge pull request #1332 from Hestia-Homes/fix/solar-429-concurrency
fix(solar): throttle per-container Solar calls to hold the fleet under 600 QPM
2026-06-26 10:44:47 +01:00
Khalim Conn-Kowlessar
e405928516 Re-baseline prediction component-accuracy gate for full-SAP donors (ADR-0037)
Full-SAP certs mapped property_type=None, so the hard cohort filter silently
excluded them as comparables. Correctly typing them admits real lodged EPCs as
donors — a ground-truth-method change (cf #1245). Net over the n=36 fixture: 16
components better, 4 worse, 6 unchanged; gains concentrated in the physical
characteristics full-SAP certs measure (window_count 3.83->1.69, building_parts,
total_window_area, floor_construction, construction_age_band, glazing, walls).
The 4 that fell are new-build-vs-old-stock service mismatch on 1-2 targets each
(heating/water fuel, cylinder insulation) + floor_area. Tighten 16, loosen 4.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 09:34:27 +00:00
Khalim Conn-Kowlessar
f568ec00fc Full-SAP certs rebaseline off the lodged SAP-2012 value (downgrade fix) 🟩
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 20:43:31 +00:00
Khalim Conn-Kowlessar
85e203f366 Resolve the Solar throttle gap from env with a 32-wide fallback 🟥
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 16:03:42 +00:00
Daniel Roth
a19b3dada1 _secondary_heating_fraction_for_category returns 0.10 for category 11 (electric ceiling heating) 🟥
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-25 15:59:14 +00:00
Khalim Conn-Kowlessar
48a47590e9 Pace Solar calls per container to stay under the 600 QPM fleet cap 🟥
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 15:59:00 +00:00
Khalim Conn-Kowlessar
440159e7f4 Harden Solar API client against 429s: jitter, Retry-After, bounded backoff 🟩
Route the Google Solar client through the shared call_with_retry with
full jitter (de-synchronises the 32 concurrent containers per Google's
"avoid synchronised requests" guidance), honouring Retry-After, a 60s max
backoff (rides out the 600 QPM per-minute window), and 6 bounded retries.
429/5xx/transport errors are transient; other 4xx propagate immediately;
404-entity-not-found stays BuildingInsightsNotFoundError. On exhaustion a
TransientHttpError surfaces so the subtask fails and is re-triggered (no
silent degrade).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 10:51:23 +00:00
Khalim Conn-Kowlessar
88ed0c2e88 Add opt-in full-jitter backoff to de-synchronise concurrent retries 🟩
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 10:45:00 +00:00
Jun-te Kim
77133f4b42
Merge pull request #1321 from Hestia-Homes/fix/off-peak-bill-day-night-split
Fix/off peak bill day night split
2026-06-24 20:35:03 +01:00
Jun-te Kim
b1ff711260 perf(modelling_e2e): batch SubTask bookkeeping to stop per-property writes
Even after batching the data writes, the handler still wrote to the DB per
property through the orchestrator's SubTask bookkeeping: create + start +
complete each self-committed, and _cascade re-listed every sibling and re-saved
the parent on every transition — ~5 writes per property plus an O(N^2) cascade.

- TaskOrchestrator.run_subtasks: create all children in one INSERT, run each
  (failures isolated per child), then persist all terminal states in one bulk
  save and cascade the parent once. Children go WAITING -> terminal; the
  transient IN_PROGRESS row is never written.
- SubTaskRepository.create_many / save_many (bulk INSERT / bulk fetch + update).
- _cascade short-circuits when the Task is already FAILED (terminal) — skips the
  sibling roll-up entirely.
- modelling_e2e handler fans out via run_subtasks instead of per-property
  create_child_subtask + run_subtask.

Per N-property batch the SubTask bookkeeping drops from ~5N writes + an O(N^2)
cascade to ~2 writes + 1 cascade.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 19:26:42 +00:00
Jun-te Kim
de5e9a2362 perf(modelling_e2e): bulk reads and batch writes to cut RDS load
The handler fired ~2+2N read round-trips and N+N write transactions per
SQS batch, pinning RDS CPU under ~32 concurrent containers on pool_size=1.

Reads: merge the duplicate property query and add overrides_for_many /
SolarRepository.get_many so overrides, solar, and property rows each load
in one query (2+2N -> 3).

Writes: buffer each modelled property's persistence intent in memory
(_PropertyWrite) during the loop, then flush the whole batch in one
PostgresUnitOfWork with a single commit, and run the baseline orchestrator
once for all written ids (N+N -> 2 transactions). Per-property modelling
failures stay isolated in the loop; the batch write is all-or-nothing and
retried via SQS (saves are idempotent upserts).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 18:43:42 +00:00
Khalim Conn-Kowlessar
1da86a6366 Pin end-to-end off-peak day/night bill regression 🟩
An Economy-7 storage dwelling now prices heating at the 0.20-day/0.80-
night blend through cert -> calculator -> bill, instead of raising
UnpricedFuel and aborting the modelling_e2e batch.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 18:05:33 +00:00
Khalim Conn-Kowlessar
81d5429b60 Source every end use's off-peak high-rate fraction from the cert 🟩
Surface the hot-water (Table 13 / HP-DHW), secondary (direct-acting),
main-2 and ALL_OTHER_USES High-Rate Fractions on CalculatorInputs from
the same Table 12a helpers the SAP cost path uses, so Bill Derivation's
day/night split matches the rating's exactly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 17:57:23 +00:00
Khalim Conn-Kowlessar
b58e27f46e Source the off-peak meter flag and main-heating fraction from the cert 🟩
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 17:52:39 +00:00
Khalim Conn-Kowlessar
3f5cd550cb Thread the off-peak meter flag and high-rate fractions onto SapResult 🟩
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 17:45:29 +00:00
Khalim Conn-Kowlessar
d3a4426ca4 Lock whole-meter off-peak routing and standard-meter no-op 🟩
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 17:39:58 +00:00