From 6dcb4d1d92ce12b9e157e8ad3ea494058391d5bf Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Thu, 19 Feb 2026 17:03:40 +0000 Subject: [PATCH 1/7] additional logging --- .../categorisation_trigger_request.py | 2 +- .../local_handler/invoke_local_lambda.py | 6 +++--- backend/categorisation/local_runner.py | 6 ++++-- backend/categorisation/processor.py | 20 ++++++++++--------- 4 files changed, 19 insertions(+), 15 deletions(-) diff --git a/backend/categorisation/categorisation_trigger_request.py b/backend/categorisation/categorisation_trigger_request.py index fbc2328b..44ac0ff1 100644 --- a/backend/categorisation/categorisation_trigger_request.py +++ b/backend/categorisation/categorisation_trigger_request.py @@ -9,4 +9,4 @@ class CategorisationTriggerRequest(BaseModel): scenario_priority_order: Optional[List[int]] = None -# {"portfolio_id": 556, "plans_to_consider": [1589319,1589320], "plan_priority_order": [1589319,1589320]} +# {"portfolio_id": 556, "scenarios_to_consider": [1039,1041], "scenario_priority_order": [1041,1039]} diff --git a/backend/categorisation/local_handler/invoke_local_lambda.py b/backend/categorisation/local_handler/invoke_local_lambda.py index 127d2575..1446a1e3 100644 --- a/backend/categorisation/local_handler/invoke_local_lambda.py +++ b/backend/categorisation/local_handler/invoke_local_lambda.py @@ -9,9 +9,9 @@ payload = { { "body": json.dumps( { - "portfolio_id": 556, - "scenarios_to_consider": [1039, 1041], - "scenarios_priority_order": [], + "portfolio_id": 569, + "scenarios_to_consider": [1069, 1060], + "scenario_priority_order": [1069, 1060], } ) } diff --git a/backend/categorisation/local_runner.py b/backend/categorisation/local_runner.py index 599cbbbb..f4718ffc 100644 --- a/backend/categorisation/local_runner.py +++ b/backend/categorisation/local_runner.py @@ -2,9 +2,11 @@ from backend.categorisation.processor import process_portfolio def main() -> None: - portfolio_id = 556 + portfolio_id = 569 + scenarios_to_consider = [1069, 1060] + scenario_priority_order = [1069, 1060] - process_portfolio(portfolio_id) + process_portfolio(portfolio_id, scenarios_to_consider, scenario_priority_order) if __name__ == "__main__": diff --git a/backend/categorisation/processor.py b/backend/categorisation/processor.py index 966ecbf5..5ed75d8f 100644 --- a/backend/categorisation/processor.py +++ b/backend/categorisation/processor.py @@ -37,8 +37,10 @@ def process_portfolio( ) plans: List[Plan] = _load_plans_for_portfolio(portfolio_id, scenarios_to_consider) + logger.info(f"Successfully loaded {len(plans)}") plans_by_property: Dict[int, List[Plan]] = _group_plans_by_property(plans) + logger.info("Successfully grouped plans by property") updated_plan_models: List[PlanModel] = [] updated_scenario_models: List[ScenarioModel] = [] @@ -51,6 +53,7 @@ def process_portfolio( cheapest_plan = choose_cheapest_relevant_plan( property_plans, scenario_priority_order ) + logger.info(f"Successfully found cheapest plan for Property {property_id}") updated_property_plan_models, updated_property_scenario_models = ( _update_plan_and_scenario_objects(property_plans, cheapest_plan) @@ -60,6 +63,7 @@ def process_portfolio( updated_scenario_models.extend(updated_property_scenario_models) if len(updated_plan_models) > 0: + logger.info(f"Updating {len(updated_plan_models)} Plans in database") bulk_update_plans(updated_plan_models, updated_scenario_models) logger.info("Successfully updated Plan default values in database") @@ -116,9 +120,10 @@ def _unset_defaults_for_scenarios_not_being_considered( if id not in scenarios_to_consider: scenarios_to_unset_default.append(id) - logger.info( - f"Unsetting {scenarios_to_unset_default} as default scenario(s) as not included in provided list of scenarios to consider" - ) + if len(scenarios_to_unset_default) > 0: + logger.info( + f"Unsetting {scenarios_to_unset_default} as default scenario(s) as not included in provided list of scenarios to consider" + ) if len(scenarios_to_unset_default) > 0: plans_to_unset_default: List[int] = get_plan_ids_by_scenario_ids( @@ -133,9 +138,9 @@ def _load_plans_for_portfolio( ) -> List[Plan]: if scenarios_to_consider: - logger.info(f"Getting {len(scenarios_to_consider)} plans") + logger.info(f"Getting plans for {len(scenarios_to_consider)} scenarios") plan_models: List[PlanModel] = get_plans_by_scenario_ids(scenarios_to_consider) - + logger.info(f"Got {len(plan_models)} plan models from database") else: logger.info( f"No list of Plans to consider provided. Getting all Plans for portfolio {portfolio_id}" @@ -159,11 +164,8 @@ def _load_plans_for_portfolio( plans.append( Plan.from_sqlalchemy(model, Scenario.from_sqlalchemy(scenario_model)) ) - logger.debug( - f"Successfully mapped plan {model.id} and scenario {scenario_model.id} to domain object" - ) - logger.debug(f"Got {len(plans)} plans from database") + logger.info(f"Got {len(plans)} Plans") return plans From a8d3ce599d3b5dd6ea4e092375506aa4355c7dc6 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Fri, 20 Feb 2026 10:11:31 +0000 Subject: [PATCH 2/7] =?UTF-8?q?handle=20all=20plans=20having=20zero=20cost?= =?UTF-8?q?=20=F0=9F=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tests/test_prioritised_plan_selected.py | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/backend/categorisation/tests/test_prioritised_plan_selected.py b/backend/categorisation/tests/test_prioritised_plan_selected.py index 74eb8c69..7544eb9c 100644 --- a/backend/categorisation/tests/test_prioritised_plan_selected.py +++ b/backend/categorisation/tests/test_prioritised_plan_selected.py @@ -92,3 +92,24 @@ def test_cheapest_plan_returned_if_not_in_priority_list( # assert assert actual_default_plan.id == expected_default_plan_id + + +def test_all_plans_zero_cost__highest_priority_returned( + created_at_datetime: datetime, +) -> None: + # arrange + epc_c_plan = make_plan(created_at_datetime, True, cost_of_works=0.0, name="EPC C") + minor_works_plan = make_plan( + created_at_datetime, False, cost_of_works=0.0, name="EPC C - Minor Works" + ) + scenario_priority_order: List[int] = [4, 3] + expected_default_plan_id = 2 + + # act + actual_default_plan = choose_cheapest_relevant_plan( + plans=[epc_c_plan, minor_works_plan], + scenario_priority_order=scenario_priority_order, + ) + + # assert + assert actual_default_plan.id == expected_default_plan_id From 0e279b15cec06d80bee07e17af73cfc6151b444b Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Fri, 20 Feb 2026 10:36:22 +0000 Subject: [PATCH 3/7] =?UTF-8?q?handle=20all=20plans=20having=20zero=20cost?= =?UTF-8?q?=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/categorisation/processor.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/backend/categorisation/processor.py b/backend/categorisation/processor.py index 5ed75d8f..590d064f 100644 --- a/backend/categorisation/processor.py +++ b/backend/categorisation/processor.py @@ -94,11 +94,15 @@ def choose_cheapest_relevant_plan( for plan in eligible_plans ) - cheapest_plans: List[Plan] = [ - plan - for plan in eligible_plans - if (plan.record.cost_of_works or float("inf")) == min_cost - ] + if all(p.record.cost_of_works == 0 for p in eligible_plans): + cheapest_plans = eligible_plans + + else: + cheapest_plans: List[Plan] = [ + plan + for plan in eligible_plans + if (plan.record.cost_of_works or float("inf")) == min_cost + ] for priority_scenario_id in scenario_priority_order: for plan in cheapest_plans: From cb55338f39eafdbfb03009bbfffc094c883cb5ad Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Fri, 20 Feb 2026 10:38:29 +0000 Subject: [PATCH 4/7] =?UTF-8?q?handle=20all=20plans=20having=20null=20cost?= =?UTF-8?q?=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tests/test_prioritised_plan_selected.py | 30 +++++++++++++++++-- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/backend/categorisation/tests/test_prioritised_plan_selected.py b/backend/categorisation/tests/test_prioritised_plan_selected.py index 7544eb9c..e2af6a63 100644 --- a/backend/categorisation/tests/test_prioritised_plan_selected.py +++ b/backend/categorisation/tests/test_prioritised_plan_selected.py @@ -1,5 +1,5 @@ from datetime import datetime -from typing import List +from typing import List, Optional import pytest from backend.app.domain.classes.plan import Plan @@ -16,7 +16,7 @@ def created_at_datetime() -> datetime: def make_plan_record( - created_at: datetime, default: bool, cost_of_works: float = 500.0 + created_at: datetime, default: bool, cost_of_works: Optional[float] = 500.0 ) -> PlanRecord: return PlanRecord( property_id=1, @@ -43,7 +43,10 @@ def make_scenario(name: str, created_at: datetime, is_default: bool) -> Scenario def make_plan( - created_at: datetime, default: bool, cost_of_works: float = 500.0, name: str = "" + created_at: datetime, + default: bool, + cost_of_works: Optional[float] = 500.0, + name: str = "", ) -> Plan: scenario = make_scenario(name, created_at, default) plan_id = 1 if default else 2 @@ -113,3 +116,24 @@ def test_all_plans_zero_cost__highest_priority_returned( # assert assert actual_default_plan.id == expected_default_plan_id + + +def test_all_plans_null_cost__highest_priority_returned( + created_at_datetime: datetime, +) -> None: + # arrange + epc_c_plan = make_plan(created_at_datetime, True, cost_of_works=None, name="EPC C") + minor_works_plan = make_plan( + created_at_datetime, False, cost_of_works=None, name="EPC C - Minor Works" + ) + scenario_priority_order: List[int] = [4, 3] + expected_default_plan_id = 2 + + # act + actual_default_plan = choose_cheapest_relevant_plan( + plans=[epc_c_plan, minor_works_plan], + scenario_priority_order=scenario_priority_order, + ) + + # assert + assert actual_default_plan.id == expected_default_plan_id From 47de308bf353207990a31657deed2303b33541d8 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Fri, 20 Feb 2026 10:39:16 +0000 Subject: [PATCH 5/7] reformatting --- backend/categorisation/processor.py | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/categorisation/processor.py b/backend/categorisation/processor.py index 590d064f..e5d69dcf 100644 --- a/backend/categorisation/processor.py +++ b/backend/categorisation/processor.py @@ -96,7 +96,6 @@ def choose_cheapest_relevant_plan( if all(p.record.cost_of_works == 0 for p in eligible_plans): cheapest_plans = eligible_plans - else: cheapest_plans: List[Plan] = [ plan From 481bd1197afcd729a437fc0ec5795f0479885c21 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Fri, 20 Feb 2026 10:40:33 +0000 Subject: [PATCH 6/7] put test portfolio back into invoke lambda script --- backend/categorisation/local_handler/invoke_local_lambda.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/categorisation/local_handler/invoke_local_lambda.py b/backend/categorisation/local_handler/invoke_local_lambda.py index 1446a1e3..5ed23c2d 100644 --- a/backend/categorisation/local_handler/invoke_local_lambda.py +++ b/backend/categorisation/local_handler/invoke_local_lambda.py @@ -9,9 +9,9 @@ payload = { { "body": json.dumps( { - "portfolio_id": 569, - "scenarios_to_consider": [1069, 1060], - "scenario_priority_order": [1069, 1060], + "portfolio_id": 556, + "scenarios_to_consider": [], + "scenario_priority_order": [], } ) } From b4583d3c8b8890879c1b68eb3b1c01c6717a2f30 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Fri, 20 Feb 2026 10:42:03 +0000 Subject: [PATCH 7/7] put test portfolio back in local runner --- backend/categorisation/local_runner.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/backend/categorisation/local_runner.py b/backend/categorisation/local_runner.py index f4718ffc..7de55bc0 100644 --- a/backend/categorisation/local_runner.py +++ b/backend/categorisation/local_runner.py @@ -1,12 +1,18 @@ +from typing import List + from backend.categorisation.processor import process_portfolio def main() -> None: - portfolio_id = 569 - scenarios_to_consider = [1069, 1060] - scenario_priority_order = [1069, 1060] + portfolio_id = 556 + scenarios_to_consider: List[int] = [] + scenario_priority_order: List[int] = [] - process_portfolio(portfolio_id, scenarios_to_consider, scenario_priority_order) + process_portfolio( + portfolio_id=portfolio_id, + scenarios_to_consider=scenarios_to_consider, + scenario_priority_order=scenario_priority_order, + ) if __name__ == "__main__":