Commit graph

6446 commits

Author SHA1 Message Date
Jun-te Kim
ff4a2e4242
Merge pull request #1198 from Hestia-Homes/feature/bill-derivation
Feature/bill derivation
2026-06-12 14:44:30 +01:00
Khalim Conn-Kowlessar
b211715750 feat(modelling): wire secondary-heating-removal into the pipeline (ADR-0028)
Orchestrator runs recommend_secondary_heating_removal; report._triggers_for
explains it via the lodged secondary_heating_type; harness catalogue + ARA seed
price it. Re-pins the golden/integration plans it shifts: it is a cheap (\£250)
SAP lever, so on gas-main certs lodging an electric secondary (691) it displaces
the \£12k ASHP (0330, 0036) or joins the all-beneficial-measures package (000490,
where its marginal SAP is 0 under the category-4 ASHP but the heater is still
physically removed). Consistent with the optimiser's existing kitchen-sink
package behaviour.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 16:04:07 +00:00
Khalim Conn-Kowlessar
797b71cd13 test(modelling): secondary-heating-removal cascade validation (ADR-0028)
Two cascade tests on the worksheet-pinned 001431 build_epc() (the user's
before/after Summary PDFs trip the documented 001431 window-extraction bug, so
the repo's sanctioned 001431 baseline is used instead):
- electric-storage main (code 402) + secondary 691: removal reproduces the
  secondary-removed cert at delta 0 — RdSAP §A.2.2 re-forces a default secondary,
  matching the user's F35→F35 example;
- gas combi main (code 104) + secondary 691: removal strictly raises SAP
  (74.22→77.61) — the Table 11 fraction reallocates to the cheaper main.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 15:32:15 +00:00
Khalim Conn-Kowlessar
f9a89a8e11 docs(adr): secondary heating removal — ADR-0028 + CONTEXT term
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>
2026-06-11 15:32:14 +00:00
Khalim Conn-Kowlessar
0d1ec2228d feat(modelling): cost data for secondary-heating-removal (ADR-0028)
Flat per-dwelling decommission price (sample_catalogue \£250) + 0.25 contingency
(covers unknown heater count / hard-wired-vs-plugged / repaint extent). The JSON
repo joins the contingency from config, proven by the new repo test. No composite
Products machinery — a lodged secondary is one roughly-fixed job, not room-scaled.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 13:51:16 +00:00
Khalim Conn-Kowlessar
ae7959f57c feat(modelling): secondary-heating-removal generator + MeasureType (ADR-0028)
recommend_secondary_heating_removal offers one standalone Option that clears the
lodged secondary system. Eligibility is purely physical (offer iff
sap_heating.secondary_heating_type is set) — no effectiveness gate, since a
lodged secondary is a fixed emitter per RdSAP (portables are ignored), and the
electric-storage §A.2.2 no-op is the Optimiser's call (ADR-0028 decisions 1-2).
Priced at a flat per-dwelling decommission cost, not room-scaled.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 13:35:14 +00:00
Khalim Conn-Kowlessar
9b286e4a22 feat(modelling): SecondaryHeatingOverlay clears the lodged secondary (ADR-0028)
The first overlay surface that sets fields to *absent* rather than to a
target state: _fold_secondary_heating clears sap_heating.secondary_heating_type
+ secondary_fuel_type, so the calculator's Table 11 secondary-fraction split
(SAP 10.2 §9a) routes 100% of space heating to the main. On an electric-storage
main RdSAP §A.2.2 re-forces a default secondary, making removal a no-op there —
left to the Optimiser to de-select (ADR-0028 decisions 2-3).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 13:13:20 +00:00
Daniel Roth
69d5968d77
Merge pull request #1215 from Hestia-Homes/feature/audit-generator/switch-template
Handle up to 50 rows in new template audit spreadsheet
2026-06-11 10:55:43 +01:00
Daniel Roth
daac080506 Merge branch 'main' into feature/audit-generator/switch-template 2026-06-11 09:51:47 +00:00
Daniel Roth
2b52cf5ef8
Merge pull request #1214 from Hestia-Homes/feature/hs-last_submission_date
Get last_submission_date from hubspot
2026-06-11 10:51:16 +01:00
Daniel Roth
f3cbfd8275 fix broken unit test 2026-06-11 09:40:09 +00:00
Daniel Roth
9a42eaf243 empty commit to trigger workflows 2026-06-11 09:32:14 +00:00
Khalim Conn-Kowlessar
6ce6e89de1 feat(modelling): gate boiler upgrade on the existing boiler's efficiency
Don't offer a like-for-like gas boiler swap to a dwelling whose existing gas
boiler is already at least as efficient as the new condensing boiler (SAP 10.2
Table 4b codes 102/104 = 84% winter) — it gains nothing, and the dwelling gets
the tune-up (cylinder + controls) instead. `_already_condensing` compares the
existing code's Table 4b winter efficiency to 84%; a non-Table-4b code (solid
fuel) has no comparable efficiency and is never treated as already-condensing.

