Commit graph

630 commits

Author SHA1 Message Date
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
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
7072499b2e Code PAS Hub Bulk LPG and Mains Gas main-fuel labels to SAP codes 🟩
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 17:17:48 +00:00
Daniel Roth
c489dfcb29 Code PAS Hub Bulk LPG and Mains Gas main-fuel labels to SAP codes 🟥
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 17:17:18 +00:00
Daniel Roth
1e9231a086 Merge branch 'main' into feature/pashub-map-main-heating-control-1557 2026-07-14 16:47:17 +00:00
Daniel Roth
f7e6bf1184
Merge pull request #1579 from Hestia-Homes/feat/pashub-glazing-1562
PAS Hub mapper: window glazing_type label -> SAP10 code (#1562)
2026-07-14 17:47:03 +01:00
Daniel Roth
ca38c8a403 Merge branch 'main' into feature/pashub-map-main-heating-control-1557 2026-07-14 16:37:33 +00:00
Jun-te Kim
af83606183
Merge pull request #1577 from Hestia-Homes/fix/sap15-energyelement-party-wall-description
Default description on bare N/A-rated wall elements in SAP-15.0 certs
2026-07-14 17:36:46 +01:00
Daniel Roth
73666ffb0f Merge branch 'main' into feature/pashub-map-main-heating-control-1557 2026-07-14 16:36:25 +00:00
KhalimCK
de8e5c5621
Merge pull request #1576 from Hestia-Homes/feat/pashub-wall-construction-1560
PAS Hub mapper: wall_construction label -> SAP10 code (#1560)
2026-07-14 17:36:16 +01:00
Khalim Conn-Kowlessar
ff36014e6f Map PasHub window glazing_type labels to SAP10 cascade codes
`_map_sap_window` copied the raw `glazing_type` survey label; the
calculator's isinstance-int guards read it as non-int and fell to the
double-pre-2002 U=2.8 default for every window — over-counting heat loss
on the ~852 windows the survey dates to 2002-2021 (RdSAP Table 24 U=2.0).

Add `_pashub_glazing_type_int` mapping the surveyed labels to the SAP10
cascade glazing codes the calculator reads for window U
(`_GLAZING_CODE_TO_UWINDOW`) and solar g (`_G_PERPENDICULAR_BY_GLAZING_TYPE`):
before 2002 -> 3, 2002-2021 -> 2, unknown install date -> 3 (pre-2002
default), post-2022 -> 13. Strict-raises `UnmappedPasHubLabel` on an unknown
label; blank passes through as "".

Completes the fabric stack (party-wall/wall-construction/insulation/glazing):
cohort MAE 2.79 -> 2.66, within-0.5 11.4% -> 12.9%, signed -0.04 -> +0.33.

Closes #1562

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 16:33:34 +00:00
Jun-te Kim
4bc042f56a Correct overclaim: rating 0 means N/A, not confirmed party wall
Reviewer pushback: the gov EPC code table (epc_codes.csv) defines
energy_efficiency_rating 0 generically as "N/A", not specifically
"party wall". The payload carries no other identifying field, so the
actual cause can't be determined from the data alone. The fix itself
is unaffected (still calc-neutral regardless of cause) — just
correcting comments/test naming to not assert an unconfirmed fact.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-14 16:29:28 +00:00
Jun-te Kim
7f7dcbc059 Default description on bare zero-rated party-wall elements in SAP-15.0 certs
Task 9d271e98-96e6-4be6-bb50-e9be6f954003 (portfolio 796) failed 21
"Top-floor flat" properties with "EnergyElement: missing required
field 'description'". Every one lodges a second walls[] entry that is
a bare zero-rated party-wall boundary element
({"energy_efficiency_rating": 0, "environmental_efficiency_rating": 0},
no description key at all) — unlike the sibling floors/roofs
"(other premises above/below)" sentinel convention, which does carry
text. Confirmed schema-wide across all 21 failing certs.

Default description to "" rather than fabricate construction text:
_joined_descriptions (heat_transmission.py) already filters out falsy
descriptions, so this is calc-neutral — it only unblocks the parse.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-14 16:28:55 +00:00
Daniel Roth
168c610ade Strict-raise on an unrecognised PasHub main-heating control label 🟩
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 16:23:19 +00:00
Daniel Roth
be076bb9a4 Resolve the cohort's remaining PasHub boiler control labels to Table 4e codes 🟩
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 16:22:23 +00:00
Daniel Roth
a213150d7d Resolve the cohort's remaining PasHub boiler control labels to Table 4e codes 🟥
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 16:20:38 +00:00
Khalim Conn-Kowlessar
ed15bd13f7 Map PasHub walls_insulation_type labels to SAP10 codes
`from_site_notes` copied the raw `walls_insulation_type` survey label onto
the building part; the calculator's `_int_or_none` read it as `None`, so
the ~101 filled-cavity walls in the cohort lost their lower-U credit and
were over-counted.

Add `_pashub_wall_insulation_type_int` (mirroring the wall-construction
helper and the Elmhurst `_ELMHURST_WALL_INSULATION_TO_SAP10` sibling)
mapping the surveyed labels to the SAP10 wall-insulation codes `u_wall`
consumes ("As built" -> 4 assumed/default, "Filled Cavity" -> 2,
"External" -> 1), strict-raising `UnmappedPasHubLabel` on an unknown label.

Stacked on #1560, this is the fabric fix that removes the cohort's
systematic SAP bias: mean signed -0.79 -> -0.04, MAE 3.11 -> 2.79,
within-0.5 8.5% -> 11.4%.

Closes #1561

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 16:19:55 +00:00
Khalim Conn-Kowlessar
aa338dd152 Map PasHub walls_construction_type labels to SAP10 codes
`from_site_notes` copied the raw `walls_construction_type` survey label
onto the building part; the calculator's `_int_or_none` read it as `None`,
so every wall fell back to the age-band + thickness U-value and lost the
solid/cavity/timber/system-built distinction.

Add `_pashub_wall_construction_int` (mirroring `_pashub_party_wall_
construction_int`) mapping the four surveyed labels to the WALL_* codes
`u_wall` consumes, strict-raising `UnmappedPasHubLabel` on an unknown
label and passing a blank label through as the empty-string "no lodging"
sentinel (the field is `Union[int, str]`). On the Guinness GMCA cohort
this trims SAP MAE 3.22 -> 3.11 and lifts within-1.0 12.9% -> 14.9%.

Closes #1560

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 16:11:36 +00:00
Jun-te Kim
5d5fa73372 Derive cylinder_size when has_hot_water_cylinder is false on SAP-15.0/16.x certs
Task 9d271e98-96e6-4be6-bb50-e9be6f954003 (portfolio 796) failed property
715358 with "SapHeating: missing required field 'cylinder_size'": cert
0685-2881-6867-9029-1761 lodges has_hot_water_cylinder="false" but omits
sap_heating.cylinder_size entirely. Mirrors the already-handled inverse
gap in _normalize_sap_schema_16_x, so default to code 1 ("no cylinder")
rather than fail loud.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-14 16:11:24 +00:00
Daniel Roth
59215c822e Resolve PasHub main-heating control label to its SAP Table 4e code 🟩
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 16:01:04 +00:00
Daniel Roth
fc53594c99 Resolve PasHub main-heating control label to its SAP Table 4e code 🟥
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 15:59:45 +00:00
Daniel Roth
abab5bf9ac
Merge branch 'main' into feature/pashub-heat-emitter-code-1556 2026-07-14 16:34:28 +01:00
Khalim Conn-Kowlessar
f9bf45de5c Map PasHub party_wall_construction labels to SAP10 codes
`from_site_notes` copied the raw `party_wall_construction_type` survey
label onto the building part; the calculator read `None` and applied the
U=0.25 house default to every party wall — phantom heat loss on the
~165/205 solid party walls that RdSAP 10 Table 15 rates U=0.0.

Add `_pashub_party_wall_construction_int` (mirroring `_pashub_main_fuel_code`)
mapping the six surveyed labels to the SAP10 codes `u_party_wall` consumes,
strict-raising `UnmappedPasHubLabel` on an unknown label, and wire it into
both site-note building-part mappers. On the Guinness GMCA cohort this
halves the systematic SAP under-rate (mean signed -1.58 -> -0.85).

Refs #1559

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 15:26:30 +00:00
Daniel Roth
64e31742b3 Strict-raise on an unrecognised PasHub main-heating emitter label 🟩
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 15:17:56 +00:00
Daniel Roth
5b0f25847c Normalize PasHub site-notes emitter label to its SAP10 code 🟩
_map_sap_heating resolves the raw emitter label (e.g. Radiators) to its
SAP10 emitter code via _pashub_heat_emitter_code, reusing the Elmhurst
emitter map. Blank passes through; an unrecognised label strict-raises
UnmappedPasHubLabel at the mapper boundary (ADR-0015) instead of
resurfacing as the calculator's UnmappedSapCode: heat_emitter_type.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 15:17:06 +00:00
Daniel Roth
8d45fcb544 Normalize PasHub site-notes emitter label to its SAP10 code 🟥
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 15:16:08 +00:00
Daniel Roth
b91763fc1d Preserve PasHub blank-Fuel electric-system inference through fuel-code normalization 🟪
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 14:34:28 +00:00
Daniel Roth
a8afcd1b3d Strict-raise on an unrecognised PasHub main-heating Fuel label 🟩
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 14:31:32 +00:00
Daniel Roth
1fead6e4c7 Strict-raise on an unrecognised PasHub main-heating Fuel label 🟥
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 14:30:49 +00:00