diff --git a/backend/engine/engine.py b/backend/engine/engine.py index 67c9dd4e..f7a374e0 100644 --- a/backend/engine/engine.py +++ b/backend/engine/engine.py @@ -652,7 +652,8 @@ async def model_engine(body: PlanTriggerRequest): epc_records["original_epc"]["estimated"] = False prepared_epc = EPCRecord( - epc_records=epc_records, run_mode="newdata", cleaning_data=cleaning_data, address_metadata=addr + epc_records=epc_records, run_mode="newdata", cleaning_data=cleaning_data, + # address_metadata=addr Switched off to remove injecting landlord inputs ) input_properties.append( @@ -724,6 +725,10 @@ async def model_engine(body: PlanTriggerRequest): # 1) EPC expired 2) Missing EPC 3) Different information from landlord vs EPC needs_rebaselining = p.epc_is_expired | p.epc_is_estimated | (len(p.epc_record.landlord_differences) > 0) + # Hack - skip + if "SAP05" in p.epc_record.walls_description: + continue + if needs_rebaselining: p.create_base_difference_epc_record(cleaned_lookup=cleaned) scoring_data = p.base_difference_record.df.copy() diff --git a/backend/utils/subtasks.py b/backend/utils/subtasks.py index e5668c53..6be3a742 100644 --- a/backend/utils/subtasks.py +++ b/backend/utils/subtasks.py @@ -113,12 +113,17 @@ def task_handler(): @wraps(func) def wrapper(event: dict[str, Any], context: Any, *args, **kwargs): - logger = setup_logger() - # Parse body: Records-style SQS or plain dict event - if "Records" in event: - raw_body = event["Records"][0].get("body", {}) + records = event.get("Records", [event]) # fallback for non-SQS + + results = [] + failures = [] + + for record in records: + # Parse body + raw_body = record.get("body", record) + if isinstance(raw_body, str): try: body = json.loads(raw_body) @@ -126,43 +131,55 @@ def task_handler(): body = {} else: body = raw_body or {} - else: - body = event - # Create fresh task + subtask - logger.info("Creating task for source: %s", task_source) - task_id, subtask_id = TasksInterface.create_task( - task_source=task_source, - inputs=body, - ) - logger.info("Created task_id=%s subtask_id=%s", task_id, subtask_id) + # Create task per message + logger.info("Creating task for source: %s", task_source) + task_id, subtask_id = TasksInterface.create_task( + task_source=task_source, + inputs=body, + ) - interface = SubTaskInterface() + logger.info("Created task_id=%s subtask_id=%s", task_id, subtask_id) - interface.update_subtask_status( - subtask_id=subtask_id, - status="in progress", - ) - - try: - result = func(body, context, *args, **kwargs) + interface = SubTaskInterface() interface.update_subtask_status( subtask_id=subtask_id, - status="complete", - outputs={"result": result} if result else None, + status="in progress", ) - logger.info("Task %s completed successfully", task_id) - return result - except Exception as e: - logger.exception("Task %s failed: %s", task_id, e) - interface.update_subtask_status( - subtask_id=subtask_id, - status="failed", - outputs={"error": str(e)}, - ) - raise + try: + result = func(body, context, *args, **kwargs) + + interface.update_subtask_status( + subtask_id=subtask_id, + status="complete", + outputs={"result": result} if result else None, + ) + + logger.info("Task %s completed successfully", task_id) + results.append(result) + + except Exception as e: + logger.exception("Task %s failed: %s", task_id, e) + + interface.update_subtask_status( + subtask_id=subtask_id, + status="failed", + outputs={"error": str(e)}, + ) + + if "Records" in event: + failures.append({"itemIdentifier": record["messageId"]}) + else: + # Handle non-SQS events + raise + + if "Records" in event: + return {"batchItemFailures": failures} + + # Handle non-SQS events + return results return wrapper diff --git a/etl/epc/Record.py b/etl/epc/Record.py index defe13f4..8dbb5ba5 100644 --- a/etl/epc/Record.py +++ b/etl/epc/Record.py @@ -580,7 +580,22 @@ class EPCRecord: if existing is not None and v is not None and abs(existing - v) > 1: # 1m tolerance self.landlord_differences[k] = v else: - if v != self._prepared_epc.get(k) and (not pd.isnull(v)) and (not pd.isnull(self._prepared_epc.get(k))): + + # Check if something has been cleaned. We want to avoid triggering re-baselining if we cleaned + # a value. In the address meta, it will possibly contain the original value, so we'd pick up a + # diference if the original value was something to be cleaned, we clean that value and then end up + # comparing the original value to the new clean one + cleaned_value = self._prepared_epc.get(k) + original_value = self.original_epc.get(k.replace("_", "-")) + + # We check if the value has been cleaned + if cleaned_value != original_value: + # The thing we want to compare against, is the original value + compare_to = original_value + else: + compare_to = cleaned_value + + if v != compare_to and (not pd.isnull(v)) and (not pd.isnull(self._prepared_epc.get(k))): self.landlord_differences[k] = v self._prepared_epc.update(self.landlord_differences) diff --git a/etl/find_my_epc/RetrieveFindMyEpc.py b/etl/find_my_epc/RetrieveFindMyEpc.py index 16b7d8b9..e6e4e5fd 100644 --- a/etl/find_my_epc/RetrieveFindMyEpc.py +++ b/etl/find_my_epc/RetrieveFindMyEpc.py @@ -778,6 +778,12 @@ class RetrieveFindMyEpc: 'Air or ground source heat pump': ["air_source_heat_pump"], "Add PV Battery": ["solar_pv_battery"], "Add PV diverter": ["solar_pv_diverter"], # Don't have a recommendation yet + "Draughtproof single-glazed windows": ["double_glazing"], + "Upgrade heating controls": ["roomstat_programmer_trvs", "time_temperature_zone_control"], + "Low energy lighting recommendation": ["low_energy_lighting"], + "Install cavity wall insulation": ["cavity_wall_insulation"], + "Install solar water heating": ["solar_water_heating"], + 'Install photovoltaics, 25% of roof area': ["solar_pv"], } survey = True diff --git a/infrastructure/terraform/lambda/hubspot_deal_etl/main.tf b/infrastructure/terraform/lambda/hubspot_deal_etl/main.tf index 516ec282..e8762337 100644 --- a/infrastructure/terraform/lambda/hubspot_deal_etl/main.tf +++ b/infrastructure/terraform/lambda/hubspot_deal_etl/main.tf @@ -11,7 +11,7 @@ data "terraform_remote_state" "pashub_to_ara" { backend = "s3" config = { bucket = "pashub-to-ara-terraform-state" - key = "ev:/${var.stage}/terraform.tfstate" + key = "env:/${var.stage}/terraform.tfstate" region = "eu-west-2" } } diff --git a/recommendations/HeatingRecommender.py b/recommendations/HeatingRecommender.py index 5a7a0e03..6aa5c93a 100644 --- a/recommendations/HeatingRecommender.py +++ b/recommendations/HeatingRecommender.py @@ -140,6 +140,9 @@ class HeatingRecommender: # All heat systems are in here so we identify whether two of these are true # MainHeatAttributes.HEAT_SYSTEMS + if "sap05" in self.property.main_heating["clean_description"].lower(): + return False + n_trues = 0 for heat_system in MainHeatAttributes.HEAT_SYSTEMS: if self.property.main_heating[f"has_{heat_system.replace(' ', '_')}"]: @@ -318,6 +321,9 @@ class HeatingRecommender: :param measures: A list of measures for the recommendations """ + if "sap05" in self.property.main_heating["clean_description"].lower(): + return + measures = MEASURE_MAP["heating"] if measures is None else measures # if we have a non-invasive ashp recommendation, we get the configuration directly from the property instance diff --git a/recommendations/LightingRecommendations.py b/recommendations/LightingRecommendations.py index 61b1f66a..91db19c9 100644 --- a/recommendations/LightingRecommendations.py +++ b/recommendations/LightingRecommendations.py @@ -100,6 +100,9 @@ class LightingRecommendations: :return: """ + if "sap05" in self.property.lighting["clean_description"].lower(): + return + if self.property.lighting["low_energy_proportion"] >= 1: return diff --git a/recommendations/SecondaryHeating.py b/recommendations/SecondaryHeating.py index c2250e1e..5b7f4ff9 100644 --- a/recommendations/SecondaryHeating.py +++ b/recommendations/SecondaryHeating.py @@ -18,7 +18,9 @@ class SecondaryHeating: def recommend(self, phase: int): # Reset self.recommendation = [] - if self.property.epc_record.secondheat_description in ["None", None]: + if self.property.epc_record.secondheat_description in ["None", None] or ( + "sap05" in self.property.epc_record.secondheat_description.lower() + ): # No secondary heating system, so no recommendation to remove it return diff --git a/recommendations/WallRecommendations.py b/recommendations/WallRecommendations.py index a696e878..6baa85aa 100644 --- a/recommendations/WallRecommendations.py +++ b/recommendations/WallRecommendations.py @@ -169,7 +169,7 @@ class WallRecommendations(Definitions): if ( (insulation_thickness in ["average", "above average"]) or self.property.walls["is_filled_cavity"] - or self.property.walls["clean_description"] is None + or self.property.walls["clean_description"] in [None, "Sap05:walls"] ) and ("cavity_extract_and_refill" not in measures ): return diff --git a/recommendations/WindowsRecommendations.py b/recommendations/WindowsRecommendations.py index ff75e72d..54445eb1 100644 --- a/recommendations/WindowsRecommendations.py +++ b/recommendations/WindowsRecommendations.py @@ -45,7 +45,8 @@ class WindowsRecommendations: measures = MEASURE_MAP["windows"] if measures is None else measures # If we have no windows recs, leave - if not any(x in measures for x in MEASURE_MAP["windows"]): + if not any(x in measures for x in MEASURE_MAP["windows"]) or "sap05" in self.property.windows[ + "clean_description"].lower(): return if self.property.windows["glazing_type"] in ["triple", "high performance"]: