Add Hyde property_overrides build script + creds-free smoke test 🟩

Two-pass org_ref-matched builder for property_overrides (classify via ChatGPT
into the landlord ledger, validate+apply user edits, write idempotently);
ephemeral-Postgres smoke proves the one-property chain without creds.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jun-te Kim 2026-06-19 19:02:42 +00:00
parent 85e43b3737
commit c1ab179d51
3 changed files with 26 additions and 14 deletions

5
.gitignore vendored
View file

@ -313,3 +313,8 @@ scripts/eon/epc_cache.pkl
scripts/hyde/.elmhurst-session/
scripts/hyde/elmhurst_downloads/
scripts/hyde/.elmhurst-creds.json
# Hyde property-overrides script artifacts
overrides_cache.json
overrides_unknowns.csv
overrides_edits.csv

View file

@ -41,7 +41,7 @@ from datetime import datetime, timezone
from enum import Enum
from typing import Any, Optional
import pandas as pd
import pandas as pd # pyright: ignore[reportMissingTypeStubs]
from sqlalchemy import Table, text
from sqlalchemy.dialects.postgresql import insert as pg_insert
from sqlmodel import SQLModel

View file

@ -25,6 +25,10 @@ from domain.epc.property_overrides.water_heating_type import WaterHeatingType
from infrastructure.landlord_overrides.landlord_overrides_postgres_repository import (
LandlordOverridesRepository,
)
from repositories.property.landlord_override_overlays import overlays_from
from repositories.property.property_overrides_postgres_reader import (
PropertyOverridesPostgresReader,
)
PORTFOLIO = 795
ORG_REF = "55180004001"
@ -36,7 +40,7 @@ SEED = {
"property_type": ("House: MidTerrace", PropertyType.HOUSE),
"built_form_type": ("House: MidTerrace", BuiltFormType.MID_TERRACE),
"wall_type": ("TimberFrame: AsBuilt", WallType.TIMBER_FRAME_AS_BUILT_NO_INSULATION_ASSUMED),
"roof_type": ("PitchedNormalLoftAccess: 300mm", RoofType.PITCHED_300_MM),
"roof_type": ("PitchedNormalLoftAccess: 300mm", RoofType.PITCHED_LOFT_300MM),
"construction_age_band": ("L: 2012-2022", ConstructionAgeBand.L_2012_2022),
"main_fuel": ("Gas: Mains Gas", MainFuelType.MAINS_GAS),
"glazing": ("100% Double glazing 2002 or later", GlazingType.DOUBLE_POST_2002),
@ -45,20 +49,21 @@ SEED = {
}
def test_one_property_end_to_end(db_engine: Engine, monkeypatch: Any, capsys: Any) -> None:
specs = b._specs_by_component()
def test_one_property_end_to_end(db_engine: Engine, monkeypatch: Any) -> None:
specs = b._specs_by_component() # pyright: ignore[reportPrivateUsage]
# minimal FE-owned `property` table + the one row we'll match by org_ref
with Session(db_engine) as s:
s.execute(text(
s.execute(text( # pyright: ignore[reportDeprecated]
"CREATE TABLE property (id bigint PRIMARY KEY, portfolio_id bigint, "
"landlord_property_id text)"))
s.execute(text("INSERT INTO property VALUES (1, :p, :ref)"),
s.execute(text("INSERT INTO property VALUES (1, :p, :ref)"), # pyright: ignore[reportDeprecated]
{"p": PORTFOLIO, "ref": ORG_REF})
# seed the classifier ledger (keyed on normalised description)
for comp, (raw, member) in SEED.items():
LandlordOverridesRepository(s, specs[comp].row_type).upsert_all(
PORTFOLIO, {b._norm(raw): member})
repo: LandlordOverridesRepository[Any] = LandlordOverridesRepository(
s, specs[comp].row_type)
repo.upsert_all(PORTFOLIO, {b._norm(raw): member}) # pyright: ignore[reportPrivateUsage]
s.commit()
# point the script at the ephemeral engine
@ -69,7 +74,7 @@ def test_one_property_end_to_end(db_engine: Engine, monkeypatch: Any, capsys: An
portfolio_id=PORTFOLIO, org_ref=ORG_REF, limit=None, apply=True))
with Session(db_engine) as s:
rows = list(s.execute(text(
rows = list(s.execute(text( # pyright: ignore[reportDeprecated]
"SELECT override_component, building_part, override_value "
"FROM property_overrides WHERE property_id = 1 ORDER BY override_component")))
got = {c: v for c, _, v in rows}
@ -81,8 +86,10 @@ def test_one_property_end_to_end(db_engine: Engine, monkeypatch: Any, capsys: An
assert got["water_heating"] == "From main system, mains gas"
assert len(rows) == 9 # all 9 components
# --- run the real verify() and confirm overlays are produced ---
b.verify(argparse.Namespace(portfolio_id=PORTFOLIO, org_ref=ORG_REF))
out = capsys.readouterr().out
print(out) # surfaced with -s so we can eyeball the one-property result
assert "EpcSimulation overlay" in out
# --- the overrides reach the SAP overlay surface ---
b.verify(argparse.Namespace(portfolio_id=PORTFOLIO, org_ref=ORG_REF)) # exercises verify()
overlays = overlays_from(
PropertyOverridesPostgresReader(lambda: Session(db_engine)).overrides_for(1))
assert len(overlays) == 9
assert any(o.heating is not None and o.heating.main_fuel_type == 26 for o in overlays)
assert any(o.glazing is not None and o.glazing.glazing_type == 2 for o in overlays)