Commit graph

659 commits

Author SHA1 Message Date
Khalim Conn-Kowlessar
5324ae0e57 PasHub floor exposure + alternative walls + extension floor 🟩
Resolve #1601 and land #1602/#1603 on the merged-#1609 tree, adjudicated
against Khalim's verified ground truth (not pre_sap).

#1601 (upper-floor +0.25 m joist void) — RESOLVED as keep-raw. The
gov-API/Elmhurst mappers add 0.25 m to upper-storey heights because their
lodged height is a clear internal ceiling. Against verified truth RAW beats
+0.25 on all 7 verified dwellings (16 Stillwater raw -0.35 within-band vs
+0.25 -0.75; every verified property is already under truth and +0.25 only
lowers SAP). pashub surveys internal dimensions (202/205 "Measurements
Location: Internal") and its upper-ground height gap is modal 0.00, so the
joist void is not differentially present and a blanket +0.25 is unsafe.
_map_floor_dimensions keeps the raw surveyed height with a RESOLVED note.

#1602 — semi-exposed / exposed floor exposure. The surveyed "Floor type"
"Semi Exposed (unheated)" now sets is_above_partially_heated_space (RdSAP
§5.14 U=0.7) and "Exposed Floor" sets is_exposed_floor (§5.13 Table 20), on
the lowest storey (floor==0) only, mirroring the gov-API/Elmhurst siblings.
New strict-raise _pashub_floor_exposure (ADR-0015).

#1603a — alternative walls. New AlternativeWall survey dataclass +
extract_alternative_walls (the "Alternative Wall"->"Windows" section) +
_map_pashub_alternative_walls -> sap_alternative_wall_1/2. The cascade
deducts each alt-wall area from the part's main opaque wall (no double
count), so a mixed-facade dwelling is billed at each sub-area's own
construction. The alt-wall block lodges "As Built" (capital B) — aliased to
the existing default insulation code.

#1603b — extension floor construction. ExtensionConstruction gains a floor
block (parsed like the main building) and _map_extension_building_part now
threads floor_type/construction/insulation/u_value_known, which the main
path already did and the extension silently dropped.

Cohort: within-0.5 50.2% -> 51.2%, MAE 0.652 -> 0.624, 0 strict-raise
errors across all 205. Verified-truth safe: none of the 7 verified
dwellings have alt walls, and #1602 moves the one it touches (2 Philips
exposed ext floor) by -0.00. pyright 0-new (mapper baseline 39). Goldens
test_full_building_construction + end_to_end EXTENSION_1 updated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 15:46:06 +00:00
Khalim Conn-Kowlessar
30b1728337 PasHub extraction: thread showers/baths, normalize floor label, code no-water-heating 🟩
Three silent-drop extraction fixes the gov-API/Elmhurst mappers already apply
but `from_site_notes` uniquely omitted — each let a favorable default reach the
SAP-10.2 calculator and over-rate the dwelling. Same calculator, so the whole
gap was extraction fidelity (gov-API corpus 78.6% within-0.5 vs pashub 23.9%).

#1598 — electric-shower + bath counts: `_map_sap_heating` built `shower_outlets`
  but never set `electric_shower_count`/`mixer_shower_count`/`number_baths`, so
  the cascade defaulted to 1 gas mixer / 0 electric, billing electric-shower hot
  water off the cheap main system. 100/205 fixtures lodge "Electric Shower".
#1599 — floor label: pashub lodges "Ground Floor" (capital F); the calculator's
  RdSAP §5(12) suspended-timber floor-infiltration gate is case-sensitive on
  "Ground floor", so the 0.2-ACH term was silently dropped. Normalized at the
  mapper boundary. 98/205 suspended-timber.
#1600 — water-heating "None": the RdSAP 10 §10.7 "no water heating system"
  signal was discarded and HW inherited the gas combi (58 Hackle 67.7 vs
  verified 59). Coded to WHC 999 so the existing electric-immersion + Table
  28/29 default fires (also folds in #1594: blank cylinder size is no cylinder,
  not a phantom one).

Guinness GMCA 205 cohort: within-0.5 23.9% -> 50.2% (103/205), MAE 1.410 ->
0.650. Ratchets tightened to 0.50 / 0.65. Verified vs Khalim's ground truth
(16 Stillwater 78.2 -> 75.7 vs 76). #1601 (upper-floor +0.25 m) held — it
empirically over-corrects this cohort (internal-vs-external dimension question,
under investigation). pyright: 0 new errors.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 13:52:13 +00:00
Khalim Conn-Kowlessar
27accc6bb1 Int-code the surveyed window orientation to its SAP10 octant code 🟩
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 08:48:36 +00:00
Khalim Conn-Kowlessar
5276b45836 Int-code the surveyed window orientation to its SAP10 octant code 🟥
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 08:45:22 +00:00
Khalim Conn-Kowlessar
f7b27dc519 Bill Simplified RIR common walls as common_wall at the main-wall U 🟩
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 08:33:18 +00:00
Khalim Conn-Kowlessar
27158d623d Bill Simplified RIR common walls as common_wall at the main-wall U 🟥
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 08:28:59 +00:00
Khalim Conn-Kowlessar
600262819c Capture the Simplified RIR 'Roof room type' label from the survey 🟥
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 08:25:11 +00:00
Khalim Conn-Kowlessar
be1e10e337 Int-code the surveyed secondary-heating label to its Table 4a room-heater code 🟩
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 00:26:51 +00:00
Khalim Conn-Kowlessar
7b04a9d5f4 Int-code the surveyed secondary-heating label to its Table 4a room-heater code 🟥
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 00:25:20 +00:00
Khalim Conn-Kowlessar
f5e4814bf9 Int-code the surveyed cylinder insulation label for the storage-loss cascade 🟩
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 23:25:48 +00:00
Khalim Conn-Kowlessar
87922b622a Int-code the surveyed cylinder insulation label for the storage-loss cascade 🟥
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 23:24:47 +00:00
Khalim Conn-Kowlessar
5a7ff6de1c Lodge the surveyed Wall Dry-Lined answer on the SAP building part 🟩
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 23:22:22 +00:00
Khalim Conn-Kowlessar
073e5ce30c Lodge the surveyed Wall Dry-Lined answer on the SAP building part 🟥
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 23:21:36 +00:00
Khalim Conn-Kowlessar
0b47798403 Capture the surveyed Wall Dry-Lined answer per building part 🟥
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 23:20:09 +00:00
Khalim Conn-Kowlessar
1f1bb8a556 Withhold roof insulation from RIR surfaces the surveyor marked insulation-unknown 🟩
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 23:17:30 +00:00
Khalim Conn-Kowlessar
7f854e7219 Withhold roof insulation from RIR surfaces the surveyor marked insulation-unknown 🟥
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 23:17:30 +00:00
Khalim Conn-Kowlessar
d743f6fb3b Capture the per-surface RIR insulation-known answer from the survey 🟥
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 23:11:09 +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
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
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
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
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
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
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
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