mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
201 lines
7.2 KiB
Python
201 lines
7.2 KiB
Python
import numpy as np
|
|
|
|
QUARTERLY_ENERGY_PRICES = [
|
|
# 2024 Q1
|
|
{"start": "2024-01-01", "end": "2024-03-31", "electricity": 0.2, "gas": 0.042},
|
|
# 2023 Q4
|
|
{"start": "2023-10-01", "end": "2023-12-31", "electricity": 0.202, "gas": 0.51},
|
|
# 2023 Q3
|
|
{"start": "2023-07-01", "end": "2023-09-30", "electricity": 0.188, "gas": 0.46},
|
|
# 2023 Q2
|
|
{"start": "2023-04-01", "end": "2023-06-30", "electricity": 0.177, "gas": 0.456},
|
|
]
|
|
|
|
|
|
class AnnualBillSavings:
|
|
"""
|
|
This is a simple class which will estimate the annual bill savings, based on the kwh savings.
|
|
This class uses data from Ofgem, including their price caps, to provide us with an estimate for
|
|
1KWH of energy.
|
|
"""
|
|
|
|
# These gas an electricity consumption figures are based off of figures presented by Ofgem
|
|
# https://www.ofgem.gov.uk/information-consumers/energy-advice-households/average-gas-and-electricity-use-explained
|
|
AVERAGE_ELECTRICITY_CONSUMPTION = 2700
|
|
AVERAGE_GAS_CONSUMPTION = 11500
|
|
|
|
# Latest price cap figures from Ofgem are for April 2024
|
|
# https://www.ofgem.gov.uk/energy-price-cap
|
|
ELECTRICITY_PRICE_CAP = 0.2236
|
|
GAS_PRICE_CAP = 0.0548
|
|
# This is the most recent export payment figure, at 9.28p/kWh
|
|
# Smart export guarantee rates can be found here:
|
|
# https://www.sunsave.energy/solar-panels-advice/exporting-to-the-grid/best-seg-rates
|
|
ELECTRICITY_EXPORT_PAYMENT = 0.0928
|
|
|
|
# This is a weighted mean of the price caps, using the consumption figures above as weights
|
|
PRICE_FACTOR = 0.09549999999999999
|
|
|
|
# Daily standard charge, based on average across England, Scotland and Wales, and includes VAT
|
|
DAILY_STANDARD_CHARGE_GAS = 0.3143
|
|
DAILY_STANDARD_CHARGE_ELECTRICITY = 0.601
|
|
|
|
EPC_BANDS = ["G", "F", "E", "D", "C", "B", "A"]
|
|
|
|
@classmethod
|
|
def estimate(cls, kwh: float):
|
|
"""
|
|
Estimate the annual bill savings based on the kwh savings
|
|
:param kwh: The kwh savings
|
|
:return: An estimate for annual bill savings
|
|
"""
|
|
return cls.PRICE_FACTOR * kwh
|
|
|
|
@classmethod
|
|
def estimate_electric(cls, kwh: float):
|
|
"""
|
|
Estimate the annual bill savings based on the kwh savings
|
|
:param kwh: The kwh savings
|
|
:return: An estimate for annual bill savings
|
|
"""
|
|
return cls.ELECTRICITY_PRICE_CAP * kwh
|
|
|
|
@classmethod
|
|
def calculate_annual_bill(cls, kwh, mains_gas=True):
|
|
"""
|
|
This method will estimate the total annual bill for a property
|
|
It assumed gas & electricity are used
|
|
:param kwh: The total kwh consumption
|
|
:param mains_gas: Whether the property uses mains gas
|
|
:return: An estimate for annual bill
|
|
"""
|
|
|
|
if mains_gas:
|
|
return cls.PRICE_FACTOR * kwh + (
|
|
cls.DAILY_STANDARD_CHARGE_GAS + cls.DAILY_STANDARD_CHARGE_ELECTRICITY * 365)
|
|
|
|
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 estimate_appliances_energy_use(cls, total_floor_area):
|
|
# 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)
|
|
return appliances_energy_use
|
|
|
|
@classmethod
|
|
def adjust_energy_to_metered(cls, epc_energy, current_epc_rating):
|
|
"""
|
|
The over-prediction of energy use by EPCs in Great Britain: A comparison
|
|
of EPC-modelled and metered primary energy use intensity
|
|
|
|
Which can be found here: https://www.sciencedirect.com/science/article/pii/S0378778823002542
|
|
We implement the results on page 10
|
|
|
|
This is used to just re-map the cost from the EPC to the metered cost
|
|
epc_energy could be cost or kwh
|
|
:return:
|
|
"""
|
|
|
|
gradients = {
|
|
"A": -0.1,
|
|
"B": -0.1,
|
|
"C": -0.43,
|
|
"D": -0.52,
|
|
"E": -0.7,
|
|
"F": -0.76,
|
|
"G": -0.76
|
|
}
|
|
|
|
intercepts = {
|
|
"A": 28,
|
|
"B": 28,
|
|
"C": 97,
|
|
"D": 119,
|
|
"E": 160,
|
|
"F": 157,
|
|
"G": 157
|
|
}
|
|
|
|
gradient = gradients[current_epc_rating]
|
|
intercept = intercepts[current_epc_rating]
|
|
|
|
# This should be negative
|
|
consumption_difference = gradient * epc_energy + intercept
|
|
consumption_difference = 0 if consumption_difference > 0 else consumption_difference
|
|
|
|
adjusted_consumption = (epc_energy + consumption_difference)
|
|
if adjusted_consumption < 0:
|
|
raise ValueError("consumption_difference should be negative")
|
|
|
|
return adjusted_consumption
|
|
|
|
@classmethod
|
|
def adjust_expected_band(cls, expected_epc_rating, current_epc_rating):
|
|
"""
|
|
Because of the differing intercepts and intercepts when adjusting, it's possible for
|
|
expected_adjusted_energy to be bigger than current_adjusted_energy. In this case, we'll
|
|
adjust, against at most 1 EPC band above the curent. This function performs the EPC adjustment
|
|
:param expected_epc_rating: The expected EPC rating
|
|
:param current_epc_rating: The current EPC rating
|
|
"""
|
|
|
|
# Find index of expected EPC rating
|
|
expected_index = cls.EPC_BANDS.index(expected_epc_rating)
|
|
current_index = cls.EPC_BANDS.index(current_epc_rating)
|
|
|
|
if expected_index - 1 < current_index:
|
|
return current_epc_rating
|
|
|
|
return cls.EPC_BANDS[expected_index - 1]
|