mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-30 13:10:47 +00:00
Add a cell-by-cell inspector for landlord-override → effective-EPC mapping
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
86b5387a05
commit
5939520b0d
1 changed files with 118 additions and 0 deletions
118
scripts/inspect_overlay.py
Normal file
118
scripts/inspect_overlay.py
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
"""Step-through inspector: did a Property's Landlord Overrides map correctly?
|
||||
|
||||
Run cell-by-cell in VS Code (each `# %%` is a cell — ▶ Run Cell / Shift+Enter),
|
||||
or top-to-bottom with `PYTHONPATH=. python -m scripts.inspect_overlay`.
|
||||
|
||||
For one PROPERTY_ID it shows: the `property_overrides` rows, whether each mapped
|
||||
to a Simulation Overlay (or is a silent no-op), the lodged-vs-effective main
|
||||
wall codes the calculator scores, and the SAP delta the overlay produces. EPC is
|
||||
fetched LIVE from the gov API by UPRN (same as run_modelling_e2e); nothing is
|
||||
written. Edit PROPERTY_ID in the second cell and re-run.
|
||||
"""
|
||||
|
||||
# %% 1 — setup: env, DB engine, gov-EPC client
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
_REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(_REPO_ROOT))
|
||||
|
||||
for _raw in (_REPO_ROOT / "backend" / ".env").read_text(encoding="utf-8").splitlines():
|
||||
_line = _raw.strip()
|
||||
if _line and not _line.startswith("#") and "=" in _line:
|
||||
_k, _v = _line.split("=", 1)
|
||||
os.environ.setdefault(_k.strip(), _v.strip().strip('"').strip("'"))
|
||||
|
||||
from sqlalchemy import create_engine, text
|
||||
from sqlmodel import Session
|
||||
|
||||
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
|
||||
from domain.sap10_calculator.calculator import Sap10Calculator
|
||||
from infrastructure.epc_client.epc_client_service import EpcClientService
|
||||
from repositories.property.landlord_override_overlays import overlays_from
|
||||
from repositories.property.property_overrides_postgres_reader import (
|
||||
PropertyOverridesPostgresReader,
|
||||
)
|
||||
|
||||
_engine = create_engine(
|
||||
f"postgresql+psycopg2://{os.environ['DB_USERNAME']}:{os.environ['DB_PASSWORD']}"
|
||||
f"@{os.environ['DB_HOST']}:{os.environ['DB_PORT']}/{os.environ['DB_NAME']}"
|
||||
)
|
||||
_epc_client = EpcClientService(os.environ["OPEN_EPC_API_TOKEN"])
|
||||
_reader = PropertyOverridesPostgresReader(lambda: Session(_engine))
|
||||
|
||||
|
||||
def _main_wall(epc: EpcPropertyData) -> object:
|
||||
"""The MAIN building part — its wall_construction / wall_insulation_type are
|
||||
what the calculator turns into the wall U-value."""
|
||||
return next(
|
||||
p for p in epc.sap_building_parts if p.identifier is BuildingPartIdentifier.MAIN
|
||||
)
|
||||
|
||||
|
||||
# %% 2 — pick the property, resolve its UPRN
|
||||
PROPERTY_ID = 709672 # <-- edit me
|
||||
|
||||
with _engine.connect() as _conn:
|
||||
_row = _conn.execute(
|
||||
text("SELECT uprn, address FROM property WHERE id = :id"),
|
||||
{"id": PROPERTY_ID},
|
||||
).fetchone()
|
||||
assert _row is not None, f"property {PROPERTY_ID} not found"
|
||||
uprn, address = int(_row[0]), _row[1]
|
||||
print(f"property {PROPERTY_ID} · uprn {uprn} · {address}")
|
||||
|
||||
# %% 3 — fetch the lodged EPC live from the gov API
|
||||
epc = _epc_client.get_by_uprn(uprn)
|
||||
assert epc is not None, f"no EPC found for uprn {uprn}"
|
||||
print(f"lodged EPC: {epc.property_type=} {epc.built_form=}")
|
||||
print(f"lodged main wall: {_main_wall(epc)!r}")
|
||||
|
||||
# %% 4 — the property_overrides rows the finaliser wrote
|
||||
overrides = _reader.overrides_for(PROPERTY_ID)
|
||||
print(f"{len(overrides.rows)} override row(s):")
|
||||
for r in overrides.rows:
|
||||
print(f" part {r.building_part} · {r.override_component} = {r.override_value!r}")
|
||||
|
||||
# %% 5 — per-row mapping: did each override produce an overlay, or is it a no-op?
|
||||
for r in overrides.rows:
|
||||
if r.override_component == "wall_type":
|
||||
sim = wall_overlay_for(r.override_value, r.building_part)
|
||||
if sim is None:
|
||||
print(f" wall_type {r.override_value!r} -> NO-OP (material/state unmapped)")
|
||||
else:
|
||||
bp = next(iter(sim.building_parts.values()))
|
||||
print(
|
||||
f" wall_type {r.override_value!r} -> "
|
||||
f"wall_construction={bp.wall_construction} "
|
||||
f"wall_insulation_type={bp.wall_insulation_type}"
|
||||
)
|
||||
else:
|
||||
print(f" {r.override_component} {r.override_value!r} -> not overlaid (tracer is wall-only)")
|
||||
|
||||
# %% 6 — fold the overrides into the Effective EPC
|
||||
overlays = overlays_from(overrides)
|
||||
prop = Property(
|
||||
identity=PropertyIdentity(portfolio_id=0, postcode="", address="", uprn=uprn),
|
||||
epc=epc,
|
||||
landlord_overrides=overlays,
|
||||
)
|
||||
effective = prop.effective_epc
|
||||
print(f"{len(overlays)} overlay(s) folded · source_path={prop.source_path}")
|
||||
|
||||
# %% 7 — lodged vs effective: the codes the calculator scores
|
||||
print(f"lodged main wall: {_main_wall(epc)!r}")
|
||||
print(f"effective main wall: {_main_wall(effective)!r}")
|
||||
|
||||
# %% 8 — the SAP delta the overlay produces (the whole point)
|
||||
lodged_sap = Sap10Calculator().calculate(epc).sap_score
|
||||
effective_sap = Sap10Calculator().calculate(effective).sap_score
|
||||
print(f"SAP lodged={lodged_sap} effective={effective_sap} delta={effective_sap - lodged_sap:+d}")
|
||||
Loading…
Add table
Reference in a new issue