mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
Slice S0380.83: Extractor + mapper recognise Exposed / Connected gable_type per RdSAP 10 §3.10
The Elmhurst Summary PDF §8.1 "Room(s) in Roof" per-surface table publishes the
gable-wall environment column with one of four values:
Party → §8.1 party-wall row
Sheltered → §8.1 sheltered external row
Exposed → §8.1 exposed external row
Connected (to heated space) → §8.1 internal partition
Per RdSAP 10 §3.10 (PDF p.30-35) "Detailed Room-in-Roof" + Table 4 (p.22)
"Heat-loss surface variants":
- Exposed gable wall → external wall at the lodged U-value
- Sheltered gable wall → external wall at the lodged U-value
- Party gable wall → party wall at U=0.25 (Table 4 row 2)
- Connected gable wall → internal partition to heated space, NOT a
heat-loss surface
The extractor was only capturing `gable_type ∈ {"Party", "Sheltered",
"Connected to heated space"}` — neither `"Exposed"` (every external gable
on cert 000565) nor the plain `"Connected"` string (the actual PDF
lodging value, vs the verbose "Connected to heated space" form used on
other Summary schemas) was recognised. Both fell through with
`gable_type=None`, masking the downstream cascade gap (cert 000565
BP[0] Main Gable Wall 1 is lodged "Exposed" at U=0.35 but extracted
as untyped → mapper routes to `gable_wall` party at U=0.25, vs the
worksheet's "Roof room Main Gable Wall 1" at U=0.35).
This slice closes the extractor side only:
backend/documents_parser/elmhurst_extractor.py:_parse_rir_surface_row
expands its `gable_type` lookup set to include "Exposed" and the
plain "Connected" lodging value.
Mapper-side: `_map_elmhurst_rir_surface` (datatypes/epc/domain/mapper.py)
preserves cert 9501's behaviour — its flat-RR elif previously hinged
on `surface.gable_type is None and is_flat`; now extends to
`surface.gable_type in (None, "Exposed") and is_flat` so the same
flat-RR routing fires whichever lodging shape the Summary PDF uses.
Net cascade impact: zero. Cert 9501 (top-floor flat) retains its
RR-gables-as-external routing. Cert 000565 (house) keeps falling
through to the default `gable_wall` (party at U=0.25) routing for
"Exposed" + "Connected" gables — the next slice in the block reroutes
those to external walls + drops Connected surfaces per RdSAP 10
Table 4. This commit is pure data-extraction completion; pin
movement lands when S0380.84 wires the mapper through.
Test baseline: 555 pass + 8 expected `test_sap_result_pin[000565-*]`
fails (was 554 + 8 at S0380.82; one new test pins the spec rule).
Pyright net-zero on touched files (45 errors, matches baseline).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
2d9cb995e6
commit
f1096f13aa
3 changed files with 85 additions and 2 deletions
|
|
@ -523,7 +523,10 @@ class ElmhurstSiteNotesExtractor:
|
|||
insulation = t
|
||||
elif t in ("Mineral or EPS", "PUR", "PIR"):
|
||||
insulation_type = t
|
||||
elif t in ("Party", "Sheltered", "Connected to heated space"):
|
||||
elif t in (
|
||||
"Party", "Sheltered", "Exposed",
|
||||
"Connected", "Connected to heated space",
|
||||
):
|
||||
gable_type = t
|
||||
return RoomInRoofSurface(
|
||||
name=name,
|
||||
|
|
|
|||
|
|
@ -509,6 +509,79 @@ def test_summary_9501_rr_gable_walls_route_to_external_walls_hlc() -> None:
|
|||
assert abs(ht.walls_w_per_k - worksheet_walls_w_per_k) <= 1e-2
|
||||
|
||||
|
||||
def test_summary_000565_extractor_recognises_exposed_and_connected_gable_types() -> None:
|
||||
"""Summary PDF §8.1 Room(s) in Roof per-surface table lists the
|
||||
gable-wall environment column with one of four published values:
|
||||
|
||||
Party → §8.1 party-wall row
|
||||
Sheltered → §8.1 sheltered external row
|
||||
Exposed → §8.1 exposed external row
|
||||
Connected (to heated space) → §8.1 internal partition
|
||||
|
||||
Per RdSAP 10 §3.10 (PDF p.30-35) "Detailed Room-in-Roof" + Table 4
|
||||
(p.22) "Heat-loss surface variants":
|
||||
|
||||
- Exposed gable wall → external wall at the lodged U-value (or
|
||||
the BP main-wall U when the lodged value is the default)
|
||||
- Sheltered gable wall → external wall at the lodged U-value
|
||||
- Party gable wall → party wall at U=0.25 (Table 4 row 2)
|
||||
- Connected gable wall → internal partition to heated space,
|
||||
NOT a heat-loss surface (drops from external + party totals)
|
||||
|
||||
The extractor was only capturing `gable_type ∈ {"Party",
|
||||
"Sheltered", "Connected to heated space"}` — neither `"Exposed"`
|
||||
(every external gable on cert 000565) nor the plain `"Connected"`
|
||||
string (the actual lodging used in Summary PDFs vs the verbose
|
||||
"Connected to heated space") was recognised. Both fell through
|
||||
with `gable_type=None`, masking the downstream cascade gap (cert
|
||||
000565 BP[0] Main Gable Wall 1 is lodged "Exposed" at U=0.35 but
|
||||
extracted as untyped → mapper routes to `gable_wall` (party at
|
||||
U=0.25) — see worksheet "Roof room Main Gable Wall 1" line at
|
||||
U=0.35).
|
||||
|
||||
This pin asserts the extractor surfaces the lodged environment
|
||||
column verbatim. The downstream mapper + cascade behaviour stays
|
||||
unchanged until follow-up slices use the new field — this is a
|
||||
pure extractor data-completion step (no test pins move).
|
||||
"""
|
||||
# Arrange
|
||||
pages = _summary_pdf_to_textract_style_pages(_SUMMARY_000565_PDF)
|
||||
site_notes = ElmhurstSiteNotesExtractor(pages).extract()
|
||||
|
||||
# Act — Main BP gables; Ext1/Ext2 gables expose both "Connected"
|
||||
# and "Exposed" values from the cert lodging.
|
||||
rir_main = site_notes.room_in_roof
|
||||
main_surfaces = {s.name: s for s in (rir_main.surfaces if rir_main else [])}
|
||||
rir_ext1 = (
|
||||
site_notes.extensions[0].room_in_roof
|
||||
if site_notes.extensions and len(site_notes.extensions) > 0
|
||||
else None
|
||||
)
|
||||
ext1_surfaces = {s.name: s for s in (rir_ext1.surfaces if rir_ext1 else [])}
|
||||
|
||||
# Assert
|
||||
# Main BP[0]: Gable Wall 1 lodged "Exposed" (default U 0.35); Gable
|
||||
# Wall 2 lodged "Sheltered" (default U 0.30).
|
||||
assert main_surfaces["Gable Wall 1"].gable_type == "Exposed", (
|
||||
f"Main Gable Wall 1 gable_type = "
|
||||
f"{main_surfaces['Gable Wall 1'].gable_type!r}; expected 'Exposed'"
|
||||
)
|
||||
assert main_surfaces["Gable Wall 2"].gable_type == "Sheltered", (
|
||||
f"Main Gable Wall 2 gable_type = "
|
||||
f"{main_surfaces['Gable Wall 2'].gable_type!r}; expected 'Sheltered'"
|
||||
)
|
||||
# Ext1 BP[1]: Gable Wall 1 lodged "Connected" (internal partition);
|
||||
# Gable Wall 2 lodged "Exposed" (default U 1.70).
|
||||
assert ext1_surfaces["Gable Wall 1"].gable_type == "Connected", (
|
||||
f"Ext1 Gable Wall 1 gable_type = "
|
||||
f"{ext1_surfaces['Gable Wall 1'].gable_type!r}; expected 'Connected'"
|
||||
)
|
||||
assert ext1_surfaces["Gable Wall 2"].gable_type == "Exposed", (
|
||||
f"Ext1 Gable Wall 2 gable_type = "
|
||||
f"{ext1_surfaces['Gable Wall 2'].gable_type!r}; expected 'Exposed'"
|
||||
)
|
||||
|
||||
|
||||
def test_summary_9501_pv_array_surfaced_from_elmhurst_section_19() -> None:
|
||||
# Arrange — cert 9501's Elmhurst §19.0 PV section lodges measured
|
||||
# array detail (2.36 kWp, South-West orientation, 45° elevation,
|
||||
|
|
|
|||
|
|
@ -3277,12 +3277,19 @@ def _map_elmhurst_rir_surface(
|
|||
if kind == "gable_wall" and surface.gable_type == "Sheltered":
|
||||
kind = "gable_wall_external"
|
||||
u_value_override = surface.default_u_value
|
||||
elif kind == "gable_wall" and surface.gable_type is None and is_flat:
|
||||
elif (
|
||||
kind == "gable_wall"
|
||||
and surface.gable_type in (None, "Exposed")
|
||||
and is_flat
|
||||
):
|
||||
# Flat with RR: gables are external by default (top of block,
|
||||
# no neighbour above). Lodge as gable_wall_external with no
|
||||
# u_value override so the cascade falls through to the main-
|
||||
# wall U (`uw` in heat_transmission.py:674) — matches cert
|
||||
# 9501's worksheet treatment of both gable walls at U=1.7.
|
||||
# Per Summary PDF schema the gable env column reads "Exposed"
|
||||
# for the same case the legacy heuristic detected via None;
|
||||
# both lodging shapes route here.
|
||||
kind = "gable_wall_external"
|
||||
area_m2 = _round_half_up_2dp(surface.length_m, surface.height_m)
|
||||
if kind in ("gable_wall", "gable_wall_external"):
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue