mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-07-12 13:29:04 +00:00
tweaking solar recommendations
This commit is contained in:
parent
800b673150
commit
d7ed4dd9a4
9 changed files with 92 additions and 30 deletions
|
|
@ -538,7 +538,8 @@ class Property:
|
||||||
"loft_insulation", "room_roof_insulation", "flat_roof_insulation",
|
"loft_insulation", "room_roof_insulation", "flat_roof_insulation",
|
||||||
"solid_floor_insulation", "suspended_floor_insulation",
|
"solid_floor_insulation", "suspended_floor_insulation",
|
||||||
"windows_glazing", "solar_pv", "heating", "hot_water_tank_insulation",
|
"windows_glazing", "solar_pv", "heating", "hot_water_tank_insulation",
|
||||||
"heating_control", "secondary_heating", "cylinder_thermostat", "mixed_glazing"
|
"heating_control", "secondary_heating", "cylinder_thermostat", "mixed_glazing",
|
||||||
|
"extension_cavity_wall_insulation",
|
||||||
]:
|
]:
|
||||||
raise NotImplementedError(
|
raise NotImplementedError(
|
||||||
"Implement me, given type %s" % recommendation["type"]
|
"Implement me, given type %s" % recommendation["type"]
|
||||||
|
|
|
||||||
|
|
@ -272,23 +272,10 @@ class GoogleSolarApi:
|
||||||
|
|
||||||
roi_summary = []
|
roi_summary = []
|
||||||
for segment in roof_segment_summaries:
|
for segment in roof_segment_summaries:
|
||||||
|
|
||||||
if segment["panelsCount"] < min_panels:
|
|
||||||
continue
|
|
||||||
|
|
||||||
wattage = segment["panelsCount"] * self.insights_data["solarPotential"]["panelCapacityWatts"]
|
wattage = segment["panelsCount"] * self.insights_data["solarPotential"]["panelCapacityWatts"]
|
||||||
generated_dc_energy = segment["yearlyEnergyDcKwh"]
|
generated_dc_energy = segment["yearlyEnergyDcKwh"]
|
||||||
ratio = generated_dc_energy / wattage
|
ratio = generated_dc_energy / wattage
|
||||||
|
|
||||||
if cost_instance is None:
|
|
||||||
cost = MCS_SOLAR_PV_COST_DATA["average_cost_per_kwh"] * (wattage / 1000)
|
|
||||||
else:
|
|
||||||
cost = cost_instance.solar_pv(
|
|
||||||
n_panels=segment["panelsCount"],
|
|
||||||
has_battery=False,
|
|
||||||
n_floors=property_instance.number_of_floors,
|
|
||||||
)["total"]
|
|
||||||
|
|
||||||
roi_summary.append(
|
roi_summary.append(
|
||||||
{
|
{
|
||||||
"segmentIndex": segment["segmentIndex"],
|
"segmentIndex": segment["segmentIndex"],
|
||||||
|
|
@ -296,7 +283,6 @@ class GoogleSolarApi:
|
||||||
"generated_dc_energy": generated_dc_energy,
|
"generated_dc_energy": generated_dc_energy,
|
||||||
"ratio": ratio,
|
"ratio": ratio,
|
||||||
"n_panels": segment["panelsCount"],
|
"n_panels": segment["panelsCount"],
|
||||||
"cost": cost,
|
|
||||||
"panneled_roof_area": self.panel_area * int(segment["panelsCount"])
|
"panneled_roof_area": self.panel_area * int(segment["panelsCount"])
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
@ -305,10 +291,21 @@ class GoogleSolarApi:
|
||||||
if roi_summary.empty:
|
if roi_summary.empty:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
if roi_summary["n_panels"].sum() < min_panels:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if cost_instance is None:
|
||||||
|
total_cost = MCS_SOLAR_PV_COST_DATA["average_cost_per_kwh"] * (wattage / 1000)
|
||||||
|
else:
|
||||||
|
total_cost = cost_instance.solar_pv(
|
||||||
|
n_panels=roi_summary["n_panels"].sum(),
|
||||||
|
has_battery=False,
|
||||||
|
n_floors=property_instance.number_of_floors,
|
||||||
|
)["total"]
|
||||||
|
|
||||||
weighted_ratio = np.average(
|
weighted_ratio = np.average(
|
||||||
roi_summary["ratio"].values, weights=roi_summary["generated_dc_energy"].values
|
roi_summary["ratio"].values, weights=roi_summary["generated_dc_energy"].values
|
||||||
)
|
)
|
||||||
total_cost = roi_summary["cost"].sum()
|
|
||||||
yearly_dc_energy = roi_summary["generated_dc_energy"].sum()
|
yearly_dc_energy = roi_summary["generated_dc_energy"].sum()
|
||||||
|
|
||||||
panel_performance.append(
|
panel_performance.append(
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,10 @@ PESSIMISTIC_ASHP_EFFICIENCY = 200
|
||||||
AVERAGE_ASHP_EFFICIENCY = 250
|
AVERAGE_ASHP_EFFICIENCY = 250
|
||||||
|
|
||||||
# Conservative estimate of the proportion of electricity that will be consumed, whereas the rest will
|
# Conservative estimate of the proportion of electricity that will be consumed, whereas the rest will
|
||||||
# be exported
|
# be exported. These are averages based on Google research. E.g
|
||||||
|
# https://www.nea.org.uk/who-we-are/innovation-technical-evaluation/solarpv/solarpv-batteries
|
||||||
SOLAR_CONSUMPTION_PROPORTION = 0.5
|
SOLAR_CONSUMPTION_PROPORTION = 0.5
|
||||||
|
SOLAR_CONSUMPTION_WITH_BATTERY_PROPORTION = 0.7
|
||||||
|
|
||||||
# Typically, each solar panel takes up around 3.4 m2 of roof space under RdSAP. This was been verified in Elmhurst
|
# Typically, each solar panel takes up around 3.4 m2 of roof space under RdSAP. This was been verified in Elmhurst
|
||||||
RDSAP_AREA_PER_PANEL = 3.4
|
RDSAP_AREA_PER_PANEL = 3.4
|
||||||
|
|
|
||||||
|
|
@ -369,9 +369,9 @@ def extract_property_request_data(
|
||||||
property_non_invasive_recommendations["recommendations"] = str(transformed)
|
property_non_invasive_recommendations["recommendations"] = str(transformed)
|
||||||
|
|
||||||
property_valution = next((
|
property_valution = next((
|
||||||
x for x in valuation_data if
|
float(x["value"]) for x in valuation_data if
|
||||||
(str(x["uprn"]) == str(uprn))
|
(str(x["uprn"]) == str(uprn))
|
||||||
), {})
|
), None)
|
||||||
|
|
||||||
return patch, property_already_installed, property_non_invasive_recommendations, property_valution
|
return patch, property_already_installed, property_non_invasive_recommendations, property_valution
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,12 @@ def app():
|
||||||
file_name=non_invasive_recommendations_filename
|
file_name=non_invasive_recommendations_filename
|
||||||
)
|
)
|
||||||
|
|
||||||
valuation_data = [{100050770761: 67_000}]
|
valuation_data = [
|
||||||
|
{
|
||||||
|
"uprn": 100050770761,
|
||||||
|
"value": 67_000
|
||||||
|
}
|
||||||
|
]
|
||||||
# Store valuation data to s3
|
# Store valuation data to s3
|
||||||
valuation_filename = f"{USER_ID}/{PORTFOLIO_ID}/valuation.csv"
|
valuation_filename = f"{USER_ID}/{PORTFOLIO_ID}/valuation.csv"
|
||||||
save_csv_to_s3(
|
save_csv_to_s3(
|
||||||
|
|
|
||||||
|
|
@ -129,10 +129,11 @@ class Recommendations:
|
||||||
phase += 1
|
phase += 1
|
||||||
|
|
||||||
# We handle recommendations covering specific non-invasive measures
|
# We handle recommendations covering specific non-invasive measures
|
||||||
self.wall_recomender.recommend_extended(measures=measures)
|
new_phase = self.wall_recomender.recommend_extended(phase=phase, measures=measures)
|
||||||
if self.wall_recomender.extended_recommendations:
|
if self.wall_recomender.extended_recommendations:
|
||||||
property_recommendations.append(self.wall_recomender.extended_recommendations)
|
property_recommendations.append(self.wall_recomender.extended_recommendations)
|
||||||
# We don't have any phasing here
|
# We don't have any phasing here
|
||||||
|
phase = new_phase
|
||||||
|
|
||||||
self.roof_recommender.recommend(phase=phase, measures=measures, default_u_values=self.default_u_values)
|
self.roof_recommender.recommend(phase=phase, measures=measures, default_u_values=self.default_u_values)
|
||||||
if self.roof_recommender.recommendations:
|
if self.roof_recommender.recommendations:
|
||||||
|
|
@ -481,6 +482,27 @@ class Recommendations:
|
||||||
]:
|
]:
|
||||||
# We don't have a percieved sap impact of mechanical ventilation or trickle vents, and we don't
|
# We don't have a percieved sap impact of mechanical ventilation or trickle vents, and we don't
|
||||||
# have the capacity to score draught proofing
|
# have the capacity to score draught proofing
|
||||||
|
if rec["type"] == "extension_cavity_wall_insulation":
|
||||||
|
|
||||||
|
previous_phase = [x for x in impact_summary if x["phase"] == (rec["phase"] - 1)]
|
||||||
|
if previous_phase:
|
||||||
|
sap = previous_phase[0]["sap"]
|
||||||
|
carbon = previous_phase[0]["carbon"]
|
||||||
|
heat_demand = previous_phase[0]["heat_demand"]
|
||||||
|
else:
|
||||||
|
sap = float(property_instance.data["current-energy-efficiency"])
|
||||||
|
carbon = float(property_instance.data["co2-emissions-current"])
|
||||||
|
heat_demand = float(property_instance.data["energy-consumption-current"])
|
||||||
|
|
||||||
|
impact_summary.append(
|
||||||
|
{
|
||||||
|
"phase": rec["phase"],
|
||||||
|
"recommendation_id": rec["recommendation_id"],
|
||||||
|
"sap": sap + rec["sap_points"],
|
||||||
|
"carbon": carbon - rec["co2_equivalent_savings"],
|
||||||
|
"heat_demand": heat_demand - rec["heat_demand"],
|
||||||
|
}
|
||||||
|
)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
phase_energy_efficiency_metrics = {
|
phase_energy_efficiency_metrics = {
|
||||||
|
|
@ -571,6 +593,17 @@ class Recommendations:
|
||||||
property_phase_impact["carbon"], rec["co2_equivalent_savings"]
|
property_phase_impact["carbon"], rec["co2_equivalent_savings"]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if rec["type"] == "loft_insulation":
|
||||||
|
# When we have a loft insulation recommendation, where there is an extension and the existing
|
||||||
|
# amount of loft insulation is already good, we limit the SAP points
|
||||||
|
# By limiting here, we don't change the value in current_phase_values. This means that the
|
||||||
|
# future recommendations won't have an impact that is too large
|
||||||
|
li_sap_limit = RoofRecommendations.get_loft_insulation_sap_limit(
|
||||||
|
property_instance.data["roof-energy-eff"], property_instance.data["extension-count"]
|
||||||
|
)
|
||||||
|
if li_sap_limit is not None:
|
||||||
|
property_phase_impact["sap"] = min(property_phase_impact["sap"], li_sap_limit)
|
||||||
|
|
||||||
# Insert this information into the recommendation.
|
# Insert this information into the recommendation.
|
||||||
if not rec.get("survey", False):
|
if not rec.get("survey", False):
|
||||||
rec["sap_points"] = property_phase_impact["sap"]
|
rec["sap_points"] = property_phase_impact["sap"]
|
||||||
|
|
@ -672,7 +705,11 @@ class Recommendations:
|
||||||
{
|
{
|
||||||
"phase": r["phase"],
|
"phase": r["phase"],
|
||||||
"recommendation_id": r["recommendation_id"],
|
"recommendation_id": r["recommendation_id"],
|
||||||
"solar_kwh_savings": r["initial_ac_kwh_per_year"] * assumptions.SOLAR_CONSUMPTION_PROPORTION,
|
"solar_kwh_savings": (
|
||||||
|
r["initial_ac_kwh_per_year"] * assumptions.SOLAR_CONSUMPTION_PROPORTION
|
||||||
|
) if not r["has_battery"] else (
|
||||||
|
r["initial_ac_kwh_per_year"] * assumptions.SOLAR_CONSUMPTION_WITH_BATTERY_PROPORTION
|
||||||
|
),
|
||||||
} for recs in property_recommendations for r in recs if r["type"] == "solar_pv"
|
} for recs in property_recommendations for r in recs if r["type"] == "solar_pv"
|
||||||
], columns=["phase", "recommendation_id", "solar_kwh_savings"])
|
], columns=["phase", "recommendation_id", "solar_kwh_savings"])
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,23 @@ class RoofRecommendations:
|
||||||
self.property.roof["is_flat"]
|
self.property.roof["is_flat"]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_loft_insulation_sap_limit(cls, roof_energy_eff, extension_count):
|
||||||
|
"""
|
||||||
|
Get the SAP limit for loft insulation
|
||||||
|
:param roof_energy_eff:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
|
||||||
|
if extension_count == 0:
|
||||||
|
# No limit
|
||||||
|
return None
|
||||||
|
|
||||||
|
if roof_energy_eff in ["Good", "Very Good"]:
|
||||||
|
return 1
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
def mds_loft_insulation(self, phase):
|
def mds_loft_insulation(self, phase):
|
||||||
"""
|
"""
|
||||||
For usages within the mds report
|
For usages within the mds report
|
||||||
|
|
@ -273,7 +290,7 @@ class RoofRecommendations:
|
||||||
# loft is already partially insulated.
|
# loft is already partially insulated.
|
||||||
# Note: This requirement is only for loft insulation
|
# Note: This requirement is only for loft insulation
|
||||||
if (
|
if (
|
||||||
(material["depth"] + insulation_thickness) < self.MINIMUM_RECOMMENDED_LOFT_INSULATION
|
material["depth"] < self.MINIMUM_RECOMMENDED_LOFT_INSULATION
|
||||||
) and is_pitched:
|
) and is_pitched:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|
@ -295,6 +312,7 @@ class RoofRecommendations:
|
||||||
|
|
||||||
# We allow a small tolerance for error so we don't discount the recommendation entirely
|
# We allow a small tolerance for error so we don't discount the recommendation entirely
|
||||||
if new_u_value <= self.BUILDING_REGULATIONS_PART_L_MAX_U_VALUE:
|
if new_u_value <= self.BUILDING_REGULATIONS_PART_L_MAX_U_VALUE:
|
||||||
|
|
||||||
lowest_selected_u_value = update_lowest_selected_u_value(lowest_selected_u_value, new_u_value)
|
lowest_selected_u_value = update_lowest_selected_u_value(lowest_selected_u_value, new_u_value)
|
||||||
|
|
||||||
cost_result = self.costs.loft_and_flat_insulation(
|
cost_result = self.costs.loft_and_flat_insulation(
|
||||||
|
|
|
||||||
|
|
@ -196,7 +196,7 @@ class SolarPvRecommendations:
|
||||||
]
|
]
|
||||||
|
|
||||||
roof_area = self.property.roof_area
|
roof_area = self.property.roof_area
|
||||||
solar_configurations = panel_performance.head(3).reset_index(drop=True)
|
solar_configurations = panel_performance.head(6).reset_index(drop=True)
|
||||||
|
|
||||||
# We combine each of these configurations with estimates with and without a battery
|
# We combine each of these configurations with estimates with and without a battery
|
||||||
for rank, recommendation_config in solar_configurations.iterrows():
|
for rank, recommendation_config in solar_configurations.iterrows():
|
||||||
|
|
|
||||||
|
|
@ -270,7 +270,7 @@ class WallRecommendations(Definitions):
|
||||||
# If the u-value is within regulations, we don't do anything
|
# If the u-value is within regulations, we don't do anything
|
||||||
return
|
return
|
||||||
|
|
||||||
def recommend_extended(self, measures):
|
def recommend_extended(self, phase, measures):
|
||||||
"""
|
"""
|
||||||
Where we have extended measures, such as extension insulation, which cannot typically be picked up
|
Where we have extended measures, such as extension insulation, which cannot typically be picked up
|
||||||
from the EPC api, we handle the recommendation of these here
|
from the EPC api, we handle the recommendation of these here
|
||||||
|
|
@ -283,22 +283,24 @@ class WallRecommendations(Definitions):
|
||||||
|
|
||||||
measures_to_recommend = [measure for measure in measures if measure in extended_measures]
|
measures_to_recommend = [measure for measure in measures if measure in extended_measures]
|
||||||
if not measures_to_recommend:
|
if not measures_to_recommend:
|
||||||
return
|
return phase
|
||||||
|
|
||||||
# We reset this to be empty
|
# We reset this to be empty
|
||||||
self.extended_recommendations = []
|
self.extended_recommendations = []
|
||||||
|
|
||||||
|
recommendation_phase = phase
|
||||||
for measure in measures_to_recommend:
|
for measure in measures_to_recommend:
|
||||||
if measure == "extension_cavity_wall_insulation":
|
if measure == "extension_cavity_wall_insulation":
|
||||||
recommendation = self.recommend_extension_cavity_wall_insulation()
|
recommendation = self.recommend_extension_cavity_wall_insulation(phase=recommendation_phase)
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError(f"Measure {measure} is not implemented")
|
raise NotImplementedError(f"Measure {measure} is not implemented")
|
||||||
|
recommendation_phase += 1
|
||||||
|
|
||||||
self.extended_recommendations.append(recommendation)
|
self.extended_recommendations.append(recommendation)
|
||||||
|
|
||||||
return
|
return recommendation_phase
|
||||||
|
|
||||||
def recommend_extension_cavity_wall_insulation(self):
|
def recommend_extension_cavity_wall_insulation(self, phase):
|
||||||
"""
|
"""
|
||||||
This function produces the recommendation for extension cavity wall insulation
|
This function produces the recommendation for extension cavity wall insulation
|
||||||
:return:
|
:return:
|
||||||
|
|
@ -331,7 +333,7 @@ class WallRecommendations(Definitions):
|
||||||
)
|
)
|
||||||
|
|
||||||
recommendation = {
|
recommendation = {
|
||||||
"phase": None,
|
"phase": phase,
|
||||||
"parts": [],
|
"parts": [],
|
||||||
"type": "extension_cavity_wall_insulation",
|
"type": "extension_cavity_wall_insulation",
|
||||||
"measure_type": "extension_cavity_wall_insulation",
|
"measure_type": "extension_cavity_wall_insulation",
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue