fix: wire boiler_efficiency_band into the reader registry (consistency guard)

test_override_component_consistency caught two real gaps: the band was in the
override_component pgEnum but (a) missing from _ROW_TYPES — so the finaliser
couldn't load its classified vocab (a latent bug the injected-vocab finaliser
tests missed), and (b) not in _COMPONENT_OVERLAYS (correct — it's an attribute
consumed by the main_heating_system overlay, not a standalone one).

Add the band to _ROW_TYPES (reader), and introduce _ATTRIBUTE_COMPONENTS to name
the classified+stored-but-no-standalone-overlay case. The guard now asserts:
pgEnum == _ROW_TYPES; and _ROW_TYPES minus attribute components == _COMPONENT_OVERLAYS.
Full repositories suite (247) + finaliser green; pyright clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-07-30 16:40:46 +00:00
parent 50554ff57e
commit 3e4b8bd781
3 changed files with 45 additions and 12 deletions

View file

@ -37,6 +37,9 @@ from infrastructure.postgres.landlord_main_fuel_override_table import (
from infrastructure.postgres.landlord_main_heating_system_override_table import (
LandlordMainHeatingSystemOverrideRow,
)
from infrastructure.postgres.landlord_boiler_efficiency_band_override_table import (
LandlordBoilerEfficiencyBandOverrideRow,
)
from infrastructure.postgres.landlord_wall_type_override_table import (
LandlordWallTypeOverrideRow,
)
@ -58,6 +61,10 @@ _ROW_TYPES: dict[str, type] = {
"construction_age_band": LandlordConstructionAgeBandOverrideRow,
"water_heating": LandlordWaterHeatingOverrideRow,
"main_heating_system": LandlordMainHeatingSystemOverrideRow,
# An attribute of the main_heating_system override (consumed by its overlay,
# not a standalone one) — but still classified + cached + stored, so it needs
# a reader entry for the finaliser to load its vocab (ADR-0068).
"boiler_efficiency_band": LandlordBoilerEfficiencyBandOverrideRow,
}

View file

@ -78,6 +78,15 @@ _COMPONENT_OVERLAYS: dict[str, Callable[[str, int], Optional[EpcSimulation]]] =
}
# Components that are classified + stored but have **no standalone overlay** — they
# are *attributes* consumed by another component's overlay, so they are absent from
# `_COMPONENT_OVERLAYS` on purpose. The Boiler Efficiency Band rides the
# main_heating_system overlay (ADR-0068), read at its call site (see
# `_boiler_efficiency_band_for`). The override-component consistency guard treats
# these as the reader/pgEnum-minus-overlays difference.
_ATTRIBUTE_COMPONENTS: frozenset[str] = frozenset({"boiler_efficiency_band"})
# Components whose overlay must be applied LAST so an explicit value wins a
# default another overlay dragged. `apply_simulations` is last-wins and override
# rows arrive in arbitrary order, so a `main_fuel` override must be applied after

View file

@ -1,12 +1,20 @@
"""Every override component must be wired through the WHOLE chain.
The finaliser reader (`_ROW_TYPES`, component -> landlord table) and the overlay
registry (`_COMPONENT_OVERLAYS`, component -> overlay mapper) must cover exactly
the same set of components. If a component is classified + stored but has no
reader entry, the finaliser silently never writes its `property_overrides` rows;
if it has no overlay entry, the row never reaches the calculator. This guard
keeps the two registries in lock-step (it would have caught the missing
main_fuel / glazing / construction_age_band reader entries).
The finaliser reader (`_ROW_TYPES`, component -> landlord table), the overlay
registry (`_COMPONENT_OVERLAYS`, component -> overlay mapper), and the
`override_component` pgEnum mirror must stay in lock-step. If a component is
classified + stored but has no reader entry, the finaliser silently never writes
its `property_overrides` rows; if it has no overlay entry, the row never reaches
the calculator; if it is missing from the pgEnum, writing/reading it throws a
LookupError against Postgres (caught live on the Hyde portfolio-796 run). This
guard would have caught the missing main_fuel / glazing / construction_age_band
reader entries.
The one exception is an **attribute component** (`_ATTRIBUTE_COMPONENTS`): it is
classified + stored + read (so it *is* in `_ROW_TYPES` and the pgEnum) but is
consumed by another component's overlay rather than having its own, so it is
deliberately absent from `_COMPONENT_OVERLAYS` the Boiler Efficiency Band rides
the main_heating_system overlay (ADR-0068).
"""
from __future__ import annotations
@ -18,18 +26,27 @@ from infrastructure.landlord_overrides.landlord_override_reader_postgres_reposit
)
from infrastructure.postgres.property_override_table import override_component_sa_enum
from repositories.property.landlord_override_overlays import (
_ATTRIBUTE_COMPONENTS, # pyright: ignore[reportPrivateUsage]
_COMPONENT_OVERLAYS, # pyright: ignore[reportPrivateUsage]
)
def test_reader_and_overlay_registries_cover_the_same_components() -> None:
# Assert
assert set(_ROW_TYPES) == set(_COMPONENT_OVERLAYS)
# Every classified/stored component (`_ROW_TYPES`) has a standalone overlay,
# except the attribute components consumed by another overlay.
assert set(_ROW_TYPES) - _ATTRIBUTE_COMPONENTS == set(_COMPONENT_OVERLAYS)
def test_attribute_components_are_read_and_stored_but_have_no_overlay() -> None:
# An attribute component must still be classified/read (in `_ROW_TYPES`) and
# stored (in the pgEnum), but must NOT appear in `_COMPONENT_OVERLAYS`.
assert _ATTRIBUTE_COMPONENTS <= set(_ROW_TYPES)
assert _ATTRIBUTE_COMPONENTS.isdisjoint(_COMPONENT_OVERLAYS)
def test_override_component_pgenum_covers_every_component() -> None:
# The property_overrides.override_component pgEnum mirror must list every
# component, or writing/reading a new-component row through it throws a
# LookupError against Postgres (caught live on the Hyde portfolio-796 run).
# classified/stored component (overlay-backed + attribute), or writing/reading
# a new-component row through it throws a LookupError against Postgres.
pgenum_values = cast(list[str], getattr(override_component_sa_enum, "enums"))
assert set(pgenum_values) == set(_COMPONENT_OVERLAYS)
assert set(pgenum_values) == set(_ROW_TYPES)