mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-07-12 13:29:04 +00:00
126 lines
4.8 KiB
Python
126 lines
4.8 KiB
Python
"""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}"
|
|
)
|
|
|
|
# %%
|