mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
feat(modelling): flat gate drops EWI on solid-wall insulation
Slice 2d. A flat can take IWI (its own unit) but not EWI (whole-block
coordination) — ADR-0019. _is_flat handles both ingestion representations:
the Elmhurst name form ('Flat') and the API stringified RdSAP code ('2' = Flat
per PROPERTY_TYPE_LOOKUP). Completes slice 2's eligibility surface.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
51ea4993a0
commit
0cef044503
2 changed files with 58 additions and 4 deletions
|
|
@ -22,6 +22,7 @@ from datatypes.epc.domain.epc_property_data import (
|
|||
BuildingPartIdentifier,
|
||||
EpcPropertyData,
|
||||
)
|
||||
from datatypes.epc.domain.field_mappings import PROPERTY_TYPE_LOOKUP
|
||||
from domain.building_geometry import gross_heat_loss_wall_area
|
||||
from domain.modelling.recommendation import Cost, MeasureOption, Recommendation
|
||||
from domain.modelling.simulation import BuildingPartOverlay, EpcSimulation
|
||||
|
|
@ -109,11 +110,26 @@ def _solid_wall_option(
|
|||
)
|
||||
|
||||
|
||||
def _allowed(measure_type: str, restrictions: PlanningRestrictions) -> bool:
|
||||
def _is_flat(epc: EpcPropertyData) -> bool:
|
||||
"""Whether the dwelling is a flat. The Elmhurst path lodges the name
|
||||
("Flat"); the API path a stringified RdSAP code (`PROPERTY_TYPE_LOOKUP`,
|
||||
where 2 = Flat) — handle both representations."""
|
||||
raw: str = (epc.property_type or "").strip()
|
||||
if raw.lower() == "flat":
|
||||
return True
|
||||
if raw.isdigit():
|
||||
return PROPERTY_TYPE_LOOKUP.get(int(raw)) == "Flat"
|
||||
return False
|
||||
|
||||
|
||||
def _allowed(
|
||||
measure_type: str, restrictions: PlanningRestrictions, is_flat: bool
|
||||
) -> bool:
|
||||
"""Whether a planning-gated Option survives (ADR-0019): EWI is removed by any
|
||||
restriction; IWI only by a listed/heritage protection."""
|
||||
restriction or by the dwelling being a flat; IWI only by a listed/heritage
|
||||
protection."""
|
||||
if measure_type == _EXTERNAL_MEASURE_TYPE:
|
||||
return not restrictions.blocks_external
|
||||
return not (restrictions.blocks_external or is_flat)
|
||||
return not restrictions.blocks_internal
|
||||
|
||||
|
||||
|
|
@ -141,10 +157,11 @@ def recommend_solid_wall(
|
|||
if not measure_types:
|
||||
return None
|
||||
|
||||
is_flat: bool = _is_flat(epc)
|
||||
allowed = tuple(
|
||||
measure_type
|
||||
for measure_type in measure_types
|
||||
if _allowed(measure_type, restrictions)
|
||||
if _allowed(measure_type, restrictions, is_flat)
|
||||
)
|
||||
if not allowed:
|
||||
return None
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ is a named generator/overlay/calculator gap to fix, never a tolerance to widen
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import replace
|
||||
from typing import Final
|
||||
|
||||
from datatypes.epc.domain.epc_property_data import (
|
||||
|
|
@ -228,6 +229,42 @@ def test_listed_building_blocks_all_solid_wall_insulation() -> None:
|
|||
)
|
||||
|
||||
|
||||
def test_flat_drops_ewi_but_keeps_iwi() -> None:
|
||||
# Arrange — a flat can take IWI to its own unit, but EWI needs whole-block
|
||||
# coordination (ADR-0019). property_type "Flat" is the Elmhurst name form.
|
||||
before: EpcPropertyData = parse_recommendation_summary(
|
||||
"solid_brick_ewi_001431_before.pdf"
|
||||
)
|
||||
flat: EpcPropertyData = replace(before, property_type="Flat")
|
||||
|
||||
# Act
|
||||
recommendation: Recommendation | None = recommend_solid_wall(flat, _AnyProduct())
|
||||
|
||||
# Assert
|
||||
assert recommendation is not None
|
||||
assert {option.measure_type for option in recommendation.options} == {
|
||||
"internal_wall_insulation"
|
||||
}
|
||||
|
||||
|
||||
def test_flat_detected_from_api_property_type_code() -> None:
|
||||
# Arrange — the API path lodges property_type as a stringified code
|
||||
# ("2" = Flat per PROPERTY_TYPE_LOOKUP), not the name.
|
||||
before: EpcPropertyData = parse_recommendation_summary(
|
||||
"solid_brick_ewi_001431_before.pdf"
|
||||
)
|
||||
flat: EpcPropertyData = replace(before, property_type="2")
|
||||
|
||||
# Act
|
||||
recommendation: Recommendation | None = recommend_solid_wall(flat, _AnyProduct())
|
||||
|
||||
# Assert — same gate fires regardless of representation.
|
||||
assert recommendation is not None
|
||||
assert {option.measure_type for option in recommendation.options} == {
|
||||
"internal_wall_insulation"
|
||||
}
|
||||
|
||||
|
||||
def test_cavity_wall_gets_no_solid_wall_recommendation() -> None:
|
||||
# Arrange — a cavity wall is handled by recommend_cavity_wall, never here.
|
||||
before: EpcPropertyData = parse_recommendation_summary(
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue