feat(modelling): Epc.sap_lower_bound() — band → minimum SAP (#1160)

Slice 3a. The inverse of Epc.from_sap_score: the minimum SAP rating in a
band (C → 69, B → 81, …), used as the Optimiser's repair target for an
INCREASING_EPC goal (goal_value "C" → target SAP 69). Keeps the
band-target derivation in the domain rather than re-coupling to
backend.app.utils.epc_to_sap_lower_bound. 8 tests incl. round-trip
through from_sap_score; pyright strict clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-06-03 12:50:40 +00:00
parent 49e86344d2
commit 504f592a27
5 changed files with 41 additions and 0 deletions

View file

@ -31,3 +31,18 @@ class Epc(Enum):
if score >= 21:
return cls.F
return cls.G
def sap_lower_bound(self) -> int:
"""The minimum SAP rating in this band — the inverse of
`from_sap_score` (A 92, B 81, C 69, D 55, E 39, F 21,
G 1). Used as an optimisation target, e.g. "reach band C" 69."""
bounds: dict["Epc", int] = {
Epc.A: 92,
Epc.B: 81,
Epc.C: 69,
Epc.D: 55,
Epc.E: 39,
Epc.F: 21,
Epc.G: 1,
}
return bounds[self]

View file

View file

View file

View file

@ -0,0 +1,26 @@
"""Behaviour of the Epc band enum's SAP mapping — the band a SAP rating falls
in, and the minimum SAP rating of a band (the inverse, used as an optimisation
target)."""
from __future__ import annotations
import pytest
from datatypes.epc.domain.epc import Epc
def test_sap_lower_bound_returns_the_band_floor() -> None:
# Act / Assert — the standard SAP10 band floors.
assert Epc.A.sap_lower_bound() == 92
assert Epc.B.sap_lower_bound() == 81
assert Epc.C.sap_lower_bound() == 69
assert Epc.D.sap_lower_bound() == 55
assert Epc.E.sap_lower_bound() == 39
assert Epc.F.sap_lower_bound() == 21
assert Epc.G.sap_lower_bound() == 1
@pytest.mark.parametrize("band", list(Epc))
def test_band_floor_round_trips_through_from_sap_score(band: Epc) -> None:
# Act / Assert — a band's floor scores back to that band.
assert Epc.from_sap_score(band.sap_lower_bound()) is band