The gate is GAS-ONLY: a non-gas boiler → gas is a fuel switch whose value (cost
/ carbon) is not captured by winter efficiency, so oil/LPG/coal → gas is never
suppressed on efficiency grounds (only gated on the mains-gas connection).

This correctly demotes the gas-with-cylinder example (cert lodges code 114
"Regular, condensing", 84% winter) to a tune-up case — confirming that 114→102
is ~0 boiler-efficiency gain in both our calc and Elmhurst (both Table 4b 84%);
Elmhurst's uplift there came from the cylinder + flue, not the boiler. The
boiler-with-cylinder overlay stays validated by the lpg pin (code 115, non-
condensing + cylinder) and by recasting the 114 fixtures' code to a pre-1998
non-condensing boiler (110) in the boiler tests — the overlay overwrites the
code to 102 regardless, so only eligibility changes, not the delta-0 result.
New tests: an already-condensing gas boiler yields no boiler upgrade (but a
tune-up); an oil condensing boiler is not gated (the fuel switch survives).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 07:15:58 +00:00
Khalim Conn-Kowlessar
ae7e6a0c42 feat(modelling): composite per-dwelling boiler + tune-up costing (ADR-0027)
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>
2026-06-10 19:41:06 +00:00
Daniel Roth
123c917d25 add additional empty rows to template 2026-06-10 16:37:53 +00:00
Daniel Roth
d571ccaee5 remove number of address2uprn tests 2026-06-10 16:26:54 +00:00
Daniel Roth
e55c2262c8 handle 50 rows in new template file 2026-06-10 16:18:46 +00:00
Daniel Roth
26d34c345c get last_submission_date from hubspot 2026-06-10 14:45:25 +00:00
Daniel Roth
64e20ebb91
Merge pull request #1212 from Hestia-Homes/feature/audit-generator/switch-template
Use new template for ventilation audit
2026-06-10 14:40:14 +01:00
Daniel Roth
a2a9566ef2 delete previous template 2026-06-10 13:34:05 +00:00
Daniel Roth
8976f55636 add todo comment for named ranges 2026-06-10 13:32:11 +00:00
Daniel Roth
131b0884c7 update template file 2026-06-10 13:26:56 +00:00
Daniel Roth
0edeeaefa6 populate_sheet writes to new Sero template column layout 🟩 2026-06-10 13:21:50 +00:00
Daniel Roth
e7954ad83a
Merge pull request #1210 from Hestia-Homes/bug/audit-generator-deploy-fix
Update subtask_handler to work for handlers where TaskOrchestrator is optional
2026-06-10 14:04:20 +01:00
Daniel Roth
921dc5108b revert pytest.ini 2026-06-10 12:59:06 +00:00
Daniel Roth
44b4882841 Merge branch 'main' into bug/audit-generator-deploy-fix 2026-06-10 12:53:18 +00:00
Daniel Roth
8ff58bd645 tell subtask_handler whether to send TaskOrchestrator to handler, defaulting to True 2026-06-10 12:43:24 +00:00
Khalim Conn-Kowlessar
07f534ee11 feat(modelling): system tune-up options (standard + zone controls)
Add the system tune-up to the heating Recommendation: keep the existing wet
boiler but install better heating controls and fix the cylinder. Two competing
Options (the Optimiser picks <=1 across the whole heating rec) per the user's
two best control end-states:

