diff --git a/applications/magic_plan/handler.py b/applications/magic_plan/handler.py index 4dc88c53..13bb59c4 100644 --- a/applications/magic_plan/handler.py +++ b/applications/magic_plan/handler.py @@ -1,5 +1,5 @@ import os -from typing import Any +from typing import Any, Optional import boto3 @@ -16,7 +16,7 @@ logger = setup_logger() @subtask_handler() -def handler(body: dict[str, Any], context: Any) -> str: +def handler(body: dict[str, Any], context: Any) -> Optional[str]: config = MagicPlanConfig.from_env(os.environ) payload = MagicPlanTriggerRequest.model_validate(body) client = MagicPlanClient( @@ -29,11 +29,14 @@ def handler(body: dict[str, Any], context: Any) -> str: s3_client = S3Client( boto_s3_client=boto_s3, bucket="retrofit-energy-assessments-dev" ) - # TODO: read s3_bucket from env var so staging/prod use the correct bucket - plan: Plan = MagicPlanOrchestrator(client, s3_client).run(payload) - logger.info("Saved MagicPlan plan uid=%s", plan.uid) - return plan.uid + + plan: Optional[Plan] = MagicPlanOrchestrator(client, s3_client).run(payload) + if plan: + logger.info("Saved MagicPlan plan uid=%s", plan.uid) + return plan.uid + + return None if __name__ == "__main__": diff --git a/applications/magic_plan/local_handler/docker-compose.yml b/applications/magic_plan/local_handler/docker-compose.yml index 5a42d259..44b448bb 100644 --- a/applications/magic_plan/local_handler/docker-compose.yml +++ b/applications/magic_plan/local_handler/docker-compose.yml @@ -4,7 +4,7 @@ services: ecmk-fetcher-lambda: build: context: ../../../ - dockerfile: backend/magic_plan/handler/Dockerfile + dockerfile: applications/magic_plan/handler/Dockerfile ports: - "9000:8080" env_file: diff --git a/applications/magic_plan/local_handler/invoke_local_lambda.py b/applications/magic_plan/local_handler/invoke_local_lambda.py index 146951fe..9221b6bf 100644 --- a/applications/magic_plan/local_handler/invoke_local_lambda.py +++ b/applications/magic_plan/local_handler/invoke_local_lambda.py @@ -12,11 +12,18 @@ payload = { { "messageId": "test-message-id", "body": json.dumps( + { + # "task_id": "00000000-0000-0000-0000-000000000001", + # "sub_task_id": "00000000-0000-0000-0000-000000000002", + "address": "63 Dunkery Road, Wythenshawe, M22 0WR | EPC", + "hubspot_deal_id": "501851906250", + } # { - # "address": "2 Laburnum Way, Rombley, BR2 8BZ | Retrofit Assessment", - # "hubspot_deal_id": "500262906061", + # "task_id": "00000000-0000-0000-0000-000000000001", + # "sub_task_id": "00000000-0000-0000-0000-000000000002", + # "address": "33 Wallaby Way, Sydney", + # "hubspot_deal_id": "123456789", # } - {"address": "33 Wallaby Way, Sydney", "hubspot_deal_id": "123456789"} ), } ] diff --git a/applications/magic_plan/local_handler/invoke_local_orchestrator.py b/applications/magic_plan/local_handler/invoke_local_orchestrator.py new file mode 100644 index 00000000..ed07ffde --- /dev/null +++ b/applications/magic_plan/local_handler/invoke_local_orchestrator.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +"""Run MagicPlanOrchestrator directly, bypassing @subtask_handler. + +Loads credentials from the repo-root .env file so no DB task/subtask rows +are needed. +""" + +import os +import sys +from pathlib import Path + +import boto3 +from dotenv import load_dotenv + +from utilities.logger import setup_logger + +setup_logger() + +REPO_ROOT = Path(__file__).resolve().parents[3] +load_dotenv(REPO_ROOT / ".env") + +sys.path.insert(0, str(REPO_ROOT)) + +from infrastructure.magic_plan.config import MagicPlanConfig +from infrastructure.magic_plan.magic_plan_client import MagicPlanClient +from infrastructure.s3.s3_client import S3Client +from orchestration.magic_plan_orchestrator import MagicPlanOrchestrator +from applications.magic_plan.magic_plan_trigger_request import MagicPlanTriggerRequest + +ADDRESS = "63 Dunkery Road, Wythenshawe, M22 0WR | EPC" +HUBSPOT_DEAL_ID = "501851906250" +# ADDRESS = "33 Wallaby Way, Sydney" +# HUBSPOT_DEAL_ID = "123456789" + +config = MagicPlanConfig.from_env(os.environ) +client = MagicPlanClient(customer_id=config.customer_id, api_key=config.api_key) + +boto3_client = boto3.client # type: ignore[attr-defined] +s3_client = S3Client( + boto_s3_client=boto3_client("s3"), + bucket="retrofit-energy-assessments-dev", +) + +request = MagicPlanTriggerRequest(address=ADDRESS, hubspot_deal_id=HUBSPOT_DEAL_ID) + +print(f"Running MagicPlanOrchestrator for: {ADDRESS!r}") +plan = MagicPlanOrchestrator(client, s3_client).run(request) +print(f"Done") diff --git a/domain/magicplan/mapper.py b/domain/magicplan/mapper.py index 45f24ba7..604e330a 100644 --- a/domain/magicplan/mapper.py +++ b/domain/magicplan/mapper.py @@ -84,25 +84,25 @@ def _map_window_ventilation( return None by_label = {f.label: f for f in fields} - def _str(label: str) -> Optional[str]: + def _try_get_str_value(label: str) -> Optional[str]: f = by_label.get(label) if f is None or not f.value.has_value: return None v = f.value.value return v[0] if isinstance(v, list) else v - def _int(label: str) -> Optional[int]: - raw = _str(label) + def _try_get_int_value(label: str) -> Optional[int]: + raw = _try_get_str_value(label) return int(raw) if raw is not None else None return WindowVentilation( - opening_type=_str("Opening Type"), - num_openings=_int("Number of Openings (In Same Window)"), - pct_openable=_int("% of Window Openable"), - trickle_vent_area_mm2=_int( + opening_type=_try_get_str_value("Opening Type"), + num_openings=_try_get_int_value("Number of Openings (In Same Window)"), + pct_openable=_try_get_int_value("% of Window Openable"), + trickle_vent_area_mm2=_try_get_int_value( "Trickle Vent Effective Area (mm2) (No Code Then Width x Height)" ), - num_trickle_vents=_int("No. of Trickle Vents"), + num_trickle_vents=_try_get_int_value("No. of Trickle Vents"), ) diff --git a/orchestration/magic_plan_orchestrator.py b/orchestration/magic_plan_orchestrator.py index a586c3ba..4d1f5c38 100644 --- a/orchestration/magic_plan_orchestrator.py +++ b/orchestration/magic_plan_orchestrator.py @@ -35,7 +35,7 @@ class MagicPlanOrchestrator: # self._s3_bucket = s3_bucket self._s3_client = s3_client - def run(self, request: MagicPlanTriggerRequest) -> Plan: + def run(self, request: MagicPlanTriggerRequest) -> Optional[Plan]: address = request.address uprn = request.uprn @@ -43,23 +43,30 @@ class MagicPlanOrchestrator: logger.info("MagicPlanService.run uprn=%s", uprn) plans: list[PlanSummary] = self._api_client.get_plans() + logger.info("Got list of Plans from MagicPlan") matched: Optional[PlanSummary] = find_matching_plan(plans, address) if matched is None: - raise ValueError(f"No MagicPlan found for address: {address!r}") + logger.warning(f"No MagicPlan found for address: {address!r}") + return None raw_bytes: bytes = self._api_client.get_plan_raw(matched.id) + logger.info(f"Got data for Plan {matched.id} from MagicPlan") + magic_plan: MagicPlanPlan = MagicPlanPlan.model_validate( json.loads(raw_bytes)["data"] ) plan: Plan = map_plan(magic_plan) + logger.info("Successfully mapped to Plan object") + uploaded_file: UploadedFile = self._upload_raw_plan_json( plan_id=matched.id, raw_bytes=raw_bytes, uprn=uprn, hubspot_deal_id=request.hubspot_deal_id, ) + logger.info("Successfully save Plan json") engine = make_engine(PostgresConfig.from_env(os.environ)) session = make_session(engine) @@ -69,6 +76,7 @@ class MagicPlanOrchestrator: MagicPlanPostgresRepository(session).save( plan, cast(int, uploaded_file.id) ) # TODO: refactor to use postgres Unit of Work + logger.info("Successfully saved Plan to database") return plan