mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-30 13:10:47 +00:00
Map a landlord wall-type override to a wall Simulation Overlay 🟩
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
80b86d4790
commit
db1e283b07
3 changed files with 136 additions and 0 deletions
60
domain/epc/wall_type_overlay.py
Normal file
60
domain/epc/wall_type_overlay.py
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
"""Map a Landlord-Override `WallType` value to a wall Simulation Overlay (ADR-0032).
|
||||
|
||||
A `WallType` value is one full EPC wall description — *material* (cavity, solid
|
||||
brick, …) combined with *insulation state* (as built / with internal insulation
|
||||
/ filled cavity / …). The calculator scores the wall from the RdSAP
|
||||
`wall_construction` (material) and `wall_insulation_type` (state) **int codes**,
|
||||
never the description string, so the overlay decomposes the value into those two
|
||||
codes and emits an `EpcSimulation` targeting the override's building part. The
|
||||
result folds onto the lodged EPC via `apply_simulations`, exactly as a wall
|
||||
Measure's overlay does. Unresolvable material/state → None (no overlay).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from datatypes.epc.domain.epc_property_data import BuildingPartIdentifier
|
||||
from domain.modelling.simulation import BuildingPartOverlay, EpcSimulation
|
||||
|
||||
# RdSAP `wall_construction` codes by material prefix (domain/sap10_ml/rdsap_uvalues.py).
|
||||
_MATERIAL_CONSTRUCTION: dict[str, int] = {
|
||||
"Cavity wall": 4,
|
||||
"Solid brick": 3,
|
||||
}
|
||||
|
||||
# RdSAP `wall_insulation_type` codes by insulation-state suffix
|
||||
# (domain/sap10_ml/rdsap_uvalues.py): external 1, filled-cavity 2, internal 3,
|
||||
# as-built/uninsulated 4, cavity+external 6, cavity+internal 7.
|
||||
_STATE_INSULATION: dict[str, int] = {
|
||||
"as built, no insulation (assumed)": 4,
|
||||
"with internal insulation": 3,
|
||||
"with external insulation": 1,
|
||||
"filled cavity": 2,
|
||||
"filled cavity and internal insulation": 7,
|
||||
"filled cavity and external insulation": 6,
|
||||
}
|
||||
|
||||
|
||||
def wall_overlay_for(
|
||||
wall_type_value: str, building_part: int
|
||||
) -> Optional[EpcSimulation]:
|
||||
material, _, state = wall_type_value.partition(", ")
|
||||
construction = _MATERIAL_CONSTRUCTION.get(material)
|
||||
insulation = _STATE_INSULATION.get(state)
|
||||
if construction is None or insulation is None:
|
||||
return None
|
||||
|
||||
identifier = (
|
||||
BuildingPartIdentifier.MAIN
|
||||
if building_part == 0
|
||||
else BuildingPartIdentifier.extension(building_part)
|
||||
)
|
||||
return EpcSimulation(
|
||||
building_parts={
|
||||
identifier: BuildingPartOverlay(
|
||||
wall_construction=construction,
|
||||
wall_insulation_type=insulation,
|
||||
)
|
||||
}
|
||||
)
|
||||
|
|
@ -25,6 +25,10 @@ class BuildingPartOverlay:
|
|||
A `None` field means "leave the baseline value unchanged".
|
||||
"""
|
||||
|
||||
# The wall material (RdSAP `wall_construction` code). Left `None` by Measures
|
||||
# — insulating a wall doesn't change its material — but set by a Landlord
|
||||
# Override that corrects the construction itself (ADR-0032).
|
||||
wall_construction: Optional[int] = None
|
||||
wall_insulation_type: Optional[int] = None
|
||||
# Added solid-wall insulation depth (mm) — drives the calculator's Table 6
|
||||
# bucket / §5.8 documentary U-value for EWI (`wall_insulation_type=1`) and
|
||||
|
|
|
|||
72
tests/domain/epc/test_wall_type_overlay.py
Normal file
72
tests/domain/epc/test_wall_type_overlay.py
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
"""The Landlord-Override `WallType` → wall Simulation Overlay mapping (ADR-0032).
|
||||
|
||||
A `WallType` value decomposes into the RdSAP `wall_construction` (material) and
|
||||
`wall_insulation_type` (state) int codes the calculator reads; the overlay
|
||||
targets the override's building part. Unresolvable values produce no overlay.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from datatypes.epc.domain.epc_property_data import BuildingPartIdentifier
|
||||
from domain.epc.wall_type_overlay import wall_overlay_for
|
||||
|
||||
|
||||
def test_solid_brick_with_internal_insulation_overlays_main_wall() -> None:
|
||||
# Act
|
||||
simulation = wall_overlay_for("Solid brick, with internal insulation", 0)
|
||||
|
||||
# Assert — solid brick (wall_construction 3) + internal insulation (type 3)
|
||||
# on the main building part.
|
||||
assert simulation is not None
|
||||
overlay = simulation.building_parts[BuildingPartIdentifier.MAIN]
|
||||
assert overlay.wall_construction == 3
|
||||
assert overlay.wall_insulation_type == 3
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("wall_type_value", "construction", "insulation"),
|
||||
[
|
||||
("Cavity wall, as built, no insulation (assumed)", 4, 4),
|
||||
("Cavity wall, with internal insulation", 4, 3),
|
||||
("Cavity wall, with external insulation", 4, 1),
|
||||
("Cavity wall, filled cavity", 4, 2),
|
||||
("Cavity wall, filled cavity and internal insulation", 4, 7),
|
||||
("Cavity wall, filled cavity and external insulation", 4, 6),
|
||||
("Solid brick, as built, no insulation (assumed)", 3, 4),
|
||||
("Solid brick, with external insulation", 3, 1),
|
||||
],
|
||||
)
|
||||
def test_material_and_state_decompose_to_their_gov_codes(
|
||||
wall_type_value: str, construction: int, insulation: int
|
||||
) -> None:
|
||||
# Act
|
||||
simulation = wall_overlay_for(wall_type_value, 0)
|
||||
|
||||
# Assert
|
||||
assert simulation is not None
|
||||
overlay = simulation.building_parts[BuildingPartIdentifier.MAIN]
|
||||
assert overlay.wall_construction == construction
|
||||
assert overlay.wall_insulation_type == insulation
|
||||
|
||||
|
||||
def test_overlay_targets_the_extension_building_part() -> None:
|
||||
# Act — building_part 1 is the first extension.
|
||||
simulation = wall_overlay_for("Solid brick, with internal insulation", 1)
|
||||
|
||||
# Assert
|
||||
assert simulation is not None
|
||||
assert BuildingPartIdentifier.EXTENSION_1 in simulation.building_parts
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"wall_type_value",
|
||||
["Unknown", "Granite or whin, as built, no insulation (assumed)", ""],
|
||||
)
|
||||
def test_unresolvable_wall_type_produces_no_overlay(wall_type_value: str) -> None:
|
||||
# Act
|
||||
simulation = wall_overlay_for(wall_type_value, 0)
|
||||
|
||||
# Assert
|
||||
assert simulation is None
|
||||
Loading…
Add table
Reference in a new issue