Commit graph

8174 commits

Author SHA1 Message Date
Khalim Conn-Kowlessar
e0b189aa6d Correct two pashub-verified pre_sap values in the accuracy manifest
Manually verified in pashub (Khalim, 2026-07-14): deal 499584755922
(9 Council Houses, CW5 8AP) is F33, not the stale D67 — our computed 34.0
was right within a point; deal 499617935574 (Brightholme, 14 North Road)
is the accredited E43, not the preliminary 53. 58 Hackle Street (D58) and
16 Bingley Close (E52) were confirmed correct, so our +7.8/+5.7 deviations
there are extraction bugs to hunt next. Harness MAE 1.684 -> 1.551; ceiling
ratcheted to 1.56.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 22:42:03 +00:00
Khalim Conn-Kowlessar
f45651dbc0 Lodge the heat-network SAP code and Table 12 community fuel for PasHub community heating 🟩
The extractor now captures "Heating System (Other):" and the mapper resolves
it to the Table 4a code (301, community boilers) + Table 12 community fuel
(51 via the shared _resolve_community_heating_fuel_code), so
is_heat_network_main routes the DLF / standing-charge / 80%-efficiency
branch instead of pricing the dwelling as an ordinary mains-gas boiler
(fixture 507644414148: +14.3 over-rate closes by ~8.6). Harness MAE
1.726 -> 1.684; ceiling ratcheted to 1.70.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 22:34:50 +00:00
Khalim Conn-Kowlessar
4d60d79cc9 Extract the PasHub community heat-source label 🟥
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 22:33:08 +00:00
Khalim Conn-Kowlessar
f9d8d33147 Lodge the heat-network SAP code and Table 12 community fuel for PasHub community heating 🟥
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 22:30:28 +00:00
Khalim Conn-Kowlessar
6f1ba4be25 Bill room-in-roof surfaces with the surveyed roof insulation, stud-wall commons 🟩
Two dig-confirmed defects in the fresh RIR mapping (499617935574 overshot
-9): the roof-space-level insulation (Joists/200mm) was never threaded into
the RIR surfaces, so slopes/commons/flat ceilings fell to the Table 18
"unknown" full-uninsulated 2.30 default; and common walls were mislabeled
"common_wall" (a Simplified-Type-2-only concept) instead of the Detailed-mode
stud_wall every worksheet-validated fixture uses. 44.0 -> ~50.3 on the target
fixture; harness MAE 1.748 -> 1.726.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 22:29:20 +00:00
Khalim Conn-Kowlessar
25c3f0b2a0 Bill room-in-roof surfaces with the surveyed roof insulation, stud-wall commons 🟥
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 22:28:10 +00:00
Khalim Conn-Kowlessar
955b4dc1e8 Extract the PasHub detailed room-in-roof block 🟩
_parse_room_in_roof captures the RdSAP §3.10 block (age range, floor area,
gables with Exposed/Party type, slopes, common walls, flat ceiling as
length x height) on main and extension roof spaces; both part builders now
carry it to sap_room_in_roof (issue #1590 bug 5 — closes the last #1590
subtask). Harness MAE 1.748 (754917 corrects to pashub's own pre-SAP; RIR
surfaces now billed per-surface instead of as insulated loft).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 22:09:44 +00:00
Khalim Conn-Kowlessar
518c682793 Extract the PasHub detailed room-in-roof block 🟥
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 22:05:31 +00:00
Khalim Conn-Kowlessar
f067f94ec9 Carry room_in_roof on extension roof spaces too (pyright union access) 🟩
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 22:04:33 +00:00
Khalim Conn-Kowlessar
0ca5108584 Carry the surveyed room-in-roof block onto the SAP building part 🟩
_pashub_room_in_roof mirrors _api_build_room_in_roof: every surveyed RIR
surface (gable typed Exposed/Party per Table 4, slopes, common walls, flat
ceiling) becomes a detailed_surfaces entry of length x height, so the
cascade bills exact RIR surfaces instead of treating the shell as
well-insulated loft (issue #1590 bug 5).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 22:01:30 +00:00
Khalim Conn-Kowlessar
9599fd8239 Carry the surveyed room-in-roof block onto the SAP building part 🟥
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 21:59:41 +00:00
Khalim Conn-Kowlessar
895df42afe Keep a PasHub system-build wall off the basement cascade 🟩
PasHub surveys never lodge a basement wall, but "System Build (i.e Any
Other)" maps to wall code 6 — the gov-API basement sentinel — and with
wall_is_basement left None the dwelling silently routed through the basement
wall/floor U-value cascade (issue #1590 bug 7). Both part builders now pin
the flag False. Harness MAE 1.732 -> 1.727.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 21:56:27 +00:00
Khalim Conn-Kowlessar
5980b2d6ed Keep a PasHub system-build wall off the basement cascade 🟥
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 21:53:21 +00:00
Khalim Conn-Kowlessar
de15bd1ed7 Resolve the PasHub Cylinder Size label to the SAP10 Table 28 enum 🟩
"Normal (90-130 litres)"->2, "Large (>170 litres)"->4; "No Access" follows
RdSAP 10 Table 28 p.55 (otherwise-branch 110L for a main-fed cylinder;
electric-immersion No Access strict-raises until meter context is plumbed).
The raw band string was int-or-noned by _cylinder_volume_l_from_code, skipping
the Table 28 volume convention (issue #1590 bug 6). Golden block updated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 21:52:32 +00:00
Khalim Conn-Kowlessar
2949460e6a Resolve the PasHub Cylinder Size label to the SAP10 Table 28 enum 🟥
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 21:48:47 +00:00
Khalim Conn-Kowlessar
090e16ddc2 Dispatch PasHub mechanical ventilation into the §2 ACH cascade 🟩
The surveyed "Ventilation type" label never reached
mechanical_ventilation_kind, so ventilation_from_cert silently defaulted every
mechanical system to NATURAL (24d), dropping its ACH heat loss — 76/205
cohort fixtures lodge dMEV/MEV/PIV (issue #1590 bug 4). Mapping mirrors the
gov-API _API_MECHANICAL_VENTILATION_TO_KIND table (PIV-from-loft is
"as natural"). Harness: within-0.5 12.2% -> 18.5%, MAE 2.538 -> 1.730 — the
campaign's largest single move; floors ratcheted to 0.18 / 1.75.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 21:45:24 +00:00
Khalim Conn-Kowlessar
5220b51961 Dispatch PasHub mechanical ventilation into the §2 ACH cascade 🟥
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 21:43:00 +00:00
Khalim Conn-Kowlessar
56b36ce7f0 Resolve the PasHub PV Connection label to the gov-API credit-gate int 🟩
"Not connected to electricity meter" -> 1 (zero Appendix M credit), "Connected
to dwellings electricity meter" -> 2; the raw string fell through the credit
gate's non-int branch to True, crediting a separately-metered array (issue
#1590 bug 3, fixture 499516101839 -6.9 SAP). Harness MAE 2.571 -> 2.538;
ceiling ratcheted to 2.55.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 21:41:04 +00:00
Khalim Conn-Kowlessar
f9079eb0ac Resolve the PasHub PV Connection label to the gov-API credit-gate int 🟥
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 21:39:08 +00:00
Khalim Conn-Kowlessar
f92d4f26b8 Extract the PasHub per-system PV block and diverter flag 🟩
"Photovoltaic array kWp Known? Yes" lodges a per-system block (kWp, pitch,
orientation, overshading) instead of the percent-roof estimate; the extractor
only read the estimate, so measured PV never reached the mapper and the
Appendix M credit was silently zero (issue #1590 bug 1). Harness MAE
2.632 -> 2.571 (fixture 507639151843's 13.5-SAP under-rate closes to +0.5);
ceiling ratcheted to 2.58.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 21:38:07 +00:00
Khalim Conn-Kowlessar
80de9b0c04 Extract the PasHub per-system PV block and diverter flag 🟥
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 21:35:34 +00:00
Khalim Conn-Kowlessar
1282f81a72 Map surveyed PasHub PV arrays into the Appendix M photovoltaic_arrays input 🟩
_pashub_pv_arrays mirrors _elmhurst_pv_arrays, reusing its format-agnostic
pitch/orientation/overshading converters (PasHub lodges "South East"
space-separated; normalise to the octant map's hyphenated keys). Wired into
the SapEnergySource construction so the calculator's Appendix M generation
path sees measured PV instead of silently zero credit (issue #1590 bug 1).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 21:33:18 +00:00
Khalim Conn-Kowlessar
9a1422f6d7 Map surveyed PasHub PV arrays into the Appendix M photovoltaic_arrays input 🟥
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 21:31:08 +00:00
Khalim Conn-Kowlessar
bc58cf4930 Lodge explicit zero roof insulation for a surveyed "Insulation At: None" 🟩
"Insulation At: None" is the assessor recording zero loft insulation, not an
unknown — with thickness left None, u_roof fell through to the age-band
"assumed insulated" default (0.16 W/m²K at band J/K) instead of the Table 16
uninsulated row (2.30), understating roof loss ~14x (issue #1590 bug 2).
Harness MAE 2.701 -> 2.632 (the two ~7-SAP roof over-raters correct exactly);
ceiling ratcheted to 2.64.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 21:28:25 +00:00
Khalim Conn-Kowlessar
66e0adf619 Lodge explicit zero roof insulation for a surveyed "Insulation At: None" 🟥
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 21:26:42 +00:00
Khalim Conn-Kowlessar
178af2f044 Add local modelling runner + portfolio-838 problem-properties handover
scripts/run_pashub_modelling.py invokes the modelling_e2e handler in-process
over a portfolio using STORED EPCs (refetch_epc=False — the correct source for
PasHub cohorts, issue #1589), parametrised by env (portfolio/scenario/pids/
dry-run/batch). Handover documents the 3 re-extraction stragglers, the
rebaseliner pass-through gotcha (effective_sap_score IS the pashub rating for
unchanged lodged EPCs — never validate the calculator against it), and the 7
root-caused extractor bugs now tracked in #1590.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 21:24:37 +00:00
Khalim Conn-Kowlessar
999008f9d0 Refresh pashub accuracy manifest pre_sap from the corrected DB rows
Two hubspot_deal_data corrections: 499539910905 D58->B82 (confirmed bad row)
and one C73->C75. Rebuilt via scripts/build_pashub_accuracy_fixtures.py
(no --force; PDFs unchanged).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 21:24:37 +00:00
Khalim Conn-Kowlessar
cab7e3d613 Close the last PasHub mapper gaps: secondary fuel + Group 3 heat-network control 🟩
Secondary fuel 'Mains gas'/'Mains Gas'->26 and 'House Coal'->11 (Table 32);
main-heating control resolution becomes group-aware with a Table 4e Group 3
map ('Charging system linked to use of community heating, room thermostat
only' -> 2306, the code the accredited Elmhurst certs lodge for this cohort;
all four linked-to-use codes are calculation-identical). All 205 Guinness
fixtures now compute (was 201/4 xfails); ratchet re-baselined to the
full-coverage aggregate (12.2% within-0.5 floor, MAE ceiling 2.71) with the
two newly-computable outliers documented for verification.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 21:24:36 +00:00
Daniel Roth
533f33d62b
Merge pull request #1581 from Hestia-Homes/feature/pashub-map-main-heating-control-1557
PasHub mapper: resolve main_heating_control label → SAP Table 4e code (#1557)
2026-07-14 19:03:25 +01:00
Daniel Roth
ecb113b52e Re-ratchet PasHub SAP-accuracy floor to the current integrated aggregate (12.4% within-0.5) 🟩
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 17:57:34 +00:00
Daniel Roth
328887c4d3 Merge branch 'main' into feature/pashub-map-main-heating-control-1557 2026-07-14 17:53:04 +00:00
KhalimCK
8eb2649b9b
Merge pull request #1587 from Hestia-Homes/worktree-issue-1566-roof-construction-country
Set roof_construction_type and country_code on the PasHub site-notes path
2026-07-14 18:51:10 +01:00
KhalimCK
6be73555c6
Merge pull request #1586 from Hestia-Homes/feat/pashub-heating-efficiency-1563
PAS Hub mapper: boiler PCDB product_id → main_heating_index_number (#1563)
2026-07-14 18:50:37 +01:00
Daniel Roth
df3fce0a3f
Merge branch 'main' into feature/pashub-map-main-heating-control-1557 2026-07-14 18:50:08 +01:00
Daniel Roth
fdcf29a057 Resolve PasHub control labels only for boiler systems, strict-raise otherwise 🟩
Table 4e is organised by heating-system family and the same control label
recurs across groups under different 2xxx codes, so the boiler (Group 1) map is
gated behind a boiler system_type; any other system strict-raises rather than
silently taking a boiler code. Adds a guard that every mapped code exists in the
calculator's _CONTROL_TYPE_BY_CODE inventory.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 17:49:18 +00:00
Daniel Roth
6c61055e6c
Merge pull request #1585 from Hestia-Homes/feat/pashub-residual-main-fuel-1558
PAS Hub: code residual main-fuel labels (Bulk LPG + Fuel: Mains Gas) (#1558)
2026-07-14 18:49:17 +01:00
Daniel Roth
bf550fdfdc
Merge branch 'main' into feat/pashub-residual-main-fuel-1558 2026-07-14 18:48:40 +01:00
Daniel Roth
313ae5253a Strict-raise a PasHub control label on a non-boiler system 🟥
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 17:46:23 +00:00
Daniel Roth
84f432a44c
Merge pull request #1582 from Hestia-Homes/feat/pashub-water-heating-1565
PAS Hub mapper: water-heating code/fuel for cylinder dwellings (#1565)
2026-07-14 18:45:44 +01:00
Jun-te Kim
18065ae010 Set roof_construction_type and country_code on the PasHub site-notes path
roof_construction_type was left None in from_site_notes, so the
calculator's "flat"/"sloping ceiling" substring routing in
heat_transmission couldn't detect flat roofs and every roof (flat or
pitched) fell through the pitched default. country_code was never set
on this path either, so the England/Wales/Scotland/NI U-value cascade
(u_floor, u_basement_floor, u_door) had no country to key off.

Sets roof_construction_type from the surveyed roof_space construction_type
(main building + extensions), and country_code="ENG" for this all-English
cohort, matching the existing from_elmhurst_site_notes path.

Fixes #1566

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-14 17:44:38 +00:00
Khalim Conn-Kowlessar
6bb1843be6 Map PasHub boiler PCDB product_id to main_heating_index_number
`_map_sap_heating` left `main_heating_index_number` None, so the
calculator fell to a generic Table-4b seasonal efficiency instead of the
actual appliance. The site note lodges the boiler by its PCDB `product_id`
(and `summer_efficiency` is always 0), and those ids resolve directly in
the calculator's PCDB (`gas_oil_boiler_record`) — so carrying `product_id`
onto `main_heating_index_number` gives the real SEDBUK efficiency.

Gated on `product_id > 0` (0 = no product lodged → stays None; the
calculator keeps its default). `main_heating_category`/`sap_main_heating_code`
were evaluated and add nothing measurable — the PCDB index alone is the
efficiency source.

Guinness GMCA cohort: within-0.5 12.9% -> 13.4%, MAE 2.657 -> 2.621, no
regression (harness still 206-xfail, no new hard failures).

Closes #1563

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 17:43:37 +00:00
Daniel Roth
98aa0127a7 Keep swallowing the control gap's boundary form in the accuracy harness 🟪
The control gap surfaces as UnmappedSapCode on main and as UnmappedPasHubLabel
once the #1557 control mapper lands (unmapped community-heating control), so the
harness must swallow both; only the closed fuel-specific MissingMainFuelType is
dropped as a fuel-drop tripwire.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 17:40:50 +00:00
KhalimCK
7a556022c2
Merge pull request #1584 from Hestia-Homes/worktree-issue-1564-secondary-heating-fuel
Map PasHub secondary-heating fuel to a SAP10 fuel code
2026-07-14 18:38:45 +01:00
Jun-te Kim
56b551674e Map PasHub secondary-heating fuel to a SAP10 fuel code
secondary_fuel_type was hardcoded None in the from_site_notes mapper,
dropping the fuel for the ~67 Guinness GMCA 205 properties that lodge a
"Panel, convector or radiant heaters" secondary heater. Adds
_pashub_secondary_fuel_code (ADR-0015 pattern, mirrors
_pashub_main_fuel_code) mapping "Electricity" -> SAP10 code 30, with
"No Secondary Heating"/blank -> None and unknown labels strict-raising
UnmappedPasHubLabel.

Fixes #1564

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-14 17:30:00 +00:00
Daniel Roth
ccae1adfc9 Code PAS Hub dual-fuel room-heater main-fuel label to SAP code 🟩
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 17:29:00 +00:00
Daniel Roth
663c05ece9 Code PAS Hub dual-fuel room-heater main-fuel label to SAP code 🟥
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 17:28:03 +00:00
Khalim Conn-Kowlessar
597ca51dd9 Map PasHub water-heating fields for cylinder dwellings
`_map_sap_heating` set cylinder geometry/insulation but left
`water_heating_code` / `water_heating_fuel` / `immersion_heating_type`
None (and immersion as a raw label). For cylinder dwellings that loses
the HW-system typing the calculator's water cascade keys off.

Resolve them at the mapper boundary, gated on cylinder presence (combi
dwellings keep the None default so HW inherits the main system, unchanged):
- `water_heating_code` from the `system` label — "From main heating 1" -> 901,
  "Electric immersion" -> 903, community -> 950; strict-raises an unknown label.
- `water_heating_fuel` = standard electricity (30) for the electric-immersion
  WHC 903; None otherwise (901 inherits the main fuel).
- `immersion_heating_type` via the Elmhurst map (Single -> 2), cylinder-gated.

Combi-dominated cohort so the accuracy impact is negligible by design
(Tier 3): within-0.5 and MAE unchanged (12.9% / 2.56), harness still
206-xfail with no new hard failures.

Closes #1565

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 17:26:36 +00:00
Daniel Roth
88f221b86e Stop swallowing closed PAS Hub main-fuel gaps in the accuracy harness 🟪
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 17:24:15 +00:00
Daniel Roth
8529c71ff2 Capture PAS Hub main-heating fuel from the colon 'Fuel:' label variant 🟩
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 17:19:12 +00:00
Daniel Roth
2783c2c0c6 Capture PAS Hub main-heating fuel from the colon 'Fuel:' label variant 🟥
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 17:18:46 +00:00