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:
Jun-te Kim 2026-06-16 17:18:31 +00:00
parent db1e283b07
commit 6eaf7456c2
2 changed files with 98 additions and 6 deletions

View file

@ -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

View 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