mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
Added unit tests for annual bill savings appliance consumption
This commit is contained in:
parent
01c50eb5cb
commit
83339d2cbe
8 changed files with 123 additions and 23 deletions
2
.idea/Model.iml
generated
2
.idea/Model.iml
generated
|
|
@ -7,7 +7,7 @@
|
|||
<sourceFolder url="file://$MODULE_DIR$/open_uprn" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/recommendations" isTestSource="false" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="Python 3.10 (backend)" jdkType="Python SDK" />
|
||||
<orderEntry type="jdk" jdkName="Python 3.10 (model_data)" jdkType="Python SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
<component name="PyNamespacePackagesService">
|
||||
|
|
|
|||
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
|
|
@ -3,7 +3,7 @@
|
|||
<component name="Black">
|
||||
<option name="sdkName" value="Python 3.10 (backend)" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10 (backend)" project-jdk-type="Python SDK" />
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10 (model_data)" project-jdk-type="Python SDK" />
|
||||
<component name="PythonCompatibilityInspectionAdvertiser">
|
||||
<option name="version" value="3" />
|
||||
</component>
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ from recommendations.recommendation_utils import (
|
|||
esimtate_pitched_roof_area,
|
||||
estimate_windows,
|
||||
)
|
||||
from backend.ml_models.AnnualBillSavings import AnnualBillSavings
|
||||
|
||||
ENVIRONMENT = os.environ.get("ENVIRONMENT", "dev")
|
||||
DATA_BUCKET = os.environ.get(
|
||||
|
|
@ -590,6 +591,23 @@ class Property:
|
|||
self.set_energy_source()
|
||||
self.find_energy_sources()
|
||||
|
||||
def set_current_energy_bill(self):
|
||||
"""
|
||||
Given what we know about the property now, estimates the current energy consumption using the UCL paper
|
||||
https://www.sciencedirect.com/science/article/pii/S0378778823002542
|
||||
:return:
|
||||
"""
|
||||
starting_heat_demand = (
|
||||
float(self.data["energy-consumption-current"]) * self.floor_area
|
||||
)
|
||||
|
||||
self.current_adjusted_energy = AnnualBillSavings.adjust_energy_to_metered(
|
||||
epc_energy_consumption=starting_heat_demand,
|
||||
current_epc_rating=self.data["current-energy-rating"],
|
||||
)
|
||||
|
||||
self.current_energy_bill = AnnualBillSavings.calculate_annual_bill(self.current_adjusted_energy)
|
||||
|
||||
def set_spatial(self, spatial: pd.DataFrame):
|
||||
"""
|
||||
Sets whether the property is in a conservation area given the output of the ConservationAreaClient
|
||||
|
|
@ -909,14 +927,13 @@ class Property:
|
|||
return component_data
|
||||
|
||||
def set_adjusted_energy(
|
||||
self, current_adjusted_energy, expected_adjusted_energy, current_energy_bill, expected_energy_bill
|
||||
self, expected_adjusted_energy, expected_energy_bill
|
||||
):
|
||||
"""
|
||||
Stores these values for usage later
|
||||
"""
|
||||
self.current_adjusted_energy = current_adjusted_energy
|
||||
|
||||
self.expected_adjusted_energy = expected_adjusted_energy
|
||||
self.current_energy_bill = current_energy_bill
|
||||
self.expected_energy_bill = expected_energy_bill
|
||||
|
||||
def set_windows_count(self):
|
||||
|
|
|
|||
|
|
@ -14,6 +14,31 @@ class GoogleSolarApi:
|
|||
# be exported
|
||||
SOLAR_CONSUMPTION_PROPORTION = 0.5
|
||||
|
||||
# These are variables, described in the documentation for cost analysis for non-us locations, seen here
|
||||
# https://developers.google.com/maps/documentation/solar/calculate-costs-non-us
|
||||
# We use the default figures that the API uses for US locations
|
||||
|
||||
# The factor by which the cost of electricity increases annually. The Solar API uses 1.022 (2.2% annual increase)
|
||||
# for US locations.
|
||||
cost_increase_factor = 1.022
|
||||
|
||||
# The efficiency at which an inverter converts the DC electricity that is produced by the solar panels to the AC
|
||||
# electricity that is used in a household. The Solar API uses 85% for US locations. We use 0.95.5 which is the
|
||||
# middle value of the 93-98% range, cited by Sunsave:
|
||||
# https://www.sunsave.energy/solar-panels-advice/system-size/inverters
|
||||
dc_to_ac_rate = 0.955
|
||||
|
||||
# The Solar API uses 1.04 (4% annual increase) for US locations
|
||||
discount_rate = 1.04
|
||||
|
||||
# How much the efficiency of the solar panels declines each year. The Solar API uses 0.995 (0.5% annual decrease)
|
||||
# for US locations
|
||||
efficiency_depreciation_factor = 0.995
|
||||
|
||||
# The expected lifespan of the solar installation. The Solar API uses 20 years. Adjust this value as needed for
|
||||
# your area
|
||||
installation_life_span = 20
|
||||
|
||||
def __init__(self, api_key, max_retries=5):
|
||||
"""
|
||||
Initialize the GoogleSolarApi class with the provided API key and maximum retries.
|
||||
|
|
@ -94,6 +119,13 @@ class GoogleSolarApi:
|
|||
self.insights_data["solarPotential"]["panelWidthMeters"]
|
||||
)
|
||||
self.panel_wattage = self.insights_data["solarPotential"]["panelCapacityWatts"]
|
||||
if self.panel_wattage != 400:
|
||||
# In the API documentation, it claims that the default output is 250W, however we've only seen 400W, so if
|
||||
# we get anything other than 400W, we'll need to adjust the calculations in the output. For this, we should
|
||||
# refer to https://developers.google.com/maps/documentation/solar/calculate-costs-non-us
|
||||
# Where the documentation explains how to adjust the yearlyEnergyDcKwh figures.
|
||||
# It should be straightforward, but I'd rather see an actual instance of this happening
|
||||
raise NotImplementedError("Panel wattage is not 400W - implement me")
|
||||
|
||||
# Automatically exclude north-facing segments
|
||||
self.exclude_north_facing_segments()
|
||||
|
|
|
|||
|
|
@ -426,9 +426,7 @@ async def trigger_plan(body: PlanTriggerRequest):
|
|||
|
||||
(
|
||||
recommendations_with_impact,
|
||||
current_adjusted_energy,
|
||||
expected_adjusted_energy,
|
||||
current_energy_bill,
|
||||
expected_energy_bill
|
||||
) = (
|
||||
Recommendations.calculate_recommendation_impact(
|
||||
|
|
@ -440,9 +438,7 @@ async def trigger_plan(body: PlanTriggerRequest):
|
|||
|
||||
# Store the resulting adjusted energy in the property instance
|
||||
property_instance.set_adjusted_energy(
|
||||
current_adjusted_energy=current_adjusted_energy,
|
||||
expected_adjusted_energy=expected_adjusted_energy,
|
||||
current_energy_bill=current_energy_bill,
|
||||
expected_energy_bill=expected_energy_bill
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
import numpy as np
|
||||
|
||||
|
||||
class AnnualBillSavings:
|
||||
"""
|
||||
This is a simple class which will estimate the annual bill savings, based on the kwh savings.
|
||||
|
|
@ -60,8 +63,58 @@ class AnnualBillSavings:
|
|||
|
||||
return cls.ELECTRICITY_PRICE_CAP * kwh + (cls.DAILY_STANDARD_CHARGE_ELECTRICITY * 365)
|
||||
|
||||
@staticmethod
|
||||
def calculate_occupants(total_floor_area):
|
||||
"""
|
||||
From Table 1b of the SAP 2012 documentation https://bregroup.com/documents/d/bre-group/sap-2012_9-92
|
||||
Provides a methodology to estimate occupancy, based on floor area. This is used to calculate the amount of
|
||||
electricity used be appliances and during cooking.
|
||||
:param total_floor_area:
|
||||
:return:
|
||||
"""
|
||||
|
||||
if total_floor_area <= 13.9:
|
||||
return 1
|
||||
|
||||
return 1 + (1.76 * (1 - np.exp(-0.000349 * (total_floor_area - 13.9) * (total_floor_area - 13.9))) + 0.0013 * (
|
||||
total_floor_area - 13.9))
|
||||
|
||||
@staticmethod
|
||||
def estimate_electrical_appliances(occupants, total_floor_area):
|
||||
"""
|
||||
From secion L2 of SAP2012 Electrical appliances
|
||||
https://bregroup.com/documents/d/bre-group/sap-2012_9-92
|
||||
Used to estimate the amount of energy used by electrical appliances
|
||||
:param occupants:
|
||||
:param total_floor_area:
|
||||
:return:
|
||||
"""
|
||||
e_a = 207.8 * np.power(total_floor_area * occupants, 0.4717)
|
||||
|
||||
days_in_month = {
|
||||
1: 31,
|
||||
2: 28,
|
||||
3: 31,
|
||||
4: 30,
|
||||
5: 31,
|
||||
6: 30,
|
||||
7: 31,
|
||||
8: 31,
|
||||
9: 30,
|
||||
10: 31,
|
||||
11: 30,
|
||||
12: 31
|
||||
}
|
||||
|
||||
eam = 0
|
||||
for m in range(1, 13):
|
||||
nm = days_in_month[m]
|
||||
eam += e_a * (1 + 0.157 * np.cos(2 * np.pi * (m - 1.78) / 12)) * nm / 365
|
||||
|
||||
return eam
|
||||
|
||||
@classmethod
|
||||
def adjust_energy_to_metered(cls, epc_energy_consumption, current_epc_rating):
|
||||
def adjust_energy_to_metered(cls, epc_energy_consumption, current_epc_rating, total_floor_area):
|
||||
"""
|
||||
The over-prediction of energy use by EPCs in Great Britain: A comparison
|
||||
of EPC-modelled and metered primary energy use intensity
|
||||
|
|
@ -72,6 +125,13 @@ class AnnualBillSavings:
|
|||
:return:
|
||||
"""
|
||||
|
||||
# The EPC energy consumption does not factor in cooking and applicance use, so this is estimated using the
|
||||
# methodology outlined in SAP, and is discussed in the UCL paper in section 3.1.1
|
||||
estimated_occupants = cls.calculate_occupants(total_floor_area=total_floor_area)
|
||||
appliances_energy_use = cls.estimate_electrical_appliances(estimated_occupants, total_floor_area)
|
||||
|
||||
epc_energy_consumption += appliances_energy_use
|
||||
|
||||
gradients = {
|
||||
"A": -0.1,
|
||||
"B": -0.1,
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ from backend.SearchEpc import SearchEpc
|
|||
import urllib.parse
|
||||
import requests
|
||||
from datetime import datetime
|
||||
from scipy import stats
|
||||
|
||||
from fuzzywuzzy import fuzz
|
||||
import numpy as np
|
||||
|
|
@ -1598,7 +1599,6 @@ def compile_data_final():
|
|||
property_attributes[c] = property_attributes[c].fillna(0)
|
||||
property_attributes[c] = property_attributes[c].astype(float)
|
||||
|
||||
from scipy import stats
|
||||
for col in fill_with_mode:
|
||||
property_attributes[col] = property_attributes[col].replace('', None)
|
||||
mode_val = stats.mode([float(x) for x in property_attributes[col].values if x not in [None, "", np.nan]])[0]
|
||||
|
|
@ -1632,6 +1632,12 @@ def compile_data_final():
|
|||
# s3_file_name="customers/Stonewater/clustering/clustering_dataframe.pkl"
|
||||
# )
|
||||
|
||||
# from utils.s3 import read_pickle_from_s3
|
||||
# data = read_pickle_from_s3(
|
||||
# bucket_name="retrofit-data-dev",
|
||||
# s3_file_name="customers/Stonewater/clustering/clustering_dataframe.pkl"
|
||||
# )
|
||||
|
||||
# CLUSTERING!!
|
||||
|
||||
# from sklearn.cluster import KMeans
|
||||
|
|
|
|||
|
|
@ -311,14 +311,6 @@ class Recommendations:
|
|||
# This is the unadjusted resulting heat demand
|
||||
predicted_heat_demand_change = starting_heat_demand - expected_heat_demand
|
||||
|
||||
# We don't want to adjust the heat demand for mechanical ventilation so we add it back on
|
||||
|
||||
# We adjust the heat demand figures to align to the UCL paper
|
||||
current_adjusted_energy = AnnualBillSavings.adjust_energy_to_metered(
|
||||
epc_energy_consumption=starting_heat_demand,
|
||||
current_epc_rating=property_instance.data["current-energy-rating"],
|
||||
)
|
||||
|
||||
# TODO: This isn't quite right as this is based on EVERY possible measure, not just the ones that are
|
||||
# actually implemented
|
||||
expected_adjusted_energy = AnnualBillSavings.adjust_energy_to_metered(
|
||||
|
|
@ -327,11 +319,10 @@ class Recommendations:
|
|||
)
|
||||
|
||||
adjusted_heat_demand_change = (
|
||||
current_adjusted_energy - expected_adjusted_energy
|
||||
property_instance.current_adjusted_energy - expected_adjusted_energy
|
||||
)
|
||||
|
||||
# TODO: We should determine if the home is gas & electricity or just electricity
|
||||
current_energy_bill = AnnualBillSavings.calculate_annual_bill(current_adjusted_energy)
|
||||
expected_energy_bill = AnnualBillSavings.calculate_annual_bill(expected_adjusted_energy)
|
||||
|
||||
for recommendations_by_type in property_recommendations:
|
||||
|
|
@ -410,8 +401,6 @@ class Recommendations:
|
|||
|
||||
return (
|
||||
property_recommendations,
|
||||
current_adjusted_energy,
|
||||
expected_adjusted_energy,
|
||||
current_energy_bill,
|
||||
expected_energy_bill
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue