mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-07-12 13:29:04 +00:00
various fixes
This commit is contained in:
parent
409fb8485c
commit
dc830fea63
4 changed files with 48 additions and 12 deletions
|
|
@ -39,7 +39,9 @@ from infrastructure.solar.google_solar_api_client import (
|
||||||
BuildingInsightsNotFoundError,
|
BuildingInsightsNotFoundError,
|
||||||
GoogleSolarApiClient,
|
GoogleSolarApiClient,
|
||||||
)
|
)
|
||||||
from applications.modelling_e2e.modelling_e2e_trigger_body import ModellingE2ETriggerBody
|
from applications.modelling_e2e.modelling_e2e_trigger_body import (
|
||||||
|
ModellingE2ETriggerBody,
|
||||||
|
)
|
||||||
from repositories.geospatial.geospatial_s3_repository import (
|
from repositories.geospatial.geospatial_s3_repository import (
|
||||||
GeospatialS3Repository,
|
GeospatialS3Repository,
|
||||||
ParquetReader,
|
ParquetReader,
|
||||||
|
|
@ -57,9 +59,12 @@ from repositories.scenario.scenario_postgres_repository import (
|
||||||
ScenarioPostgresRepository,
|
ScenarioPostgresRepository,
|
||||||
)
|
)
|
||||||
from utilities.aws_lambda.task_handler import task_handler
|
from utilities.aws_lambda.task_handler import task_handler
|
||||||
|
from utilities.logger import setup_logger
|
||||||
|
|
||||||
_engine: Optional[Engine] = None
|
_engine: Optional[Engine] = None
|
||||||
|
|
||||||
|
logger = setup_logger()
|
||||||
|
|
||||||
|
|
||||||
def _get_engine() -> Engine:
|
def _get_engine() -> Engine:
|
||||||
global _engine
|
global _engine
|
||||||
|
|
@ -72,7 +77,9 @@ def _s3_parquet_reader() -> ParquetReader:
|
||||||
bucket = os.environ["DATA_BUCKET"]
|
bucket = os.environ["DATA_BUCKET"]
|
||||||
|
|
||||||
def read(key: str) -> pd.DataFrame:
|
def read(key: str) -> pd.DataFrame:
|
||||||
s3: Any = cast(Any, boto3.client("s3")) # pyright: ignore[reportUnknownMemberType]
|
s3: Any = cast(
|
||||||
|
Any, boto3.client("s3")
|
||||||
|
) # pyright: ignore[reportUnknownMemberType]
|
||||||
raw = cast(bytes, s3.get_object(Bucket=bucket, Key=key)["Body"].read())
|
raw = cast(bytes, s3.get_object(Bucket=bucket, Key=key)["Body"].read())
|
||||||
return pd.read_parquet(io.BytesIO(raw)) # type: ignore[return-value]
|
return pd.read_parquet(io.BytesIO(raw)) # type: ignore[return-value]
|
||||||
|
|
||||||
|
|
@ -110,6 +117,11 @@ def handler(body: dict[str, Any], context: Any) -> None:
|
||||||
no_solar = trigger.no_solar
|
no_solar = trigger.no_solar
|
||||||
dry_run = trigger.dry_run
|
dry_run = trigger.dry_run
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
f"start property={property_id} portfolio={portfolio_id} "
|
||||||
|
f"scenario={scenario_id} no_solar={no_solar} dry_run={dry_run}"
|
||||||
|
)
|
||||||
|
|
||||||
engine = _get_engine()
|
engine = _get_engine()
|
||||||
epc_client = EpcClientService(os.environ["OPEN_EPC_API_TOKEN"])
|
epc_client = EpcClientService(os.environ["OPEN_EPC_API_TOKEN"])
|
||||||
geospatial = GeospatialS3Repository(_s3_parquet_reader())
|
geospatial = GeospatialS3Repository(_s3_parquet_reader())
|
||||||
|
|
@ -121,10 +133,12 @@ def handler(body: dict[str, Any], context: Any) -> None:
|
||||||
{"pid": property_id},
|
{"pid": property_id},
|
||||||
).one()
|
).one()
|
||||||
uprn = int(row[0])
|
uprn = int(row[0])
|
||||||
|
logger.info(f"resolved uprn={uprn}")
|
||||||
|
|
||||||
epc: Optional[EpcPropertyData] = epc_client.get_by_uprn(uprn)
|
epc: Optional[EpcPropertyData] = epc_client.get_by_uprn(uprn)
|
||||||
if epc is None:
|
if epc is None:
|
||||||
raise ValueError(f"no EPC found for UPRN {uprn} (property {property_id})")
|
raise ValueError(f"no EPC found for UPRN {uprn} (property {property_id})")
|
||||||
|
logger.info(f"fetched EPC (energy_rating_current={epc.energy_rating_current})")
|
||||||
|
|
||||||
overrides_reader = PropertyOverridesPostgresReader(lambda: Session(engine))
|
overrides_reader = PropertyOverridesPostgresReader(lambda: Session(engine))
|
||||||
overlaid = Property(
|
overlaid = Property(
|
||||||
|
|
@ -140,10 +154,18 @@ def handler(body: dict[str, Any], context: Any) -> None:
|
||||||
restrictions = (
|
restrictions = (
|
||||||
spatial.restrictions if spatial is not None else PlanningRestrictions()
|
spatial.restrictions if spatial is not None else PlanningRestrictions()
|
||||||
)
|
)
|
||||||
solar_insights = None if no_solar else _solar_insights_for(solar_client, spatial)
|
logger.info(f"spatial={'found' if spatial is not None else 'not found'}")
|
||||||
|
|
||||||
|
if no_solar:
|
||||||
|
solar_insights = None
|
||||||
|
logger.info("solar skipped (no_solar=True)")
|
||||||
|
else:
|
||||||
|
solar_insights = _solar_insights_for(solar_client, spatial)
|
||||||
|
logger.info(f"solar={'found' if solar_insights is not None else 'not found'}")
|
||||||
|
|
||||||
with Session(engine) as session:
|
with Session(engine) as session:
|
||||||
scenario = ScenarioPostgresRepository(session).get_many([scenario_id])[0]
|
scenario = ScenarioPostgresRepository(session).get_many([scenario_id])[0]
|
||||||
|
logger.info(f"loaded scenario goal={scenario.goal!r} goal_value={scenario.goal_value!r}")
|
||||||
products = ProductPostgresRepository(session)
|
products = ProductPostgresRepository(session)
|
||||||
|
|
||||||
# secondary_heating_removal is absent from the live material.type enum;
|
# secondary_heating_removal is absent from the live material.type enum;
|
||||||
|
|
@ -152,6 +174,7 @@ def handler(body: dict[str, Any], context: Any) -> None:
|
||||||
MeasureType.SECONDARY_HEATING_REMOVAL
|
MeasureType.SECONDARY_HEATING_REMOVAL
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.info("running modelling pipeline")
|
||||||
plan = run_modelling(
|
plan = run_modelling(
|
||||||
effective_epc,
|
effective_epc,
|
||||||
planning_restrictions=restrictions,
|
planning_restrictions=restrictions,
|
||||||
|
|
@ -161,14 +184,15 @@ def handler(body: dict[str, Any], context: Any) -> None:
|
||||||
scenario=scenario,
|
scenario=scenario,
|
||||||
print_table=False,
|
print_table=False,
|
||||||
)
|
)
|
||||||
|
logger.info(
|
||||||
|
f"modelling complete: SAP {plan.baseline.sap_continuous:.1f}→"
|
||||||
|
f"{plan.post_sap_continuous:.1f} measures={len(plan.measures)} "
|
||||||
|
f"cost=£{plan.cost_of_works:,.0f}"
|
||||||
|
)
|
||||||
|
|
||||||
if dry_run:
|
if dry_run:
|
||||||
measure_types = ", ".join(m.measure_type for m in plan.measures) or "none"
|
measure_types = ", ".join(m.measure_type for m in plan.measures) or "none"
|
||||||
print(
|
logger.info(f"[dry_run] measures=[{measure_types}] — skipping DB write")
|
||||||
f"[dry_run] property={property_id} scenario={scenario_id} "
|
|
||||||
f"SAP {plan.baseline.sap_continuous:.1f}→{plan.post_sap_continuous:.1f} "
|
|
||||||
f"measures=[{measure_types}] cost=£{plan.cost_of_works:,.0f}"
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
PlanPostgresRepository(session).save(
|
PlanPostgresRepository(session).save(
|
||||||
plan,
|
plan,
|
||||||
|
|
@ -181,3 +205,4 @@ def handler(body: dict[str, Any], context: Any) -> None:
|
||||||
property_id, has_recommendations=bool(plan.measures)
|
property_id, has_recommendations=bool(plan.measures)
|
||||||
)
|
)
|
||||||
session.commit()
|
session.commit()
|
||||||
|
logger.info("plan saved")
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,9 @@ payload = {
|
||||||
{
|
{
|
||||||
"body": json.dumps(
|
"body": json.dumps(
|
||||||
{
|
{
|
||||||
"property_id": 0,
|
"property_id": 709634,
|
||||||
"portfolio_id": 0,
|
"portfolio_id": 785,
|
||||||
"scenario_id": 0,
|
"scenario_id": 1266,
|
||||||
"no_solar": False,
|
"no_solar": False,
|
||||||
"dry_run": True,
|
"dry_run": True,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
awslambdaric
|
awslambdaric
|
||||||
boto3
|
boto3
|
||||||
|
numpy==2.1.2
|
||||||
pandas==2.2.2
|
pandas==2.2.2
|
||||||
pyarrow
|
pyarrow==17.0.0
|
||||||
|
httpx
|
||||||
|
requests
|
||||||
pydantic
|
pydantic
|
||||||
sqlalchemy==2.0.36
|
sqlalchemy==2.0.36
|
||||||
sqlmodel
|
sqlmodel
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ TaskOrchestrator.create_task_with_subtask(...) + run_subtask(...).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
import logging
|
||||||
from contextlib import AbstractContextManager
|
from contextlib import AbstractContextManager
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from typing import Any, Callable, Optional, cast
|
from typing import Any, Callable, Optional, cast
|
||||||
|
|
@ -13,6 +14,8 @@ from utilities.aws_lambda.default_orchestrator import default_orchestrator
|
||||||
from domain.tasks.tasks import Source
|
from domain.tasks.tasks import Source
|
||||||
from orchestration.task_orchestrator import TaskOrchestrator
|
from orchestration.task_orchestrator import TaskOrchestrator
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
OrchestratorCM = Callable[[], AbstractContextManager[TaskOrchestrator]]
|
OrchestratorCM = Callable[[], AbstractContextManager[TaskOrchestrator]]
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -63,6 +66,11 @@ def task_handler(
|
||||||
)
|
)
|
||||||
results.append(result)
|
results.append(result)
|
||||||
except Exception:
|
except Exception:
|
||||||
|
logger.exception(
|
||||||
|
"subtask failed (task_source=%s source_id=%s)",
|
||||||
|
task_source,
|
||||||
|
source_id,
|
||||||
|
)
|
||||||
if "Records" in event:
|
if "Records" in event:
|
||||||
message_id = record.get("messageId", "")
|
message_id = record.get("messageId", "")
|
||||||
failures.append({"itemIdentifier": message_id})
|
failures.append({"itemIdentifier": message_id})
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue