diff --git a/backend/Property.py b/backend/Property.py index c4b1b969..649a9547 100644 --- a/backend/Property.py +++ b/backend/Property.py @@ -93,6 +93,7 @@ class Property: self.data = { k.replace("_", "-"): v for k, v in epc_record.get("prepared_epc").items() } + self.old_data = epc_record.get("old_data") self.property_dimensions = None # This is a list of measures that have already been installed in the property, typically found as a result @@ -1193,6 +1194,11 @@ class Property: if self.hotwater["heater_type"] is not None: self.hot_water_energy_source = heater_type_to_fuel[self.hotwater["heater_type"]] + + if self.hotwater["extra_features"] == "plus solar": + self.hot_water_energy_source = self.heating_energy_source + " + Solar Thermal" + return + else: fuel = system_type_modification[self.hotwater["system_type"]] diff --git a/backend/app/plan/router.py b/backend/app/plan/router.py index c26a5217..25e41e52 100644 --- a/backend/app/plan/router.py +++ b/backend/app/plan/router.py @@ -312,7 +312,17 @@ def get_on_site_data(body: PlanTriggerRequest): return patches, already_installed, non_invasive_recommendations -def extract_propert_on_site_recommendations(config, already_installed, non_invasive_recommendations, uprn): +def extract_property_on_site_recommendations(config, patches, already_installed, non_invasive_recommendations, uprn): + patch_has_uprn = "uprn" in patches[0] + if patch_has_uprn: + patch = next(( + x for x in patches if str(x["uprn"]) == str(config["uprn"]) + ), {}) + else: + patch = next(( + x for x in patches if (x["address"] == config["address"]) and (x["postcode"] == config["postcode"]) + ), {}) + property_already_installed = next(( x for x in already_installed if (x["address"] == config["address"]) and (x["postcode"] == config["postcode"]) @@ -348,7 +358,7 @@ def extract_propert_on_site_recommendations(config, already_installed, non_invas property_non_invasive_recommendations["recommendations"] = str(transformed) - return property_already_installed, property_non_invasive_recommendations + return patch, property_already_installed, property_non_invasive_recommendations router = APIRouter( @@ -423,9 +433,13 @@ async def trigger_plan(body: PlanTriggerRequest): epc_records, energy_assessment["energy_assessment_is_newer"] = create_epc_records( epc_searcher, energy_assessment ) - patch = next(( - x for x in patches if (x["address"] == config["address"]) and (x["postcode"] == config["postcode"]) - ), {}) + + patch, property_already_installed, property_non_invasive_recommendations = ( + extract_property_on_site_recommendations( + config, patches, already_installed, non_invasive_recommendations, uprn + ) + ) + epc_records = patch_epc(patch, epc_records) prepared_epc = EPCRecord( @@ -434,10 +448,6 @@ async def trigger_plan(body: PlanTriggerRequest): cleaning_data=cleaning_data ) - property_already_installed, property_non_invasive_recommendations = extract_propert_on_site_recommendations( - config, already_installed, non_invasive_recommendations, uprn - ) - input_properties.append( Property( id=property_id, @@ -509,6 +519,7 @@ async def trigger_plan(body: PlanTriggerRequest): # TODO: For simple properties, we should do a comparison/check between the solar API's roof area and the # basic estimate of roof area + # TODO: Debug this building_ids = [ { "building_id": p.building_id, @@ -797,12 +808,6 @@ async def trigger_plan(body: PlanTriggerRequest): ] recommendations[p.id] = final_recommendations - # With that complete, we now total the kwh and cost savings for the property - # total_kwh_savings = sum([rec["kwh_savings"] for rec in final_recommendations if rec["default"]]) - # total_energy_cost_savings = sum( - # [rec["energy_cost_savings"] for rec in final_recommendations if rec["default"]] - # ) - logger.info("Uploading recommendations to the database") # If we have any work to do, we create a new scenario engine_scenario = create_scenario( diff --git a/backend/app/plan/schemas.py b/backend/app/plan/schemas.py index bbcd5a57..63ca7834 100644 --- a/backend/app/plan/schemas.py +++ b/backend/app/plan/schemas.py @@ -35,6 +35,7 @@ class PlanTriggerRequest(BaseModel): "air_source_heat_pump", "internal_wall_insulation", "external_wall_insulation", + "secondary_heating" } _allowed_goals = {"Increasing EPC"} diff --git a/backend/ml_models/AnnualBillSavings.py b/backend/ml_models/AnnualBillSavings.py index f791599a..13c9e0a5 100644 --- a/backend/ml_models/AnnualBillSavings.py +++ b/backend/ml_models/AnnualBillSavings.py @@ -285,6 +285,10 @@ class AnnualBillSavings: # The solar thermal covers a % of the heating kwh, so we need to adjust the cost return (kwh / cop) * assumptions.SOLAR_CONSUMPTION_PROPORTION * cls.GAS_PRICE_CAP + if fuel == "Electricity + Solar Thermal": + # The solar thermal covers a % of the heating kwh, so we need to adjust the cost + return (kwh / cop) * assumptions.SOLAR_CONSUMPTION_PROPORTION * cls.ELECTRICITY_PRICE_CAP + if fuel == "Oil": price_data = cls.FUEL_DATA[cls.FUEL_DATA["Fuel"] == "Kerosene"].squeeze() cost_per_kwh = cls.cost_per_kwh( diff --git a/etl/customers/newhaven/newhaven_study.py b/etl/customers/newhaven/newhaven_study.py index 9cda3d29..e87705b8 100644 --- a/etl/customers/newhaven/newhaven_study.py +++ b/etl/customers/newhaven/newhaven_study.py @@ -11,7 +11,7 @@ EPC_DIRECTORY = Path(src_file_path).parent / "local_data" / "all-domestic-certif CUSTOMER_DATA_DIRECTORY = "/Users/khalimconn-kowlessar/Documents/hestia/Customers/Newhaven/Data" USER_ID = 8 -PORTFOLIO_ID = 89 +PORTFOLIO_ID = 90 def make_asset_list(): @@ -109,8 +109,8 @@ def make_asset_list(): asset_list = asset_list[asset_list["Class Description"] != "Caravan"] asset_list = asset_list[~pd.isnull(asset_list["current-energy-efficiency"])] - # Take a 10% sample, for properties that have an EPC, with a seed - asset_list = asset_list.sample(frac=0.25, random_state=42) + # Take a sample, for properties that have an EPC, with a seed + # asset_list = asset_list.sample(frac=0.5, random_state=42) AVG_FLOOR_HEIGHT = asset_list["floor-height"].median() @@ -195,6 +195,17 @@ def make_asset_list(): property_non_invasive_recs = [] if not property_ashp_potential.empty: + + if property_costs.empty: + similar_properties = ashp_potential[ + ashp_potential["Overall Suitability Rating"] & + (ashp_potential["Recommended Heat Pump Size [kW]"] == + property_ashp_potential["Recommended Heat Pump Size [kW]"].values[0]) + ].merge( + renewables_cost, how="inner", on="UPRN" + ) + property_costs = similar_properties[["Air Source Heat Pump - Total"]].mean().to_frame().T + property_non_invasive_recs.append( { "type": "air_source_heat_pump", @@ -256,6 +267,21 @@ def make_asset_list(): file_name=non_invasive_recommendations_filename ) + # We add a patch to one of the units because there's no data for the built form + # We would be able to handle this automatically in the future, when using OS API + patches = [{ + "uprn": "10033266220", + "built-form": "Semi-Detached", + }] + + # Store patches in s3 + patches_filename = f"{USER_ID}/{PORTFOLIO_ID}/patches.json" + save_csv_to_s3( + dataframe=pd.DataFrame(patches), + bucket_name="retrofit-plan-inputs-dev", + file_name=patches_filename + ) + # Create three scenarios body1 = { "portfolio_id": str(PORTFOLIO_ID), @@ -264,7 +290,7 @@ def make_asset_list(): "goal_value": "A", "trigger_file_path": filename, "already_installed_file_path": "", - "patches_file_path": "", + "patches_file_path": patches_filename, "non_invasive_recommendations_file_path": non_invasive_recommendations_filename, "scenario_name": "Demand Reduction - no solid wall, windows, LEDs", "multi_plan": True, @@ -283,7 +309,7 @@ def make_asset_list(): "goal_value": "A", "trigger_file_path": filename, "already_installed_file_path": "", - "patches_file_path": "", + "patches_file_path": patches_filename, "non_invasive_recommendations_file_path": non_invasive_recommendations_filename, "scenario_name": "Demand Reduction - no solid wall, floors or heating", "multi_plan": True, @@ -294,6 +320,25 @@ def make_asset_list(): } print(body2) + # 2.5 - full fabric, no decant + body2_5 = { + "portfolio_id": str(PORTFOLIO_ID), + "housing_type": "Private", + "goal": "Increasing EPC", + "goal_value": "A", + "trigger_file_path": filename, + "already_installed_file_path": "", + "patches_file_path": patches_filename, + "non_invasive_recommendations_file_path": non_invasive_recommendations_filename, + "scenario_name": "Demand Reduction - no solid wall, floors or heating", + "multi_plan": True, + "exclusions": [ + "internal_wall_insulation", "floor_insulation", "heating", "solar_pv", + ], + "budget": None, + } + print(body2_5) + # Scenario B body3 = { "portfolio_id": str(PORTFOLIO_ID), @@ -302,7 +347,7 @@ def make_asset_list(): "goal_value": "A", "trigger_file_path": filename, "already_installed_file_path": "", - "patches_file_path": "", + "patches_file_path": patches_filename, "non_invasive_recommendations_file_path": non_invasive_recommendations_filename, "scenario_name": "Demand Reduction, Heating Systems, Solar PV - no solid wall or floors", "multi_plan": True, @@ -319,7 +364,7 @@ def make_asset_list(): "goal_value": "A", "trigger_file_path": filename, "already_installed_file_path": "", - "patches_file_path": "", + "patches_file_path": patches_filename, "non_invasive_recommendations_file_path": non_invasive_recommendations_filename, "scenario_name": "Whole House", "multi_plan": True, diff --git a/recommendations/Recommendations.py b/recommendations/Recommendations.py index ef478426..33c8bee4 100644 --- a/recommendations/Recommendations.py +++ b/recommendations/Recommendations.py @@ -43,6 +43,9 @@ DESCRIPTIONS_TO_FUEL_TYPES = { "Electric storage heaters, Electric storage heaters": {"fuel": "Electricity", "cop": 1}, "Boiler and radiators, electric": {"fuel": "Electricity", "cop": 0.9}, "Gas boiler/circulator, no cylinder thermostat": {"fuel": "Natural Gas", "cop": 0.9}, + "Boiler and radiators, dual fuel (mineral and wood)": {"fuel": "Wood Logs", "cop": 0.9}, + "Electric immersion, standard tariff, plus solar": {"fuel": "Electricity + Solar Thermal", "cop": 1}, + "From main system, flue gas heat recovery": {"fuel": "Natural Gas", "cop": 0.9}, } STARTING_DUMMY_ID_VALUE = -9999