mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-30 13:10:47 +00:00
Fold landlord overrides onto the lodged EPC to form the Effective EPC 🟩
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
db1e283b07
commit
6eaf7456c2
2 changed files with 98 additions and 6 deletions
|
|
@ -1,10 +1,12 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Literal, Optional
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Literal, Optional, Sequence
|
||||
|
||||
from datatypes.epc.domain.epc_property_data import EpcPropertyData
|
||||
from domain.geospatial.planning_restrictions import PlanningRestrictions
|
||||
from domain.modelling.scoring.overlay_applicator import apply_simulations
|
||||
from domain.modelling.simulation import EpcSimulation
|
||||
from domain.property.site_notes import SiteNotes
|
||||
|
||||
SourcePath = Literal["site_notes", "epc_with_overlay", "predicted"]
|
||||
|
|
@ -43,6 +45,11 @@ class Property:
|
|||
# structural). Used as the Effective EPC only as a last resort — when there is
|
||||
# neither a lodged EPC nor Site Notes; a real source always wins.
|
||||
predicted_epc: Optional[EpcPropertyData] = None
|
||||
# Resolved Landlord Overrides as Simulation Overlays, folded onto the lodged
|
||||
# EPC to form the Effective EPC (ADR-0032). Empty when the Property has no
|
||||
# overrides — the EPC is then returned unchanged. Only applied on the
|
||||
# `epc_with_overlay` path; never when Site Notes are the source.
|
||||
landlord_overrides: Sequence[EpcSimulation] = field(default_factory=tuple)
|
||||
# The current open-market value (a Property Valuation) — externally sourced
|
||||
# and mostly absent; feeds the Plan's Valuation Uplift £ forms (ADR-0018).
|
||||
current_market_value: Optional[float] = None
|
||||
|
|
@ -78,10 +85,11 @@ class Property:
|
|||
def effective_epc(self) -> EpcPropertyData:
|
||||
"""The EpcPropertyData the modelling pipeline scores against.
|
||||
|
||||
Path 1: the Site Notes' surveyed data. Path 2: the public EPC (Landlord
|
||||
Overrides overlay is a later slice — returned as-is for now). Path 3: a
|
||||
neighbour-synthesised EPC (EPC Prediction gap-fill, ADR-0031), used only
|
||||
when neither real source is present.
|
||||
Path 1: the Site Notes' surveyed data. Path 2: the public EPC with any
|
||||
Landlord Overrides folded on as Simulation Overlays (ADR-0032) — returned
|
||||
as-is when there are none. Path 3: a neighbour-synthesised EPC (EPC
|
||||
Prediction gap-fill, ADR-0031), used only when neither real source is
|
||||
present.
|
||||
"""
|
||||
if self.source_path == "site_notes":
|
||||
assert self.site_notes is not None
|
||||
|
|
@ -90,4 +98,6 @@ class Property:
|
|||
assert self.predicted_epc is not None
|
||||
return self.predicted_epc
|
||||
assert self.epc is not None
|
||||
if self.landlord_overrides:
|
||||
return apply_simulations(self.epc, self.landlord_overrides)
|
||||
return self.epc
|
||||
|
|
|
|||
82
tests/domain/property/test_property_landlord_overlay.py
Normal file
82
tests/domain/property/test_property_landlord_overlay.py
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
"""Effective EPC on the `epc_with_overlay` path folds Landlord Overrides (ADR-0032).
|
||||
|
||||
When a Property has a lodged EPC and resolved Landlord Overrides (as Simulation
|
||||
Overlays), the Effective EPC is the lodged EPC with those overlays applied — so
|
||||
the calculator scores what the landlord knows beyond the cert. With no overrides
|
||||
the lodged EPC is returned unchanged.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from datatypes.epc.domain.epc_property_data import (
|
||||
BuildingPartIdentifier,
|
||||
EpcPropertyData,
|
||||
)
|
||||
from domain.epc.wall_type_overlay import wall_overlay_for
|
||||
from domain.property.property import Property, PropertyIdentity
|
||||
|
||||
_JSON_SAMPLES = Path(__file__).resolve().parents[3] / "backend/epc_api/json_samples"
|
||||
|
||||
|
||||
def _epc() -> EpcPropertyData:
|
||||
raw: dict[str, Any] = json.loads(
|
||||
(_JSON_SAMPLES / "RdSAP-Schema-21.0.0" / "epc.json").read_text()
|
||||
)
|
||||
from datatypes.epc.domain.mapper import EpcPropertyDataMapper
|
||||
|
||||
return EpcPropertyDataMapper.from_api_response(raw)
|
||||
|
||||
|
||||
def _identity() -> PropertyIdentity:
|
||||
return PropertyIdentity(
|
||||
portfolio_id=1, postcode="A0 0AA", address="1 Some Street", uprn=12345
|
||||
)
|
||||
|
||||
|
||||
def _main_wall(epc: EpcPropertyData) -> Any:
|
||||
return next(
|
||||
part
|
||||
for part in epc.sap_building_parts
|
||||
if part.identifier is BuildingPartIdentifier.MAIN
|
||||
)
|
||||
|
||||
|
||||
def test_effective_epc_folds_the_wall_override_onto_the_main_part() -> None:
|
||||
# Arrange — a Property with a lodged EPC and a solid-brick/internal-insulation
|
||||
# wall override.
|
||||
overlay = wall_overlay_for("Solid brick, with internal insulation", 0)
|
||||
assert overlay is not None
|
||||
prop = Property(identity=_identity(), epc=_epc(), landlord_overrides=[overlay])
|
||||
|
||||
# Act
|
||||
main = _main_wall(prop.effective_epc)
|
||||
|
||||
# Assert — the override's codes are present on the main wall.
|
||||
assert main.wall_construction == 3
|
||||
assert main.wall_insulation_type == 3
|
||||
|
||||
|
||||
def test_effective_epc_is_the_lodged_epc_when_there_are_no_overrides() -> None:
|
||||
# Arrange — a Property with an EPC and no Landlord Overrides.
|
||||
prop = Property(identity=_identity(), epc=_epc())
|
||||
|
||||
# Act
|
||||
effective = prop.effective_epc
|
||||
|
||||
# Assert — the lodged EPC is returned untouched (same object, no fold).
|
||||
assert effective is prop.epc
|
||||
|
||||
|
||||
def test_baseline_wall_is_unchanged_when_no_override_applies() -> None:
|
||||
# Arrange — the lodged main wall is cavity (construction 4).
|
||||
prop = Property(identity=_identity(), epc=_epc())
|
||||
|
||||
# Act
|
||||
main = _main_wall(prop.effective_epc)
|
||||
|
||||
# Assert
|
||||
assert main.wall_construction == 4
|
||||
Loading…
Add table
Reference in a new issue