- system_tune_up        — standard controls (programmer + room thermostat +
  TRVs, SAP 10.2 Table 4e code 2106)
- system_tune_up_zoned  — time-and-temperature zone control (code 2110, type 3):
  more SAP uplift for more cost

Both keep the boiler (no fuel / SAP code / flue change), set the control
ABSOLUTELY to their end-state, and apply the conditional cylinder fixes (an
80 mm jacket when under-insulated, a thermostat when absent — only when a
cylinder exists). Each control option is offered only when it genuinely improves
the existing control — standard is skipped when the control is already 2106 /
2110 / 2112, zone when already 2110 / 2112 — so neither is ever a downgrade or a
no-op.

Validated against the Elmhurst "system tune up" re-lodgements (cert 001431):
nine befores spanning controls 2101-2113 all converge to the two common afters,
proving the control overlay is absolute. The cascade pin is parametrised over
two starting controls (2101 "no control" + 2113 "room thermostat and TRVs") x
both afters, delta 0 (SAP/CO2/PE).

Wires the two MeasureTypes through contingencies (0.15), the offline catalogue
(500 / 900), the catalogue-coverage list, the report triggers, and the ARA
first-run seed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 10:20:46 +00:00
Daniel Roth
3ceafe8b49
Merge pull request #1208 from Hestia-Homes/bug/audit-generator-task-orchestrator
Pass unused TaskOrchestrator to audit generator handler to avoid TypeError
2026-06-10 10:36:18 +01:00
Daniel Roth
5c4999aade unused task orchestrator in handler signature 2026-06-10 09:30:38 +00:00
Daniel Roth
2c605f80ca pass task orchestrator to handler 2026-06-10 09:29:46 +00:00
Khalim Conn-Kowlessar
3d108a9d9e feat(harness): explain gas_boiler_upgrade in the report triggers
Add the `gas_boiler_upgrade` branch to `report._triggers_for`, mirroring the
generator's eligibility guard so a cohort report explains why the boiler upgrade
fired: the wet-boiler SAP code, the mains-gas connection that makes the gas
end-state installable, and the cylinder presence that shapes the bundle (combi
vs regular + cylinder fixes).

No golden API cert selects the boiler upgrade (it competes with — and on houses
loses to — the ASHP bundle within the one heating Recommendation), so the branch
is covered by a direct `_triggers_for` unit test, following the repo pattern for
testing internal helpers (cert_to_inputs).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 08:44:37 +00:00
Daniel Roth
bb1c8c88ce Two-parameter subtask handler completes without TypeError 🟩 2026-06-10 08:40:32 +00:00
Khalim Conn-Kowlessar
2413bc87da feat(modelling): solid-fuel(coal)->gas boiler upgrade + boiler_flue_type end-state
Pin the coal-boiler-with-cylinder upgrade and add the `boiler_flue_type`
end-state field. A solid-fuel (coal) boiler (fuel 11, SAP code 153) on a
mains-gas street converts to a gas condensing boiler (fuel 11->26, code 102) —
the non-gas->gas path for a solid-fuel system, eligible because code 153 is in
the wet-boiler solid-fuel range 151-161 and mains gas is present.

