Model/backend/documents_parser
Khalim Conn-Kowlessar a461b70d19 Slice S0380.112: per-BP rooflight allocation (RdSAP 10 §3.7 p.19)
RdSAP 10 §3.7 (PDF p.19) verbatim:

  "for each building part, software will deduct window/door areas
  contained in the relevant wall areas"

The same per-BP deduction applies to roof windows / rooflights
piercing each BP's roof. Pre-slice the cascade lumped every
rooflight's area onto BP[0] Main's `rw_area_part` (S0380.106-era
convention), leaving the actual host BP's gross roof un-deducted.

Cert 000565 §11 Openings lodges:
  Roof Windows 1(Ext2)  External roof Ext2, 1.20 m²
  Roof Windows 2(Ext4)  External roof Ext4, 0.50 m²

Worksheet (30) ground truth — each rooflight deducts from its
host BP's gross roof:
  Ext2: 25.00 − 1.20 = 23.80 net × 0.30 = 7.1400 W/K
  Ext4:  3.00 − 0.50 =  2.50 net × 0.00 = 0.0000 W/K

Pre-slice cascade:
  Ext2: 25.00 (un-deducted) × 0.30 = 7.5000 (+0.36 W/K over)
  Plus 1.70 m² of RW area lumped onto Main's external aggregate
  → +1.20 m² double-count (Ext2 gross + Main rw_area_part)

3-layer fix:
1. `datatypes/epc/domain/epc_property_data.py`: add `window_location:
   Union[int, str] = 0` to SapRoofWindow (mirror of
   `SapWindow.window_location` shape).
2. `datatypes/epc/domain/mapper.py` `_map_elmhurst_roof_window`:
   thread `w.building_part` through (mirror of
   `_map_elmhurst_window`'s pass-through).
3. `domain/sap10_calculator/worksheet/heat_transmission.py`: pre-loop
   compute `rw_area_by_bp[i]` from each `SapRoofWindow.window_location`
   via the existing `_window_bp_index` resolver; per-BP loop reads
   `rw_area_by_bp[i]` instead of allocating everything to BP[0].

Cohort safety: cert 000516's lone rooflight is on the Main BP
(Summary §11 row "Main, External wall"), so the per-BP allocation
returns Main = 0 = same as the prior lump-on-Main convention. The
000516 hand-built fixture's SapRoofWindow now sets
`window_location="Main"` to mirror the Elmhurst mapper string-form.

Cert 000565 cascade snapshot (HEAD 794ef7ed → this):
  roof_w_per_k          51.6773 → 51.3185 (Δ +0.30 → -0.06)
  total_external_area  858.66  → 857.46  (Δ +1.02 → -0.18)
  thermal_bridging_w/k 128.80  → 128.62  (Δ +0.15 → -0.03)
  sap_score (int)          28 → 29 ✓ EXACT (recovered)
  sap_score_continuous 28.4903 → 28.5027  (Δ -0.0184 → -0.0060)
  ecf                   5.3887 →  5.3877
  total_fuel_cost_gbp  4681.89 → 4681.01
  co2_kg_per_yr        6449.73 → 6448.59
  space_heating_kwh   59031.86 → 59019.21
  main_heating_fuel   34724.63 → 34715.31

Closes the +1.20 m² Ext2 rooflight double-count. Remaining
residuals (Ext3 -0.17 m² + -0.06 W/K) closed by S0380.113 (H=0
gable retention).

Pyright net-zero (58 → 58 errors across touched files).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 19:15:16 +00:00
..
handler address JTK review comments 2026-04-20 15:11:17 +00:00
tests Slice S0380.112: per-BP rooflight allocation (RdSAP 10 §3.7 p.19) 2026-05-30 19:15:16 +00:00
__init__.py Map to RdSapSiteNotes from site notes JSON 🟥 2026-04-16 13:54:03 +00:00
db_writer.py include updating epc_property_data to pashub to ara workflow 2026-04-29 09:55:14 +00:00
elmhurst_extractor.py Slice S0380.102: Wire MEV decentralised cascade into pumps_fans (SAP 10.2 §2.6.4 + Table 4f line 230a) 2026-05-30 15:45:55 +00:00
extractor.py Handle wall thickness "Unmeasurable" 🟩 2026-04-30 16:41:16 +00:00
local_runner.py update local runner to work for elmhurst 2026-04-24 14:01:36 +00:00
parser.py load ecmk site notes to db 2026-04-29 11:20:47 +00:00
pdf.py update local runner to work for elmhurst 2026-04-24 14:01:36 +00:00