diff --git a/.idea/Model.iml b/.idea/Model.iml
index 09f2e496..c6561970 100644
--- a/.idea/Model.iml
+++ b/.idea/Model.iml
@@ -7,7 +7,7 @@
-
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index fb10c6b0..50cad4ca 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -3,7 +3,7 @@
-
+
diff --git a/backend/Funding.py b/backend/Funding.py
index 4a198bf9..4609e3d4 100644
--- a/backend/Funding.py
+++ b/backend/Funding.py
@@ -1041,7 +1041,7 @@ class Funding:
pre_heating_system=pre_heating_system
)
- innovation_uplift = pps * measure["uplift"]
+ innovation_uplift = pps * measure["innovation_rate"]
if self.tenure == "Private":
# We return ECO4 rates
diff --git a/backend/Property.py b/backend/Property.py
index 66ca84e3..d6f43b8a 100644
--- a/backend/Property.py
+++ b/backend/Property.py
@@ -1238,15 +1238,11 @@ class Property:
):
return True
- suitable_house = self.data["property-type"] == "House" and self.data["built-form"] in [
- "Detached", "Semi-Detached", "End-Terrace",
- ]
+ suitable_property_type = (
+ self.data["property-type"] in ["House", "Bungalow"] and
+ self.data["built-form"] not in ["Enclosed Mid-Terrace", "Enclosed End-Terrace"]
+ )
- suitable_bungalow = self.data["property-type"] == "Bungalow" and self.data["built-form"] in [
- "Detached", "Semi-Detached"
- ]
-
- suitable_property_type = suitable_house or suitable_bungalow
has_air_source_heat_pump = self.main_heating["has_air_source_heat_pump"]
return suitable_property_type and not has_air_source_heat_pump
diff --git a/backend/apis/GoogleSolarApi.py b/backend/apis/GoogleSolarApi.py
index 9d136b22..9073b307 100644
--- a/backend/apis/GoogleSolarApi.py
+++ b/backend/apis/GoogleSolarApi.py
@@ -548,22 +548,19 @@ class GoogleSolarApi:
"""
is_flat = property_instance.roof["is_flat"]
- filtered_segments = []
- for i, seg in enumerate(self.roof_segments):
- # DO NOT overwrite seg["segmentIndex"]
- keep = True
- if not is_flat:
- if self.NORTH_FACING_AZIMUTH_RANGE[0] <= seg['azimuthDegrees'] <= self.NORTH_FACING_AZIMUTH_RANGE[1]:
- keep = False
+ kept = []
+ allowed = set()
+ for i, seg in enumerate(self.roof_segments): # i is the API segmentIndex
+ if not is_flat and (
+ self.NORTH_FACING_AZIMUTH_RANGE[0] <= seg['azimuthDegrees'] <= self.NORTH_FACING_AZIMUTH_RANGE[1]):
+ continue
+ s = dict(seg)
+ s["localIndex"] = len(kept) # for charts/UI only
+ kept.append(s)
+ allowed.add(i) # this i IS the API segmentIndex
- if keep:
- seg = dict(**seg) # shallow copy
- seg["localIndex"] = i # optional local index as a reference from this loop
- filtered_segments.append(seg)
-
- self.roof_segments = filtered_segments
-
- self.allowed_segment_indices = {s["segmentIndex"] for s in self.roof_segments}
+ self.roof_segments = kept
+ self.allowed_segment_indices = allowed
@staticmethod
def haversine(lat1, lon1, lat2, lon2):
diff --git a/backend/engine/engine.py b/backend/engine/engine.py
index 026b0405..d97d96ab 100644
--- a/backend/engine/engine.py
+++ b/backend/engine/engine.py
@@ -1,5 +1,6 @@
import ast
import json
+from copy import deepcopy
from datetime import datetime
from tqdm import tqdm
@@ -547,7 +548,6 @@ async def model_engine(body: PlanTriggerRequest):
epc_searcher.ordnance_survey_client.property_type = config.get("property_type", None)
# For the moment, our OS API access is unavailable, so we skip and interpolate
epc_searcher.find_property(skip_os=True)
- # TODO: Placeholder
if epc_searcher.newest_epc.get("estimated") and body.file_format == "domna_asset_list":
epc_searcher.newest_epc["uprn-source"] = epc_searcher.UPRN_SOURCE_SIMULATED
@@ -821,11 +821,7 @@ async def model_engine(body: PlanTriggerRequest):
x in property_measure_types for x in assumptions.measures_needing_ventilation
) and not p.has_ventilation
- input_measures = optimiser_functions.prepare_input_measures(
- measures_to_optimise, body.goal, needs_ventilation
- )
-
- if not input_measures[0]:
+ if not measures_to_optimise:
# Nothing to do, we just reshape the recommendations
recommendations[p.id] = optimiser_functions.flatten_recommendations_with_defaults(
p.id, recommendations, set()
@@ -838,18 +834,77 @@ async def model_engine(body: PlanTriggerRequest):
gain = optimiser_functions.calculate_gain(body=body, p=p, fixed_gain=fixed_gain)
from backend.Funding import Funding
+ from recommendations.optimiser.funding_optimiser import optimise_with_funding_paths
+ from recommendations.recommendation_utils import convert_thickness_to_numeric, get_wall_u_value
+
funding = Funding(
+ tenure=body.housing_type,
project_scores_matrix=project_scores_matrix,
partial_project_scores_matrix=partial_project_scores_matrix,
whlg_eligible_postcodes=whlg_eligible_postcodes,
eco4_social_cavity_abs_rate=13,
eco4_social_solid_abs_rate=17,
+ eco4_private_cavity_abs_rate=13,
+ eco4_private_solid_abs_rate=17,
gbis_social_cavity_abs_rate=21,
gbis_social_solid_abs_rate=25,
gbis_private_cavity_abs_rate=21,
gbis_private_solid_abs_rate=28,
)
+ # When the goal is Increasing EPC, we can run the funding optimiser
+ if body.goal == "Increasing EPC":
+
+ # We insert the innovation uplift
+ measures_to_optimise_with_uplift = deepcopy(measures_to_optimise)
+
+ li_thickness = convert_thickness_to_numeric(
+ p.roof["insulation_thickness"], p.roof["is_pitched"], p.roof["is_flat"]
+ )
+ current_wall_u_value = p.walls["thermal_transmittance"]
+ if current_wall_u_value is None:
+ current_wall_u_value = get_wall_u_value(
+ clean_description=p.walls["clean_description"],
+ age_band=p.age_band,
+ is_granite_or_whinstone=p.walls["is_granite_or_whinstone"],
+ is_sandstone_or_limestone=p.walls["is_sandstone_or_limestone"],
+ )
+
+ for group in measures_to_optimise_with_uplift:
+ for r in group:
+ if r["type"] in ["mechanical_ventilation", "low_energy_lighting", "secondary_heating"]:
+ r["innovation_uplift"] = 0
+ continue
+
+ r["innovation_uplift"] = funding.get_innovation_uplift(
+ measure=r,
+ starting_sap=p.data["current-energy-efficiency"],
+ floor_area=p.floor_area,
+ is_cavity=p.walls["is_cavity_wall"],
+ current_wall_uvalue=current_wall_u_value,
+ is_partial="partial" in p.walls["clean_description"].lower(),
+ existing_li_thickness=li_thickness,
+ mainheating=p.main_heating,
+ main_fuel=p.main_fuel,
+ mainheat_energy_eff=p.data["mainheat-energy-eff"],
+ )
+
+ input_measures = optimiser_functions.prepare_input_measures(
+ measures_to_optimise_with_uplift, body.goal, needs_ventilation, funding=True
+ )
+
+ solutions = optimise_with_funding_paths(
+ p=p,
+ input_measures=input_measures,
+ housing_type=body.housing_type,
+ budget=body.budget,
+ target_gain=gain,
+ funding=funding
+ )
+
+ # Given the solutions we select the optimal one
+ # optimal_solution =
+
if not body.optimise:
if body.goal != "Increasing EPC":
raise NotImplementedError("Only EPC optimisation is currently supported")
diff --git a/etl/epc/DataProcessor.py b/etl/epc/DataProcessor.py
index 9655cf77..f5fc3582 100644
--- a/etl/epc/DataProcessor.py
+++ b/etl/epc/DataProcessor.py
@@ -11,7 +11,6 @@ from etl.epc.settings import (
IGNORED_TENURES,
FULLY_GLAZED_DESCRIPTIONS,
AVERAGE_FIXED_FEATURES,
- BUILT_FORM_REMAP,
COLUMNS_TO_MERGE_ON,
FIXED_FEATURES,
COLUMNTYPES,
@@ -123,7 +122,6 @@ class EPCDataProcessor:
self.confine_data(ignore_step=ignore_step)
self.remap_anomalies()
self.remap_floor_level(ignore_step=ignore_step)
- self.remap_build_form()
self.cast_data_column_values_to_lower()
self.standardise_construction_age_band(ignore_step=ignore_step)
self.clean_missing_rooms(ignore_step=ignore_step)
@@ -240,13 +238,6 @@ class EPCDataProcessor:
for col in convert_to_lower:
self.data[col] = self.data[col].str.lower()
- def remap_build_form(self):
- """
- Remap build form to standard values
- No Violation mode or newdata modes required
- """
- self.data["BUILT_FORM"] = self.data["BUILT_FORM"].replace(BUILT_FORM_REMAP)
-
def remap_anomalies(self):
"""
Remap anomalies to None
diff --git a/etl/epc/Record.py b/etl/epc/Record.py
index b8950757..8e6be5d0 100644
--- a/etl/epc/Record.py
+++ b/etl/epc/Record.py
@@ -7,7 +7,7 @@ from etl.epc.ValidationConfiguration import (
)
from etl.epc.DataProcessor import EPCDataProcessor
from recommendations.rdsap_tables import england_wales_age_band_lookup, FLOOR_LEVEL_MAP
-from etl.epc.settings import DATA_ANOMALY_MATCHES, BUILT_FORM_REMAP
+from etl.epc.settings import DATA_ANOMALY_MATCHES
import re
import os
import numpy as np
@@ -748,10 +748,6 @@ class EPCRecord:
if not self.prepared_epc:
raise ValueError("EPC Recrod doesn not contain epc data")
- self.prepared_epc["built-form"] = BUILT_FORM_REMAP.get(
- self.prepared_epc["built-form"], self.prepared_epc["built-form"]
- )
-
if self.prepared_epc["built-form"] in DATA_ANOMALY_MATCHES:
if self.prepared_epc["property-type"] in ["Flat", "Maisonette"]:
self.prepared_epc["built-form"] = "End-Terrace"
diff --git a/recommendations/HeatingRecommender.py b/recommendations/HeatingRecommender.py
index e7e008d5..d2bccbcc 100644
--- a/recommendations/HeatingRecommender.py
+++ b/recommendations/HeatingRecommender.py
@@ -526,9 +526,8 @@ class HeatingRecommender:
ashp_descriptions = {
"Time and temperature zone control": (
f"Install two cascaded air source heat pumps, and upgrade heating controls to Smart Thermostats, "
- "room sensors and smart radiator valves (time & temperature zone control). Ensure you have an 18 "
- "or "
- "24 hour tariff"
+ "room sensors and smart radiator valves (time & temperature zone control). Ensure you have single "
+ "tariff"
)
}
else:
@@ -536,9 +535,8 @@ class HeatingRecommender:
ashp_descriptions = {
"Time and temperature zone control": (
f"Install a {ashp_size}KW air source heat pump, and upgrade heating controls to Smart Thermostats, "
- "room sensors and smart radiator valves (time & temperature zone control). Ensure you have an 18 "
- "or "
- "24 hour tariff"
+ "room sensors and smart radiator valves (time & temperature zone control). Ensure you have a "
+ "single tariff"
),
"Programmer, TRVs and bypass": (
f"Install a {ashp_size}KW air source heat pump, with programmer, TRVs and a Bypass valve. Ensure "
@@ -560,7 +558,7 @@ class HeatingRecommender:
ashp_costs_with_controls[key] += controls_rec[key]
if controls_rec is None:
- description = f"Install a {ashp_size}KW Air source heat pump. Ensure you have an 18 or 24 hour tariff"
+ description = f"Install a {ashp_size}KW Air source heat pump. Ensure you have a single tariff"
elif already_installed:
description = "The property already has an air source heat pump, no further action needed."
else:
@@ -581,9 +579,10 @@ class HeatingRecommender:
f" £7,500 of funding can be claimed from the boiler upgrade scheme"
)
+ # These are the impacts based on a single tariff with an ashp
simulation_config = {
- "mainheat_energy_eff_ending": "Very Good",
- "hot_water_energy_eff_ending": "Very Good"
+ "mainheat_energy_eff_ending": "Good",
+ "hot_water_energy_eff_ending": "Average"
}
description_simulation = {
"mainheat-description": new_heating_description,
diff --git a/recommendations/SolarPvRecommendations.py b/recommendations/SolarPvRecommendations.py
index 1a9b47ac..f21b7bf3 100644
--- a/recommendations/SolarPvRecommendations.py
+++ b/recommendations/SolarPvRecommendations.py
@@ -291,5 +291,6 @@ class SolarPvRecommendations:
},
"initial_ac_kwh_per_year": recommendation_config["initial_ac_kwh_per_year"],
"description_simulation": {"photo-supply": roof_coverage_percent},
+ "innovation_rate": solar_pv_product["innovation_rate"],
}
)