mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-07-27 23:35:01 +00:00
scripts/run_pashub_modelling.py invokes the modelling_e2e handler in-process over a portfolio using STORED EPCs (refetch_epc=False — the correct source for PasHub cohorts, issue #1589), parametrised by env (portfolio/scenario/pids/ dry-run/batch). Handover documents the 3 re-extraction stragglers, the rebaseliner pass-through gotcha (effective_sap_score IS the pashub rating for unchanged lodged EPCs — never validate the calculator against it), and the 7 root-caused extractor bugs now tracked in #1590. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
122 lines
4.5 KiB
Python
122 lines
4.5 KiB
Python
"""Run the modelling_e2e handler locally over a portfolio's properties, using
|
|
the STORED EPCs (``refetch_epc=False``) — the correct source for PasHub
|
|
site-notes cohorts, where the stored site note is the newest assessment (the
|
|
gov-API refetch would bypass it; see issue #1589 / ADR-0001).
|
|
|
|
Invokes the real Lambda handler in-process (no Docker) with one SQS-shaped event
|
|
per batch of property ids. Reads ``backend/.env`` and maps ``DB_*`` -> the
|
|
``POSTGRES_*`` vars the handler's ``PostgresConfig.from_env`` expects.
|
|
|
|
Config via environment variables (all optional):
|
|
|
|
RUN_PORTFOLIO portfolio id (default 838)
|
|
RUN_SCENARIO scenario id (default 1297)
|
|
RUN_PIDS comma-separated property ids; unset -> every property in the
|
|
portfolio, ordered by id
|
|
RUN_DRY "1" dry-run / "0" write to the DB (default "1")
|
|
RUN_BATCH properties per handler invocation (default 50)
|
|
RUN_REFETCH_EPC / RUN_REPREDICT_EPC / RUN_REFETCH_SOLAR
|
|
"1"/"0" to override the refetch flags (default "0" — use
|
|
stored EPCs/solar; flip to re-fetch from the gov API/Google)
|
|
|
|
Examples:
|
|
python scripts/run_pashub_modelling.py # dry-run, whole portfolio
|
|
RUN_DRY=0 python scripts/run_pashub_modelling.py # real writes
|
|
RUN_SCENARIO=1301 RUN_DRY=0 python scripts/run_pashub_modelling.py
|
|
RUN_PIDS=754772,754780 RUN_DRY=0 python scripts/run_pashub_modelling.py
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
|
|
from sqlalchemy import text
|
|
|
|
from scripts.e2e_common import build_engine, load_env
|
|
|
|
|
|
def _configure_env() -> None:
|
|
"""Load ``backend/.env`` and mirror the FastAPI-layer ``DB_*`` creds onto the
|
|
``POSTGRES_*`` names the modelling handler reads."""
|
|
load_env()
|
|
for pg, db in (
|
|
("POSTGRES_HOST", "DB_HOST"),
|
|
("POSTGRES_PORT", "DB_PORT"),
|
|
("POSTGRES_USERNAME", "DB_USERNAME"),
|
|
("POSTGRES_PASSWORD", "DB_PASSWORD"),
|
|
("POSTGRES_DATABASE", "DB_NAME"),
|
|
):
|
|
if db in os.environ:
|
|
os.environ.setdefault(pg, os.environ[db])
|
|
|
|
|
|
def _flag(name: str, default: bool) -> bool:
|
|
raw = os.environ.get(name)
|
|
return default if raw is None else raw == "1"
|
|
|
|
|
|
def _portfolio_property_ids(portfolio_id: int) -> list[int]:
|
|
engine = build_engine()
|
|
with engine.begin() as conn:
|
|
conn.execute(text("SET statement_timeout = 120000"))
|
|
rows = conn.execute(
|
|
text("SELECT id FROM property WHERE portfolio_id = :p ORDER BY id"),
|
|
{"p": portfolio_id},
|
|
)
|
|
return [int(r[0]) for r in rows]
|
|
|
|
|
|
def main() -> None:
|
|
_configure_env()
|
|
# Imported after env is configured so the handler's module-level engine binds
|
|
# to the mirrored POSTGRES_* vars.
|
|
from applications.modelling_e2e.handler import handler
|
|
|
|
portfolio_id = int(os.environ.get("RUN_PORTFOLIO", "838"))
|
|
scenario_id = int(os.environ.get("RUN_SCENARIO", "1297"))
|
|
dry_run = _flag("RUN_DRY", True)
|
|
batch_size = int(os.environ.get("RUN_BATCH", "50"))
|
|
refetch_epc = _flag("RUN_REFETCH_EPC", False)
|
|
repredict_epc = _flag("RUN_REPREDICT_EPC", False)
|
|
refetch_solar = _flag("RUN_REFETCH_SOLAR", False)
|
|
|
|
pids_env = os.environ.get("RUN_PIDS")
|
|
property_ids: list[int] = (
|
|
[int(x) for x in pids_env.split(",") if x.strip()]
|
|
if pids_env
|
|
else _portfolio_property_ids(portfolio_id)
|
|
)
|
|
batches: list[list[int]] = [
|
|
property_ids[i : i + batch_size]
|
|
for i in range(0, len(property_ids), batch_size)
|
|
]
|
|
|
|
print(
|
|
f"portfolio={portfolio_id} scenario={scenario_id} dry_run={dry_run} "
|
|
f"refetch_epc={refetch_epc} repredict_epc={repredict_epc} "
|
|
f"refetch_solar={refetch_solar} properties={len(property_ids)} "
|
|
f"batches={len(batches)}"
|
|
)
|
|
|
|
for i, chunk in enumerate(batches):
|
|
body = {
|
|
"property_ids": chunk,
|
|
"portfolio_id": portfolio_id,
|
|
"scenario_id": scenario_id,
|
|
"refetch_epc": refetch_epc,
|
|
"repredict_epc": repredict_epc,
|
|
"refetch_solar": refetch_solar,
|
|
"dry_run": dry_run,
|
|
}
|
|
event = {"Records": [{"body": json.dumps(body)}]}
|
|
print(
|
|
f"\n=== batch {i + 1}/{len(batches)}: {len(chunk)} props "
|
|
f"({chunk[0]}..{chunk[-1]}) ==="
|
|
)
|
|
result: object = handler(event, None)
|
|
print(" ->", json.dumps(result, default=str)[:800])
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|