Commit graph

40 commits

Author SHA1 Message Date
Khalim Conn-Kowlessar
bd84a2f190 Close the #1639 residual PasHub LRHA WAVE 3 mapper blocks 🟩
Clears the three non-table-add blocks tracked in #1639 plus a latent age-band
bug they surfaced. LRHA WAVE 3 cohort: 77 → 103 of 104 computing.

Block A (exact cylinder volume): a measured cylinder lodges its litres on a
separate "Exact cylinder volume:" line; parse it in the extractor, carry it on
WaterHeating.cylinder_volume_measured_l, route the "Exact cylinder volume" band
to SAP cylinder-size code 6, and thread the litres into SapHeating (the
calculator reads them only when the code is 6). Verified end-to-end on the real
166 L fixture.

Block B (Controls line-wrap): PasHub wraps a long "Controls:" value across two
PDF lines and _get_in read only the first, truncating the label so the mapper
missed its Table 4e code. Re-join the continuation when the value ends on a
conjunction/comma (grammatically incomplete) so "…room thermostat and TRVs"
matches the existing code 2314. Scoped to Controls: — the shared _get_in is
unchanged; a wider truncation audit is tracked separately.

Age-band normalisation: the heat_network_age_band '2007 - 2011' block was one
facet of a broader bug — PasHub lodges the construction age band in mixed
templates ("K: …" prefixed, tight "1991-1995", space-padded "2007 - 2011") and
only the letter form was normalised, so bare year ranges were SILENTLY
MIS-RATED by the age-band-keyed U-value/DLF cascades. Normalise every template
to its RdSAP letter in _extract_age_band: unblocks the heat-network dwellings
AND corrects ~30 already-computing fixtures (within-0.5 42.9% → 49.5%,
MAE 3.05 → 1.02). Sibling Guinness cohort unchanged (82.9% / 0.36).

Block C (gable 'None') was already resolved on main — no change needed.

