Commit graph

108 commits

Author SHA1 Message Date
Khalim Conn-Kowlessar
1928e5a2d6 Cohort residual slice 13: Detailed §3.10 RR geometry — per-surface lodgement
Adds `SapRoomInRoofSurface` dataclass (kind + area + insulation thickness
+ insulation type) and an optional `detailed_surfaces` list on
`SapRoomInRoof`. When `detailed_surfaces` is present, the Simplified
A_RR formula is bypassed and the calculator iterates each surface,
applying the appropriate Table 17 / Table 4 U-value:

  slope         → roof_w_per_k   via u_rr_slope        (Table 17 col 1)
  flat_ceiling  → roof_w_per_k   via u_rr_flat_ceiling (Table 17 col 2)
  stud_wall     → roof_w_per_k   via u_rr_stud_wall    (Table 17 col 3)
  gable_wall    → party_walls_w_per_k at U=0.25         (Table 4 "as
                                                        common wall")

This mapping mirrors the U985 worksheet for 000477 where RR stud walls
+ slope + flat-ceiling lines sit under (30) and RR gable walls sit
under (32). The §3.9 deduction of `A_RR_floor` from the storey-below
roof area still applies.

Synthetic test pins a 1-storey + RR dwelling with 4 detailed surfaces
(slope/stud_wall/flat_ceiling/gable_wall) at hand-computed U-values
from Table 17 and Table 4, abs=0.001 tolerance.

Reference: RdSAP 10 (10-06-2025) §3.10 page 24-25; Figure 4; Table 17
page 44; Table 4 page 22.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 19:36:10 +00:00
Khalim Conn-Kowlessar
3ff864bf86 Cohort residual slice 12: Simplified Type 2 RR geometry (common walls <1.8m)
Extends `SapRoomInRoof` with six optional fields capturing the RdSAP10
§3.9.2 Simplified Type 2 lodgement: common_wall_length_m / height_m
plus two gable length/height pairs.

Type 2 fires when `common_wall_height_m` is set and < 1.8 m (otherwise
the space is a separate storey). Geometry per spec page 23:
  A_common_wall = L × (0.25 + H)
  A_gable       = L × (0.25 + H_gable)
                  − Σ ((H_gable − H_common_wall_i)² / 2)
  A_RR_final    = A_RR − Σ A_common_wall − Σ A_gable
                  (− party / sheltered / connected when lodged, future
                  slice when a fixture exercises them)

Common walls and gables route to walls_w_per_k at U_main_wall (per spec:
"Common wall U-value is inferred from the U-value of the main wall in
the building part below"). A_RR_final routes to roof_w_per_k at
u_rr_default_all_elements (Table 18 col 4).

Synthetic test: 1-storey cavity-uninsulated dwelling at age B + RR
(floor 10 m², common_wall_length 5 m × 1 m height). Pins
walls_w_per_k = 60 × 1.5 + 6.25 × 1.5 = 99.375 W/K and
roof_w_per_k = 30 × 0.40 + 26.025 × 2.30 = 71.857 W/K at abs=0.001.

No production fixture exercises Type 2 yet — synthetic test is the
unit-level guard until a Type 2 cert lands in the corpus.

Reference: RdSAP 10 (10-06-2025) §3.9.2 page 22-23.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 19:32:14 +00:00
Khalim Conn-Kowlessar
af6fcfb190 Cohort residual slice 2: cert→ventilation cascade closes useful kWh on all 6 fixtures
Surfaces four cert lodgements that the §2 ventilation cascade was
missing on the cert→inputs path. Without them, `cert_to_inputs` was
defaulting:
  - extract_fans_count    → 0  (PDF: 1-2 fans per fixture)
  - percent_draughtproofed → 0  (PDF: 75-100% per fixture)
  - sheltered_sides        → 2  (PDF: 1-3 per fixture — hardcoded TODO)
  - has_suspended_timber_floor → False (PDF: True on 000477/000487)

Net effect on (25)m monthly effective ACH ranged from -19% (000477)
to +5% (000490) → propagated 1:1 through HLC × ΔT → useful space heat
→ main + secondary fuel kWh → cost / SAP integer.

Schema:
- `SapVentilation` gains 4 new optional fields: `sheltered_sides`,
  `has_suspended_timber_floor`, `suspended_timber_floor_sealed`,
  `has_draught_lobby`. RdSAP cert lodges these but the type didn't
  surface them.
- `cert_to_inputs.cert_to_inputs` reads them when set; falls back to
  the SAP10.2 §2 worst-case defaults (sheltered=2, no timber floor,
  no draught lobby) when the cert hasn't lodged. Removes the long-
  standing `sheltered_sides=2` hardcode + 4 TODOs.
- `make_minimal_sap10_epc` accepts a `sap_ventilation` kwarg.

Per-fixture build_epc() updates lodge the U985 PDF values verbatim.

E2E pin: new parametrized test
`test_elmhurst_cert_to_inputs_monthly_infiltration_ach_matches_u985_
worksheet` asserts `inputs.monthly_infiltration_ach[m] == LINE_25_
EFFECTIVE_ACH[m]` at abs=1e-3 across all 6 fixtures + 12 months
(72 assertions). All pass.

Useful space heating drift:
  000474: useful 10821.69 → 10765.85 (Δ -55.8 kWh vs PDF 10612.86 → +1.4% over, was +2.0%)
  000490: useful 11262.05 → 11184.06 (Δ -78.0 kWh vs PDF 11183.28 → +0.007% — essentially exact)

SAP integer status:
  000474: 62 = PDF 62 (delta 0) ✓
  000490: 58 vs PDF 57 (delta 1; continuous 57.77 vs 57.40)
          — remaining residual is pumps_fans hardcoded at 130 kWh
          vs PDF 160 (Table 4f cascade not yet implemented → -£4 cost
          + 0.3 continuous SAP). Next slice.

Tightens `result.secondary_heating_fuel_kwh_per_yr` pin abs=10 → abs=0.1
(was loose to absorb the +0.7% useful overshoot which has now closed).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 11:15:31 +00:00
Khalim Conn-Kowlessar
6b99ad0a55 heat_transmission: route exposed/semi-exposed floors through Table 20
SapFloorDimension gains an is_exposed_floor flag (default False) signalling
that the floor sits over outside air or unheated space rather than soil —
typical for an extension that hangs off the main from the first storey
upward (Elmhurst 000490 Extension 1 is exactly this shape).

heat_transmission_from_cert now consults the flag on the part's ground
SapFloorDimension and dispatches to u_exposed_floor (Table 20) instead
of the BS EN ISO 13370 / Table 19 cascade. Basement floor still wins
priority (Table 23 § 5.17 overrides everything else for that part).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 13:22:44 +00:00
Khalim Conn-Kowlessar
a8b443f669 SAP calculator entry point + cert→inputs adapter + strict P6.1 identifiers
Lands the production code that the just-committed Elmhurst conformance
fixtures (6455d48b) exercise: the SAP10.3 calculator orchestrator
(domain.sap.calculator.Sap10Calculator), the RdSAP-driven cert→inputs
mapper (domain.sap.rdsap.cert_to_inputs), and the EpcPropertyData
strict-type pass that P6.1 starts.

calculator.py is the entry point. Two surfaces depending on the caller's
shape:
- Sap10Calculator().calculate(epc) — full RdSAP mapper + worksheet loop
- calculate_sap_from_inputs(inputs) — pure physics over typed inputs

P6.1 introduces BuildingPartIdentifier as a strictly-typed replacement
for bare-string matching on SapBuildingPart.identifier (motivated by
the pain point at worksheet/dimensions.py:74-82). Two boundary factories
canonicalise raw inputs: from_api_string for the gov-EPC API, and
extension(n) for site-notes / construction id flows.

Also catches up two transitive deps that 6455d48b implicitly required
but I missed:
- ml/rdsap_uvalues.py — party-wall U-value rows that heat_transmission
  resolves; the U=0.0 branch the 000516 fixture exercises lands here.
- ml/tests/_fixtures.py — make_minimal_sap10_epc that every Elmhurst
  fixture imports. Without this catch-up, checking out 6455d48b in
  isolation would ImportError.

Out of scope (will commit separately): ml/transform.py legacy envelope
drift; backend/ FastAPI + documents_parser layer; etl/ scratch.

824 tests pass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 09:54:30 +00:00
Khalim Conn-Kowlessar
195336b7e1 slice 15d: +50 features (gap fill + secondary building part); drop 2 derived
Removes:
  - environmental_impact_current (SAP-derived rating, leaks into co2 target)
  - energy_rating_average (average of sap_score + potential, direct leak)

Adds:
  Doors            draughtproofed_door_count, insulated_door_u_value
  Hot water        cylinder_insulation_type, cylinder_thermostat,
                   secondary_heating_type
  Ventilation      mechanical_vent_duct_placement, _duct_insulation,
                   _duct_insulation_level, _measured_installation
  Lighting         low_energy_fixed_lighting_bulbs_count,
                   fixed_lighting_outlets_count,
                   low_energy_fixed_lighting_outlets_count
  Windows          window_avg_glazing_gap_mm, window_avg_frame_factor,
                   window_pct_permanent_shutters_insulated
  Main dwelling    room_in_roof_floor_area_m2, alternative_wall_count,
                   alternative_wall_area_m2, flat_roof_insulation_thickness_mm,
                   wall_thickness_measured
  Element counts   wall_count, roof_count, floor_count,
                   main_heating_count_elements, main_heating_controls_present
  Wind             wind_turbine_hub_height_m, wind_turbine_rotor_diameter_m
  Flat             flat_unheated_corridor_length_m
  Addendum         addendum_stone_walls, addendum_system_build,
                   addendum_numbers_count
  LZC              lzc_energy_sources_count
  Secondary part   secondary_dwelling_present + 11 fabric features
                   (wall/roof/floor construction + insulation + thickness
                   + area + heat-loss perimeter) + other_building_parts_count

Wires through schema -> domain -> mapper: adds Addendum dataclass,
lzc_energy_sources, mechanical_vent_duct_insulation_level. Also fixes
_measurement_value to accept raw dicts (from_dict left some Measurement
fields as dict when they weren't typed as a dataclass).

Results at N=25,000 2026 RdSAP certs:
  sap_score          MAPE=0.043  sMAPE=0.036  R^2=0.891
  co2_emissions      sMAPE=0.106  R^2=0.929
  peui_raw           MAPE=0.087  sMAPE=0.084  R^2=0.860
  peui_ucl           MAPE=0.079  sMAPE=0.076  R^2=0.866
  space_heating_kwh  MAPE=0.112  sMAPE=0.108  R^2=0.947
  hot_water_kwh      MAPE=0.071  sMAPE=0.069  R^2=0.854  (+0.082 R^2 vs 15b)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-17 10:13:03 +00:00
Khalim Conn-Kowlessar
9f6f7608b9 slice 15b: +18 features — heating type code, hot water, windows, flat, supply
Heating: primary_sap_main_heating_code (the SAP10 heating-system enum was the
single biggest missing input), primary_emitter_temperature,
primary_main_heating_fraction.

Hot water: immersion_heating_type, shower_outlet_count.

Windows: window_pct_living, window_pct_external, window_pct_permanent_shutters
(area-weighted shares parallel to existing window aggregates).

Dwelling: conservatory_type, has_heated_separate_conservatory.

Flat-only block (sap_flat_details): flat_level, flat_top_storey,
flat_storey_count, flat_location, flat_heat_loss_corridor (int sentinels
like '20+' coerce to None for the categorical features).

Energy supply: meter_type, pv_connection, wind_turbines_terrain_type.

Also plumbs `air_tightness` EnergyElement, `sap_flat_details` and
`has_heated_separate_conservatory` through the 21.0.1 mapper path (they were
silently None before).

Results at N=25,000 2026 RdSAP certs:
  sap_score          MAPE=0.044  sMAPE=0.038  R^2=0.884  (+0.045 R^2 vs 15a)
  co2_emissions      sMAPE=0.108  R^2=0.925
  peui_raw           MAPE=0.092  sMAPE=0.088  R^2=0.849
  peui_ucl           MAPE=0.081  sMAPE=0.078  R^2=0.860
  space_heating_kwh  MAPE=0.111  sMAPE=0.108  R^2=0.945
  hot_water_kwh      MAPE=0.081  sMAPE=0.079  R^2=0.772

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-17 00:08:11 +00:00
Khalim Conn-Kowlessar
0ffda529ec slice 15a: add wall/floor/roof + demand scalar features for retrofit simulation
15 new features wired through schema -> domain -> mapper -> transform:

Main Dwelling fabric (11):
  - wall_insulation_type, wall_insulation_thickness_mm, wall_dry_lined,
    wall_thickness_mm, party_wall_construction
  - roof_insulation_location, roof_insulation_thickness_mm
  - floor_construction, floor_insulation, floor_insulation_thickness_mm,
    floor_heat_loss

Dwelling-level scalars (4):
  - multiple_glazed_proportion, number_baths, number_baths_wwhrs,
    extract_fans_count

Thickness strings like '50mm'/'NI'/'ND' parsed via _parse_thickness_mm; NI
(no insulation) lands as 0mm so the model sees the physical zero rather than
a missing value. Categorical sentinels ('NA'/'NI'/'ND') become None.

Also fixed long-standing typo `multiple_glazed_propertion` -> `_proportion`
in domain dataclass + its lone DB-model usage.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-16 22:08:27 +00:00
Khalim Conn-Kowlessar
c496f345f8 slice 14l: bigger-run fixes — UCL guard, PV Measurement coercion, sMAPE
Three changes surfaced by the 25k 2026 run:
- transform._peui_ucl returns None for non-positive raw PEUI (net-exporters).
  apply_ucl_correction would otherwise raise ValueError on negative input.
- PhotovoltaicArray scalars (peak_power, pitch, orientation, overshading)
  now accept Measurement | int | float in the schema; mapper coerces via
  _measurement_value.
- train_baseline reports sMAPE alongside MAPE — handles zero-actual rows
  (e.g. co2_emissions for net-zero certs) where MAPE explodes.

Results at N=25,000 RdSAP 2026 certs (~32s end-to-end):
  sap_score          MAPE=0.064  sMAPE=0.054  R^2=0.762
  co2_emissions      sMAPE=0.140  R^2=0.890
  peui_raw           MAPE=0.126  sMAPE=0.120  R^2=0.714
  peui_ucl           MAPE=0.114  sMAPE=0.108  R^2=0.736
  space_heating_kwh  MAPE=0.167  sMAPE=0.157  R^2=0.915
  hot_water_kwh      MAPE=0.089  sMAPE=0.086  R^2=0.737

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-16 21:15:37 +00:00
Khalim Conn-Kowlessar
8fddd25b9a slice 14k: E2E pipeline runs on real 2026 RdSAP certs
Two production fixes surfaced by the live run:
- mapper.from_rdsap_schema_21_0_1 now sets the three ML target scalars
  (energy_rating_current, co2_emissions_current, energy_consumption_current).
  They were silently None for every cert before, leaving the only labels as
  the kWh fields from renewable_heat_incentive.
- train_baseline coerces object-dtype columns to numeric (None -> NaN) and
  drops rows with null target per fit, so LightGBM accepts the frame.

E2E on 500 real certs (~1s):
  sap_score             R^2=0.604  MAPE=0.084
  co2_emissions         R^2=0.813  MAPE=0.130
  peui_raw              R^2=0.979  MAPE=0.026
  space_heating_kwh     R^2=0.823  MAPE=0.213
  hot_water_kwh         R^2=0.519  MAPE=0.115

peui_ucl excluded: UCL correction still needs wiring.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-16 20:47:41 +00:00
Khalim Conn-Kowlessar
6697a6c76e slice 14j: Optional sweep across schema 21.0.1 + mapper guards
Across 500 real RdSAP-21.0.1 certs from 2026, mapper goes 0% -> 100% success.
Schema-loading + ml-transform + ml_training_data: 146 tests pass.

Mainly affected fields:
- SapHeating: instantaneous_wwhrs, shower_outlets (now Union with List shape)
- SapWindow: glazing_gap, frame_factor, pvc_frame, window_transmission_details
- SapEnergySource: pv_battery_count, wind_turbine_details, pv_batteries (List form)
- SapBuildingPart: all 13 sub-fields now Optional
- SapFloorDimension: Measurement | int | float fallback
- RdSapSchema21_0_1: 16 top-level fields (mechanical_vent_*, lighting counts, ...)

Mapper helpers added: _measurement_value, _first_pv_battery, _first_shower_outlet.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-16 20:35:28 +00:00
Khalim Conn-Kowlessar
ccb654c230 slice 14i: pin real RdSAP cert as fixture + RED regression test
Currently fails on SapWindow.glazing_gap (first of ~30 fields the dataclass
incorrectly treats as required). Will go GREEN once 14j sweeps Optional.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-16 20:23:29 +00:00
Khalim Conn-Kowlessar
b050348927 slice 10.5: PhotovoltaicArray on SAP10 schema + EpcPropertyData
SAP10 EPCs with measured PV carry photovoltaic_supply as a nested
list of arrays (peak_power, pitch, orientation, overshading) rather
than the legacy unmeasured wrapper {none_or_no_details:
{percent_roof_area: N}}. The schema-21 dataclasses now accept both
shapes via Union[PhotovoltaicSupply, List[List[PhotovoltaicArray]]],
and from_dict._coerce now dispatches list values onto list type
variants of multi-type Unions.

EpcPropertyData.SapEnergySource gains
photovoltaic_arrays: Optional[List[PhotovoltaicArray]] — populated
when the measured shape is present, otherwise None. The legacy
photovoltaic_supply field is preserved for the fallback case.
Both schema-21.0.0 and 21.0.1 mappers dispatch via the new
_map_schema_21_pv helper.

Unblocks Slice 11 (PV feature aggregation in EpcMlTransform).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-16 16:00:25 +00:00
Khalim Conn-Kowlessar
dba254e316 slice 8a: window physics and orientation aggregates
Thirteen window-aggregate features land on the transform: count,
total area, eight SAP-octant area columns (N/NE/E/SE/S/SW/W/NW),
area-weighted draught-proofing pct, and area-weighted u_value +
solar transmittance (nullable, populated only when windows carry
transmission_details). Windows with orientation outside 1-8 (0,
NR) contribute to count and total area but no octant.

Also: epc codes CSV (gov api /api/codes export, RdSAP-Schema-21.x +
older versions) moved next to EpcPropertyData as epc_codes.csv —
canonical SAP enum source for upcoming categorical-share slices.
.gitignore exception added so the reference CSV is tracked.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-16 15:32:45 +00:00
Khalim Conn-Kowlessar
81f6163295 added ucl corrected peui 2026-05-16 14:39:24 +00:00
Khalim Conn-Kowlessar
a64e7e74c5 adding kwh feidls to EpcPropertyData and testing to_row 2026-05-16 14:33:25 +00:00
Jun-te Kim
dfc100f78b rank address similiarity 2026-05-12 16:02:01 +00:00
Jun-te Kim
27f2ef5e83 get rid of duplicate function and make better sensible variable name 2026-05-12 13:46:02 +00:00
Jun-te Kim
35d191c70e merged from main and resolved pytest.ini confict 2026-05-12 12:54:28 +00:00
Jun-te Kim
e06ead55d0 add more type hint 2026-05-12 09:48:21 +00:00
Jun-te Kim
197e9a0e00 added histroci_epc.csv 2026-05-11 15:21:16 +00:00
Jun-te Kim
6504785e7c merged from main 2026-05-11 12:30:29 +00:00
Jun-te Kim
bf91722f30 renamed a function to be self commenting 2026-05-11 08:45:26 +00:00
Jun-te Kim
fb758b76bf changed to utils 2026-05-11 08:37:44 +00:00
Jun-te Kim
c9c43f178c demo generated for use in address2uprn 2026-05-08 14:48:15 +00:00
Jun-te Kim
7a49f5df20 save plan temporary while i incorporate skills to claude 2026-05-08 12:19:03 +00:00
Jun-te Kim
a39c3a0772 added added historic epc data class with shape 2026-05-08 12:03:35 +00:00
Jun-te Kim
32bf1cc98d load historic epc from csv 🟢 2026-05-07 16:26:29 +00:00
Jun-te Kim
74b7b87de6 load historic epc from csv 🟥 2026-05-07 16:22:41 +00:00
Jun-te Kim
02fb3afbe4 defined histrocial epc data shapre from csv 2026-05-07 16:04:01 +00:00
Daniel Roth
78da2f88b6 Handle wall thickness "Unmeasurable" 🟩 2026-04-30 16:41:16 +00:00
Khalim Conn-Kowlessar
a1b207ba55 bolstering testing 2026-04-28 13:46:09 +00:00
Khalim Conn-Kowlessar
001e9ce882 remove inline import 2026-04-28 12:03:39 +00:00
Khalim Conn-Kowlessar
821a0a08f7 addressing feedback on from_api_response 2026-04-28 12:02:34 +00:00
Daniel Roth
51bd18e0d7 Rename window frame material column 🟩 2026-04-27 16:11:32 +00:00
Daniel Roth
01ebb2e0e1 extract window frame details from elmhurst site notes 🟩 2026-04-27 16:04:02 +00:00
Daniel Roth
8f94bb5435 extract window frame details from elmhurst site notes 🟥 2026-04-27 15:50:25 +00:00
Daniel Roth
268576e345 map elmhurst window transmission details to epc property data class 🟩 2026-04-27 14:16:59 +00:00
Daniel Roth
5940977bb2 tweak window transmission data source type 2026-04-27 14:11:33 +00:00
Daniel Roth
865ee3eada map elmhurst energy fields to epc property data class 🟩 2026-04-27 12:16:26 +00:00
Daniel Roth
afedbd2365 add energy fields to ElmhurstSiteNotes 2026-04-27 12:09:35 +00:00
Daniel Roth
6cc73b6ebf remove unused import 2026-04-27 11:06:58 +00:00
Khalim Conn-Kowlessar
d338be867b added missing files 2026-04-25 22:41:57 +00:00
Khalim Conn-Kowlessar
3ed25030d4 added new api call for new epc api 2026-04-25 22:17:38 +00:00
Daniel Roth
b36c8b884c map remaining Elmhurst fields to EpcPropertyData 🟩 2026-04-24 15:33:59 +00:00
Daniel Roth
1105491141 Map Elmhurst site notes to EpcPropertyData 🟩 2026-04-24 13:52:02 +00:00
Daniel Roth
540ee2c3c1 Elmhurst site notes dataclasses 2026-04-24 13:07:20 +00:00
Daniel Roth
5aa86a8610 address fields are not optional 2026-04-24 10:22:41 +00:00
Daniel Roth
691d6f04a8 extend EpcPropertyData domain model with site-notes-only fields 🟩 2026-04-23 14:50:41 +00:00
Daniel Roth
308abd359c extend EpcPropertyData domain model with site-notes-only fields 🟥 2026-04-23 14:39:04 +00:00