New `boiler_flue_type` HeatingOverlay field, routed to main_heating_details[0]
and set to 2 (room-sealed/balanced) on both boiler shapes: every relodged after
lodges flue type 2, but coal's before lodged none. The field is SAP-inert (the
cascade score is unchanged by it), so it is written purely for end-state
fidelity — the overlay now represents the installed condensing boiler's flue.
Validated via the overlay-equality unit tests.

The coal after predates the user-locked "always add a cylinder thermostat when
absent" rule, so it stale-lodged thermostat 'N'; the pin corrects it to the
rule's end-state 'Y' in-test (the gas with-cylinder after got the same
correction by re-lodging). The cylinder is already 80 mm insulated, so the
jacket is skipped and only the thermostat is added; controls (2106) are
unchanged. Cascade-pinned delta 0 (SAP/CO2/PE).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 08:27:07 +00:00
Daniel Roth
51cf545776 Two-parameter subtask handler completes without TypeError 🟥 2026-06-10 08:22:43 +00:00
Khalim Conn-Kowlessar
7bc9797a26 test(modelling): pin non-gas->gas + already-insulated-cylinder boiler upgrades
Two more boiler-upgrade cascade pins, validating the existing generator across
fuels and cylinder states (no source change):

- oil combi: an oil boiler (fuel 28, code 130) on a mains-gas street converts to
  a gas condensing combi (fuel 28->26, code 104). Proves the non-gas -> gas
  conversion gated on a mains-gas connection (ADR-0024 revised).
- already-insulated cylinder: a gas boiler heating a pre-jacketed cylinder
  (type 2 / 80 mm, no thermostat) gets a new boiler + a thermostat, with the
  jacket NOT re-applied. Proves the cylinder path's skip-jacket branch against a
  real cert. (Sourced from an LPG re-lodgement whose fuel the Summary mapper
  reads as mains gas 26 — a separate LPG fuel-mapping gap, noted in the test.)

Both pin delta 0 (SAP/CO2/PE) against the relodged after.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 16:37:32 +00:00
Khalim Conn-Kowlessar
63dd69ff8b feat(modelling): gas combi boiler upgrade + controls-when-inadequate
Extend the gas-boiler-upgrade Option to combi (no-cylinder) dwellings and add
the controls upgrade shared by both boiler shapes. A dwelling has a cylinder or
it does not, so the one `gas_boiler_upgrade` Option is shaped per dwelling:

- no cylinder -> a gas condensing combi (Table 4b code 104), no cylinder fields
  touched;
- a cylinder  -> a regular boiler (code 102) heating it, with the conditional
  cylinder jacket/thermostat (slice 1).

Controls: bring an inadequate boiler control up to full programmer + room
thermostat + TRVs (SAP 10.2 Table 4e Group 1 code 2106). "Inadequate" = the
Group-1 codes with NO room thermostat (2101, 2102, 2107, 2108, 2109, 2111) —
these lack boiler interlock (Table 4c(2) / footnote c) p.171), so adding a room
thermostat genuinely improves SAP. Room-thermostatted (2103/2104/2105/2106/2113)
or better zone controls (2110/2112) are left unchanged — never downgraded, so
no phantom uplift. The with-cylinder cert (control 2106) is therefore untouched
and its pin still holds at delta 0.

Validated by the combi before/after re-lodgement (cert 001431, gas boiler
upgrade - no cylinder): control 2111 "TRVs and bypass" -> 2106, fan flue
False->True, SAP code 112 -> 104. Cascade-pinned delta 0 (SAP/CO2/PE). Removed
the slice-1 placeholder test asserting no boiler Option fires without a cylinder
(the combi Option now correctly fires there).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 16:28:00 +00:00
Khalim Conn-Kowlessar
31c74ab500 feat(modelling): gas-boiler-upgrade-with-cylinder option in the heating rec
Add the first boiler-upgrade option to the single "Heating & Hot Water"
Recommendation (ADR-0024 expansion): a dwelling whose existing wet boiler heats
a hot-water cylinder is offered a new gas condensing boiler, with the cylinder
jacketed when under-insulated and given a thermostat when absent. One competing
Option (the Optimiser picks <=1), folded into one composite Plan line.