Fixture 461178278096 lodges no main heating at all (MissingMainFuelType);
tolerated as a known gap in the harness and ticketed (#1680) for the modelling
decision. LRHA ratchets re-baselined to the corrected cohort (0.49 / 1.05).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 12:43:19 +00:00
Khalim Conn-Kowlessar
b7f127caec Fix six PasHub from_site_notes extractor/mapper defects (#1649)
All six are `from_site_notes`-only (gov-API RdSAP corpus unmoved at 78.9%),
graded against the DB PasHub-assessment oracle (energy_rating_current,
source='lodged'), not the surveyor-entered pre_sap.

A. Manual-Entry boiler efficiency. Extractor read PCDF-Search columns that a
   manual survey lacks; mapper returned None for all boilers → the generic
   0.80 gas default. Now read `Type of boiler:` / `Heating System (Boiler):`
   / `Is there an open flue?` and resolve the descriptor to its SAP 10.2
   Table 4b code. Moves all 7 Wythenshawe manual boilers to the oracle
   (4a Hollyhedge back boiler 63.6→53.4=53; group MAE 2.42→0.42).

B. Room-in-roof gable type None → strict-raise. A form variant omits the
   gable-type line (genuine field-absence); default None → party `gable_wall`
   (U=0.25), mirroring `_api_type_1_gable_kind(None)`. Present-but-unknown
   labels still strict-raise. Unblocks 8 & 34 Bucklow Drive.

C. Secondary `Closed room heater` unmapped → add Table 4a code 633.
   Unblocks 8 Brinkshaw Avenue.

D. Alternative-wall insulation thickness never extracted → parse
   `Wall insulation thickness:` and pass it through instead of the RdSAP 10
   §5.4 100 mm favourable default.

E. `Number of heated rooms?` label mismatch → the PDF lodges
   `Please enter the number of HEATED rooms:`.

F. HW cylinder thermostat dropped (NEW, systematic). `_map_sap_heating`
   never set `cylinder_thermostat`, so a surveyed thermostat defaulted None
   and the calculator applied the SAP 10.2 Table 2b Note a) ×1.3 penalty.
   Map it ("Y"/"N", gated on a lodged cylinder) as the Elmhurst path does.
   Fixes the two worst DB-oracle under-raters (52 Bucklow 59.4→61.8=62;
   13 Kerne Grove 65.0→67.1=67).

DB-oracle cohort (148 matched): within-0.5 83.8%→87.8%, MAE 0.427→0.303.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 11:30:00 +00:00
KhalimCK
04b7c26f67
PasHub SAP accuracy: infiltration inputs + immersion DHW + lights/lobby/heat-network (#1615)
* Electric-immersion DHW: bill at 100%, not the space boiler's efficiency 🟩

A separate electric immersion heater (SAP water-heating code 903) is 100%
efficient (SAP 10.2 Table 4a) and the space-heating boiler provides no water
heating, so Appendix D2.1 Eq D1 (the boiler seasonal-efficiency cascade) must
not apply to it. But `_water_heating_main` resolves DHW to the SPACE main — a
gas/oil boiler keeps its PCDB record — so three water-efficiency branches in
cert_to_inputs billed the immersion-heated cylinder at the boiler's ~87% summer
efficiency (a mongrel: electric fuel price x gas-boiler efficiency):

  1. the scalar `water_eff = water_pcdb_main.summer_efficiency_pct / 100`,
  2. the SAP §9.4.11 / Table 4c(2) -5pp no-interlock adjustment, and
  3. the Eq D1 (winter, summer) seasonal pair from `pcdb_main`.

Gate all three on `not dhw_is_electric_immersion`; the correct immersion path
(`_water_efficiency_with_category_inherit` -> 1.0, Eq D1 off) then applies,
mirroring the Table 3 zero-primary-loss gate already present for WHC 903.

General bug — fires for any WHC-903 dwelling whose space main is a PCDB gas/oil
boiler. It surfaced via the PasHub campaign's #1600 no-water-heating default
(WHC 999 -> 903) on a gas-combi dwelling: 58 Hackle St M11 4WU, SAP 55.30 ->
57.67 (pre_sap 58, verified 59).

Guardrails: the gov-API RdSAP corpus IMPROVES 78.8% -> 78.9% within-0.5, MAE
0.625 -> 0.622 (a handful of corpus certs with a boiler space main + electric
immersion move closer to accredited) — MAE ceiling ratcheted 0.626 -> 0.625.
Regression pinned in test_cert_to_inputs (RED before / GREEN after). pyright
0-new (cert_to_inputs baseline 30).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* PasHub SAP accuracy: thread low-energy lights + draught lobby (from_site_notes) 🟩

Two systematic silent-drops in the PAS Hub `from_site_notes` path, each a field
the gov-API/Elmhurst mappers thread but site-notes dropped, letting a favourable
default reach the SAP-10.2 calculator. Validated against the correct pashub
oracle (property_baseline_performance.effective_sap_score, portfolio 838):
cohort within-0.5 55.1% → 62.6%, MAE 0.599 → 0.521.

- Low-energy lights: when "exact LED/CFL known = No", PAS Hub lodges an aggregate
  "Number of fixed low energy lights?" count. Previously dropped (no dataclass
  field / extractor key / mapper thread) → calculator saw 0 low-energy bulbs and
  applied the pessimistic L5b/L8c no-data default, under-rating SAP. Now threaded
  into low_energy_fixed_lighting_bulbs_count, mirroring from_elmhurst_site_notes.
  ~6% of the cohort; resolves 58 Hackle (−0.43 vs oracle 59 with #1615).
- Draught lobby: _map_sap_ventilation set only the legacy `draught_lobby` field,
  never the canonical `has_draught_lobby` §2 (13) gate the cascade reads, so the
  surveyed lobby was ignored and infiltration over-stated. Now mirrors Elmhurst.

Two other audited levers were REJECTED against the pashub oracle (they matched
the gov-cert/RdSAP convention but pashub does not apply them): percent_draughtproofed
(cohort 30.8%) and the §A.2.2 assumed secondary heater (19.2%). A heat-network
control-code fix (2306→2303 for 16 Bingley) is HELD pending spec/Elmhurst
adjudication — it contradicts ADR-0053; see the NOTE on _PASHUB_HEAT_NETWORK_CONTROL_TO_SAP10.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* PasHub heat-network control: room-thermostat-only maps to 2308, not 2306 🟩

The Table 4e Group 3 label "charging linked to use of community heating, room
thermostat only" was mapped to 2306 — the linked-to-use *with-TRVs* code
(control type 3, space charging 1.00). The label explicitly excludes TRVs, so
it is control type 2 / space 1.05 = code 2308/2309 (linked-to-use → DHW 1.00).
2306 asserted TRVs the survey doesn't have and over-rated the cohort's sole
heat-network dwelling (16 Bingley Close 56.89 → 54.01 vs pashub oracle 52).

Verified against SAP 10.2 Table 4e as encoded in cert_to_inputs.py
(_CONTROL_TYPE_BY_CODE 2308→2, space-charging 2308→1.05, DHW 2308→1.00) and the
RdSAP control-label vocabulary in MainheatControlAttributes.py. 2303 (the earlier
audit's guess) is rejected: it is a flat-rate code (DHW 1.05) whose oracle match
was a coincidence of two offsetting spec violations. The residual +2.0 to oracle
52 is the documented SAP-10.2-engine-vs-lodged offset, not a fuel/flags gap
(those were threaded by the #1590 follow-up) — supersedes the ADR-0053 /
HANDOVER_838 "community fuel/flags" attribution.

16 Bingley is the only heat-network fixture in portfolio 838, so this moves one
fixture strictly toward its oracle. Harness ratcheted: within-0.5 0.51→0.58,
MAE 0.625→0.521 (observed 58.5% / 0.520 with all three 2026-07-15 levers).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* PasHub SAP accuracy: restore 3 dropped §2 infiltration inputs (from_site_notes)

`from_site_notes` uniquely dropped three ventilation inputs that the gov-API
and Elmhurst sibling mappers all set, causing a systematic cohort-wide SAP
under-rate vs the company's own accredited SAP-10.2 certs
(property_baseline_performance.effective_sap_score, portfolio 838):

1. percent_draughtproofed — never set, so §2(15) window infiltration was
   pinned at its 0.25-ACH worst case on every dwelling. Reconstructed as the
   area-weighted % of draught-proofed windows (mirrors Elmhurst
   `draught_proofing_percent`). Dominant term.
2. Upper-storey +0.25 m joist void — omitted; now added, matching
   `_UPPER_FLOOR_HEIGHT_ADD_M` on the gov-API/Elmhurst paths. This
   RE-ADJUDICATES #1601 ("keep raw"): that call was confounded by the
   then-present draught-proofing drop suppressing every verified dwelling.
3. sheltered_sides — left None → calculator's flat default of 2, which
   over-shelters end/semi/detached (RdSAP §S5 = 1/1/0). Now derived from
   built form.

The three are NON-ADDITIVE — each overshoots alone (which is why all three
were previously rejected individually) — but together they land all 7
verified ground-truth dwellings toward truth (none regress) and every built
form near zero. Cohort 62.1% -> 83.3% within-0.5, MAE 0.507 -> 0.377.

Guardrails: gov-API RdSAP corpus unchanged (78.9%, these are from_site_notes-
only helpers). pashub harness 58.5% -> 79.5% / MAE 0.520 -> 0.389; ratchets
tightened 0.58->0.78 and 0.521->0.40. pyright zero-new (mapper baseline 39).
New focused tests in TestFromSiteNotesInfiltrationFixes cover all three
fields incl. area-weighting and the built-form shelter map; goldens updated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 00:30:55 +01:00
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
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
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
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
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
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
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
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
Daniel Roth
78da2f88b6 Handle wall thickness "Unmeasurable" 🟩 2026-04-30 16:41:16 +00:00
Daniel Roth
6c70c5a535 Extract address when Property photo element is missing from PDF 🟩 2026-04-30 16:25:41 +00:00
Daniel Roth
e15646c341 rename example site notes to PasHub_ and add Elmhurst example 2026-04-24 13:01:51 +00:00
Daniel Roth
146b5999dc additional fields mapped from pdf 6 🟩 2026-04-23 11:23:29 +00:00
Daniel Roth
2bf8bc6d5e additional fields mapped from pdf 5 🟩 2026-04-23 11:12:25 +00:00
Daniel Roth
c5c3f3fc83 additional fields mapped from pdf 4 🟥 2026-04-23 10:36:15 +00:00
Daniel Roth
0e69f8e7a5 extract cylinder thermostat 🟥 2026-04-21 15:16:49 +00:00
Daniel Roth
691efcad72 extracy heating immersion type 🟥 2026-04-21 15:07:18 +00:00
Daniel Roth
7700aac5bb extract cylinder thermostat 🟥 2026-04-21 15:03:12 +00:00
Daniel Roth
1e0d72a805 extract secondary heating system 🟥 2026-04-21 11:47:35 +00:00
Daniel Roth
c558b79b68 map water heating cylinder size 🟥 2026-04-21 11:13:04 +00:00
Daniel Roth
da26e4a4cb extract no extensions 🟥 2026-04-21 10:53:14 +00:00
Daniel Roth
6b4a8dfef1 extract water heating cylinder thickness alternative field name 🟩 2026-04-21 10:45:56 +00:00
Daniel Roth
ac854f161a extract water heating cylinder thickness 🟥 2026-04-21 10:40:07 +00:00
Daniel Roth
0a8b9e0767 Include inspection metadata in output 2026-04-20 09:04:54 +00:00
Daniel Roth
968f025bc3 inspection date is date not str 2026-04-20 08:17:01 +00:00
Daniel Roth
c271caf378 refactor full extract test 2026-04-20 08:11:58 +00:00
Daniel Roth
1ef6af6bab pdf json to RdSapSiteNotes full extract 🟥 2026-04-20 08:08:37 +00:00
Daniel Roth
47cae167c8 Load Conservatories, Renewables, RoomCountElements, WaterUse, CustomerReponse, SurveyAddendum from SiteNotes JSON 🟥 2026-04-16 14:30:15 +00:00
Daniel Roth
4bcdb4823b Load Ventilation from SiteNotes JSON 🟥 2026-04-16 14:24:11 +00:00
Daniel Roth
29f9a664a5 Load HeatingAndHotWater from SiteNotes JSON 🟥 2026-04-16 14:19:01 +00:00
Daniel Roth
4a415ab4a2 Load Windows from SiteNotes JSON 🟥 2026-04-16 14:15:11 +00:00
Daniel Roth
3960998fee Load RoofSpace from SiteNotes JSON 🟥 2026-04-16 14:11:44 +00:00
Daniel Roth
2ed741df5c Load BuildingMeasurements from SiteNotes JSON 🟥 2026-04-16 14:08:20 +00:00
Daniel Roth
101860fac1 Load BuildingConstruction from SiteNotes JSON 🟥 2026-04-16 14:01:05 +00:00
Daniel Roth
1db9c68878 Map to RdSapSiteNotes from site notes JSON: additional General test 🟩 2026-04-16 13:57:49 +00:00
Daniel Roth
bb648b8223 Map to RdSapSiteNotes from site notes JSON 🟩 2026-04-16 13:56:27 +00:00
Daniel Roth
4f3c7894ae Map to RdSapSiteNotes from site notes JSON 🟥 2026-04-16 13:54:03 +00:00