Commit graph

18 commits

Author SHA1 Message Date
Khalim Conn-Kowlessar
4264e0ad4b Slice 99d: surface PV array from Elmhurst Summary §19.0
Cert 9501 lodges measured PV: 2.36 kWp South-West, 45° pitch, "None
Or Little" overshading. The worksheet's §10a credit (-250.02 GBP =
PV used in dwelling £-129.49 + PV exported £-120.53) depends on the
Appendix M / Appendix U3.3 cascade reading these from
`SapEnergySource.photovoltaic_arrays`. The prior extractor only
captured the `photovoltaic_panel: "Panel details"` label — the
actual kW / orientation / elevation / overshading were silently
dropped, so the cascade computed total cost ~£250 too high → ECF
2.92 vs worksheet 2.26 → SAP 59.26 vs 68.53 (Δ -9.27).

Changes:
- Extend `surveys.elmhurst_site_notes.Renewables` with 4 new
  optional fields: pv_peak_power_kw / pv_orientation /
  pv_elevation_deg / pv_overshading.
- Add `ElmhurstSiteNotesExtractor._extract_pv_array_detail` —
  anchors on "Photovoltaic panel details" then reads the 4
  consecutive value lines (kWp, orientation, elevation, overshading).
- Add `_elmhurst_pv_arrays` mapper helper to build the
  `[PhotovoltaicArray(...)]` list when all 4 values are present;
  return None for the "PV absent" path the cascade already handles.
- Add `_ELMHURST_PV_OVERSHADING_TO_RDSAP` map: "None Or Little" → 1
  (ZPV=1.0 per cert_to_inputs._PV_OVERSHADING_FACTOR), "Modest" →
  2, "Significant" → 3, "Heavy" → 4. RdSAP omits SAP10.2 Table M1's
  5th "Severe" bucket.
- Wire `photovoltaic_arrays=_elmhurst_pv_arrays(survey.renewables)`
  into `from_elmhurst_site_notes`'s `SapEnergySource(...)` call.

Effect on cert 9501 Summary path:
- sap_continuous 59.2585 → 68.7577 (target 68.5252; Δ +0.23)
- total_fuel_cost £1099 → £843 (worksheet £849; -£6 over-credit)
- ECF 2.92 → 2.24 (worksheet 2.26; -0.02 over-credit)

The remaining +0.23 SAP / +£6 cost drift is a precision gap in the
Appendix M cost-offset cascade for measured PV (not a missing-data
gap); next slice closes it to 1e-4.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 21:38:14 +00:00
Khalim Conn-Kowlessar
58088c1056 Slice 53: Summary_000487 chain pins SAP at 1e-4 — last cohort cert closed
Three extensions closing the last 0.05 SAP residual on 000487 — and
with it, all 6 Elmhurst Summary PDFs match their U985 worksheets to
1e-4 unrounded SAP.

1. Alternative-wall extraction. `WallDetails` gains an
   `alternative_walls: List[AlternativeWall]` field; the extractor
   parses §7's "Alternative Wall N Area / Type / Insulation /
   Thickness / Thickness Unknown / U-value Known" prefixed labels.
   Even when an extension lodges "As Main Wall: Yes" we still pull
   alt walls from the extension's own subsection (they don't
   inherit) — the main wall fields are merged with the extension's
   alt-wall list.

2. Alt-wall mapper plumbing. `_map_elmhurst_alternative_wall` builds
   a `SapAlternativeWall` per lodged Elmhurst entry; the building-
   part mapper attaches up to two via `sap_alternative_wall_1/_2`
   per `SapBuildingPart`. When the surveyor flags `Thickness
   Unknown: Yes` (cohort's only example — 000487 Ext1's
   "TimberWallOneLayer" entry) we route the cascade with
   thickness=None so `u_wall` falls through to the age-band-and-
   construction default — Timber Frame age B uninsulated → U=1.9,
   matching the full-cert-text U=1.90 the handbuilt fixture lodges
   for the same 9-mm thin timber wall.

3. "TI" wall-construction code mapping. The §7 "Alternative Wall 1
   Type: TI Timber Frame" uses leading code "TI" rather than the
   "TF" code seen on the primary wall types — both alias to SAP10
   wall_construction=5 (Timber Frame).

Final cohort state — all 6 closed at 1e-4:

  000474   0.0000  ✓ Slice 47
  000477   0.0000  ✓ Slice 52
  000480   0.0000  ✓ Slice 50
  000487   0.0000  ✓ THIS SLICE
  000490   0.0000  ✓ Slice 49
  000516   0.0000  ✓ Slice 51

758 tests pass; pyright net-zero (35 baseline).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-24 21:42:42 +00:00
Khalim Conn-Kowlessar
598f04084a Slice 50: Summary_000480 chain pins SAP at 1e-4; Room-in-Roof + baths + party-wall + roof-none
Four mapper extensions, validated by 000480 closing to 1e-4 and large
gap reductions across 000477/000487/000516.

1. Room-in-Roof support. `ElmhurstSiteNotes` gains `RoomInRoof` +
   `RoomInRoofSurface` dataclasses; extractor parses §8.1 (Flat
   Ceiling / Stud Wall / Slope / Gable Wall / Common Wall) with
   Length × Height + insulation + gable-type + measured-U cells.
   Mapper produces a `SapRoomInRoof` with `detailed_surfaces`
   attached to the Main bp: Stud Walls / Slopes / Flat Ceilings
   route through Table 17 insulation thickness; Gable Walls split
   between `gable_wall` (Party → Table 4 U=0.25) and
   `gable_wall_external` (Sheltered → assessor-lodged U-value
   override, e.g. 000487 Gable Wall 2 at U=0.86). Empty surfaces
   (0×0 — the cohort lodges a full 5-pair table) and Common Walls
   (handled by cascade's Simplified Type 2 geometry) are dropped.
   `total_floor_area_m2` now includes the RR floor area.

2. Party-wall construction mapping. 000516 lodges "S Solid masonry /
   timber / system build" which routes to SAP10 wall_construction=3
   (Solid Brick → U=0.0 via Table 4). The previous mapper used the
   same wall-type table as `wall_construction`, which lacked the
   "S" code and fell through to None (cascade default 0.25). Split
   into a dedicated `_elmhurst_party_wall_construction_int` keyed
   on the party-wall category codes.

3. Roof "None" insulation. When the §8.0 Roofs subsection lodges
   "Insulation N None" without a separate "Insulation Thickness"
   line, treat thickness as 0 mm so the cascade picks Table 16
   row 0 (U=2.30) rather than the age-band default. Closes the
   29 W/K roof-loss gap on 000516.

4. `number_baths` lodgement. `SapHeating.number_baths` now reads
   `survey.baths_and_showers.number_of_baths`. The cascade defaults
   `None → has-bath` for the modal UK case, but explicit `0` lodged
   on 000477/000480 (bathless dwellings, rare) drops the bath HW
   demand line per Table 1b. Closes 000480's last ~0.3 SAP gap.

Cohort state after this slice (target 1e-4):

  000474   0.0000  ✓ Slice 47
  000477  +1.1161     Elmhurst floor_ach quirk (true vs false despite
                      "T Suspended timber" lodged on all certs)
  000480   0.0000  ✓ THIS SLICE
  000487  +1.1844     extractor still drops most §11 windows on this
                      layout variant
  000490   0.0000  ✓ Slice 49
  000516  +0.1774     roof-window separation by U-value heuristic

3/6 certs now closed at 1e-4. Pyright net-zero (35 baseline). Tests
756 pass (added `test_summary_000480_full_chain_sap_matches_worksheet_
pdf_exactly`).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-24 21:09:22 +00:00
Khalim Conn-Kowlessar
7f17de84aa Slice 49: Summary_000490 chain pins SAP at 1e-4; secondary heating + RdSAP sheltered-sides
Two mapper extensions, both validated by 000490 closing to 1e-4:

1. Secondary heating extraction. Elmhurst Summary PDFs lodge the
   secondary heating SAP code in the §14.1 Main Heating2 sub-section
   (between "14.1 Main Heating2" and "14.1 Community Heating") — not
   in the §14.0 Main Heating1 block where the main system lives.
   `ElmhurstMainHeating` gains a `secondary_heating_sap_code` field;
   the extractor reads it from the right section; the mapper threads
   it through to `SapHeating.secondary_heating_type`. The cascade
   then applies Table 11's 10% secondary fraction.

2. Sheltered-sides derivation per RdSAP §S5. The Summary PDF doesn't
   lodge per-dwelling sheltered-sides; the value is derived from
   built-form (Detached=0, Semi-Detached=1, End-Terrace=1, Mid-
   Terrace=2, Enclosed Mid-Terrace=3, Enclosed End-Terrace=2).
   `_map_elmhurst_ventilation` now takes built_form and populates
   `SapVentilation.sheltered_sides`. The table is cross-checked
   against U985-0001-NNNNNN.pdf line (19) across the 6 worksheet
   fixtures.

Cohort SAP deltas after this slice (target 1e-4):

  000474   0.0000  ✓ Slice 47
  000477  +2.6555     diagnosis pending (lighting bulb count diff)
  000480  +4.1955     diagnosis pending
  000487  +4.4553     extractor still drops most windows
  000490   0.0000  ✓ THIS SLICE
  000516  +1.5162     roof-window separation

Pyright net-zero on touched files (35 errors, same baseline). 755
tests pass (up from 754 — new `test_summary_000490_full_chain_sap_
matches_worksheet_pdf_exactly`).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-24 20:13:19 +00:00
Khalim Conn-Kowlessar
36f2c7bbdf Slice 46a: Elmhurst mapper handles multi-bp Summary PDFs — Summary_000474 chain test flips green
ElmhurstSiteNotes had no representation for extensions: singular dimensions / walls / roof / floor fields could only describe the main bp. Summary PDFs lodge "1st Extension" / "2nd Extension" subsections in §4, §7, §8, §9 with optional "As Main: Yes" inheritance. This slice:

- Adds `ExtensionPart` dataclass and `ElmhurstSiteNotes.extensions: List[ExtensionPart]`.
- Adds `_split_section_by_bp` helper + per-bp parsing of dimensions / walls / roof / floor in the extractor; "As Main" inherits from the main bp.
- Refactors `_map_elmhurst_building_part` into a parameterised builder; adds `_map_elmhurst_building_parts` that yields Main + one SapBuildingPart per extension (capped at 4 per RdSAP10 §1.2).
- Scaffold test `test_summary_000474_mapper_produces_three_building_parts` flips from strict-xfail to passing.

Single-bp behaviour is unchanged (empty extensions list defaults). 752 existing tests stay green.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-24 17:55:13 +00:00
Daniel Roth
78da2f88b6 Handle wall thickness "Unmeasurable" 🟩 2026-04-30 16:41:16 +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
afedbd2365 add energy fields to ElmhurstSiteNotes 2026-04-27 12:09:35 +00:00
Daniel Roth
540ee2c3c1 Elmhurst site notes dataclasses 2026-04-24 13:07:20 +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
1e0d72a805 extract secondary heating system 🟥 2026-04-21 11:47:35 +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
edecbe6bef pdf json to RdSapSiteNotes full extract 🟩 2026-04-20 08:10:45 +00:00
Daniel Roth
6415980384 correct broken sitenote parsing tests 2026-04-13 16:22:21 +00:00
Daniel Roth
a7d460a2d4 pashub rdsap sitenotes pdf output to dataclasses 2026-04-13 11:35:02 +00:00