The end-state is read from the Elmhurst before/after re-lodgements (cert 001431,
gas boiler upgrade - with cylinder), which REVISE ADR-0024:

- Target is always a gas condensing boiler, not fuel-preserving: every after
  lodges fuel 26. Gas->gas always; a non-gas wet boiler ->gas only with a
  mains-gas connection; electric boilers are left alone (electrification is the
  upgrade path). Eligibility = wet-boiler SAP code (Table 4a/4b 101-141 /
  151-161 / 191-196) + not an electric boiler + mains gas present.
- End-state is a Table 4b SAP code, not a PCDB index: code 102 (regular boiler
  + cylinder). The calculator derives the condensing seasonal efficiency from
  the code, so no efficiency input exists or is needed.
- A modern condensing boiler has a fanned flue: the after flips
  `fan_flue_present` False->True on every cert (SAP 10.2 Table 4f flue-fan +
  the Table 4b condensing-efficiency basis). Added as a new HeatingOverlay
  field, routed to main_heating_details[0].
- Cylinder thermostat is always added when absent (user-locked); the jacket is
  the 80 mm `cylinder_insulation_type=2` end-state, applied only when the
  cylinder is below 80 mm (never downgrading a better one). Both are conditional
  per-dwelling components, not a frozen overlay.

Cascade-pinned delta-0 (SAP/CO2/PE) against the relodged after via
`_assert_overlay_reproduces_after`. NB the absolute SAP on this dwelling is
subject to a separate Summary-path mapper roof-fidelity gap (we read the roof
better-insulated than Elmhurst, scoring ~75 vs the printed 56); the gap is
identical on before+after (the boiler measure never touches the roof) so it
cancels and the pin still proves the exact heating field-delta. Tracked on the
calculator branch.

Wires the new `gas_boiler_upgrade` MeasureType through contingencies (0.26),
the offline sample catalogue, the catalogue-coverage list, and the ARA
first-run integration seed (the option fires on any mains-gas boiler+cylinder
dwelling).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 16:16:46 +00:00
Daniel Roth
f6a4b0f722
Merge pull request #1206 from Hestia-Homes/bug/audit-generator-deploy-fix
Add missing variable to lambda variables
2026-06-09 16:39:34 +01:00
Daniel Roth
c1dda41857 add missing variable to lambda variables 2026-06-09 15:33:35 +00:00
Daniel Roth
63b1fa08a8
Merge pull request #1204 from Hestia-Homes/feature/generate-ventilation-audit-from-magicplan
New application to generate ventilation audit file for a given hubspot deal ID
2026-06-09 16:25:12 +01:00
Daniel Roth
dcd5204b54 put db engine construction inside handler to avoid import errors in test 2026-06-09 15:18:42 +00:00
Daniel Roth
236f33c25f move spreadsheet population logic to domain 2026-06-09 14:43:24 +00:00
Daniel Roth
48a413a5e2 correct imports in postgres repo 2026-06-09 14:25:29 +00:00
Daniel Roth
94f8ef5458 Merge branch 'main' into feature/generate-ventilation-audit-from-magicplan 2026-06-09 14:22:57 +00:00
Daniel Roth
163703285f deployment 2026-06-09 14:15:52 +00:00
Daniel Roth
b9eb23f6df allow write to real s3 when running locally 2026-06-09 13:52:39 +00:00
Daniel Roth
608006d977 reapply conditional formatting to populated file 2026-06-09 13:29:11 +00:00
Daniel Roth
f8c955b2d3 local runner and correct template path 2026-06-09 13:03:05 +00:00
Daniel Roth
e010fa1b40 remove redundant files 2026-06-09 12:52:04 +00:00