Model/datatypes/epc
Khalim Conn-Kowlessar 9458a03021 Slice S0380.97: Floor "Insulation Thickness" extractor + mapper (RdSAP 10 §5.13 Table 20)
RdSAP 10 Specification §5.13 "U-values of exposed and semi-exposed
upper floors" (PDF p.47) + Table 20:

  "Otherwise, to simplify data collection no distinction is made in
   terms of U-value between an exposed floor (to outside air below)
   and a semi-exposed floor (to an enclosed but unheated space
   below) and the U-values in Table 20 are used."

  Table 20 (excerpt, age bands A-G | H or I):
    Age band     Unknown/as built   50mm   100mm   150mm
    A to G            1.20           0.50   0.30    0.22
    H or I            0.51           0.50   0.30    0.22

Cert 000565 Summary §9 2nd Extension lodges:
  Location:               U Above unheated space
  Type:                   N Suspended, not timber
  Insulation:             R Retro-fitted
  Insulation Thickness:   200 mm
  Default U-value:        0.22

Pre-slice the extractor's `_floor_details_from_lines` did NOT read
the "Insulation Thickness" cell (only the §8 roof extractor had the
field). FloorDetails carried no thickness → mapper plumbed
`SapBuildingPart.floor_insulation_thickness=None` → cascade
`u_exposed_floor(age=H, ins=None)` returned U=0.51 (Table 20 row[0]
unknown/as-built) vs worksheet 0.22 (Table 20 150 mm column for
age H) — over-counting BP[2] floor by (0.51-0.22) × 30 m² = +8.70
W/K.

Three-layer fix:

1. Schema (`elmhurst_site_notes.py:FloorDetails`) — add
   `insulation_thickness_mm: Optional[int] = None` (mirror of
   `RoofDetails`).
2. Extractor (`elmhurst_extractor.py:_floor_details_from_lines`) —
   parse "Insulation Thickness" via existing `_local_val` (mirror of
   `_roof_details_from_lines` pattern at line 333).
3. Mapper (`mapper.py:_map_elmhurst_building_part`) — translate
   `floor.insulation_thickness_mm` to `SapBuildingPart.floor_
   insulation_thickness=f"{n}mm"` (digit-prefix string convention
   matching the API mapper + the wall pattern at line 3125-3129).

Cascade no-op: existing `_parse_thickness_mm` accepts "200mm" → 200;
`u_exposed_floor(age=H, ins=200)` returns 0.22 (clamps thickness ≥
125 mm to Table 20 row[3]) ✓.

Movement at HEAD (cert 000565):
- BP[2] Ext2 floor cascade U: 0.51 → 0.22 ✓ EXACT vs ws 0.22
- floor_w_per_k: 70.37 → 61.67 ✓ EXACT vs ws 61.67 (closed +8.70)
- sap_score (int): 28 → 29 ✓ EXACT vs ws 29
- sap_score_continuous: 28.31 → 28.5086 vs ws 28.5087 (Δ -0.20 →
  -0.0001 — within 1e-4 strict floor!)
- SH: -38 kWh vs ws (was +218 → essentially closed)

Test count: 587 → 590 pass (+2 new AAA tests + sap_score integer
pin flipped from FAIL to PASS) + 8 expected 000565 fails (sap_score
integer pin removed from the work queue).

Cohort safety: only cert 000565 §9 lodges "Insulation Thickness"
(grep audit across Summary fixtures); cohort certs lodge "As built"
or omit the line. Pyright net-zero per touched file.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 16:28:47 +00:00
..
domain Slice S0380.97: Floor "Insulation Thickness" extractor + mapper (RdSAP 10 §5.13 Table 20) 2026-06-01 16:28:47 +00:00
loaders demo generated for use in address2uprn 2026-05-08 14:48:15 +00:00
schema Slice S0380.48: surface real-API pv_batteries[].battery_capacity (5 kWh) 2026-06-01 16:28:47 +00:00
search bolstering testing 2026-04-28 13:46:09 +00:00
surveys Slice S0380.97: Floor "Insulation Thickness" extractor + mapper (RdSAP 10 §5.13 Table 20) 2026-06-01 16:28:47 +00:00
__init__.py testing out rebaselining 2026-02-12 22:25:03 +00:00
construction_age_band.py testing out rebaselining 2026-02-12 22:25:03 +00:00
efficiency.py beginning to assembly the parity class 2026-02-04 18:34:59 +00:00
floor.py preparing partiy class 2026-02-05 08:54:27 +00:00
fuel.py beginning to assembly the parity class 2026-02-04 18:34:59 +00:00
heating_controls.py beginning to assembly the parity class 2026-02-04 18:34:59 +00:00
hotwater.py beginning to assembly the parity class 2026-02-04 18:34:59 +00:00
main_heating.py beginning to assembly the parity class 2026-02-04 18:34:59 +00:00
property_type_built_form.py beginning to assembly the parity class 2026-02-04 18:34:59 +00:00
roof.py beginning to assembly the parity class 2026-02-04 18:34:59 +00:00
walls.py beginning to assembly the parity class 2026-02-04 18:34:59 +00:00
windows.py testing out rebaselining 2026-02-12 22:25:03 +00:00