From 4b2c4cb0a37fbf9faefcc19fb225c9801fad48db Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Mon, 12 Aug 2024 19:09:58 +0100 Subject: [PATCH] adding fuel pricing table --- backend/app/plan/router.py | 4 ++ backend/ml_models/AnnualBillSavings.py | 76 ++++++++++++++++++++++++++ recommendations/Recommendations.py | 4 +- 3 files changed, 83 insertions(+), 1 deletion(-) diff --git a/backend/app/plan/router.py b/backend/app/plan/router.py index 90d84bd9..6eb5d5ad 100644 --- a/backend/app/plan/router.py +++ b/backend/app/plan/router.py @@ -343,6 +343,8 @@ def extract_propert_on_site_recommendations(config, already_installed, non_invas for rec in property_non_invasive_recommendations["recommendations"]: if isinstance(rec, str): transformed.append({"type": rec, }) + else: + transformed.append(rec) property_non_invasive_recommendations["recommendations"] = str(transformed) @@ -720,6 +722,8 @@ async def trigger_plan(body: PlanTriggerRequest): ) # We now insert kwh estimates and costs into the recommendations + # TODO: We should join the methodology which maps the heating and hot water descriptions to the fuel types in + # Recommendations, but also the Property class for property_id in recommendations.keys(): property_recommendations = recommendations[property_id] property_instance = [p for p in input_properties if p.id == property_id][0] diff --git a/backend/ml_models/AnnualBillSavings.py b/backend/ml_models/AnnualBillSavings.py index 0317b9e3..50945874 100644 --- a/backend/ml_models/AnnualBillSavings.py +++ b/backend/ml_models/AnnualBillSavings.py @@ -1,4 +1,5 @@ import numpy as np +import pandas as pd QUARTERLY_ENERGY_PRICES = [ # 2024 Q1 @@ -40,6 +41,53 @@ class AnnualBillSavings: DAILY_STANDARD_CHARGE_GAS = 0.3143 DAILY_STANDARD_CHARGE_ELECTRICITY = 0.601 + # Based on https://www.nottenergy.com/advice-and-tools/project-energy-cost-comparison + # For July 2024. These quotes are based on the east midlands region, so we + FUEL_DATA = pd.DataFrame([ + {"Fuel": "Electricity Standard", "Price (p)": 28.58, "Unit": "kWh", "Boiler Efficiency (%)": 100, + "Energy Content, Net Calorific value (kWh/unit)": 1.00, "Price per kWh (p) (inc boiler efficiency)": 28.58, + "CO2eq emission factor kgCO2eq/kWh (Gross CV)": 0.275}, + {"Fuel": "Mains Gas Standard", "Price (p)": 6.31, "Unit": "kWh", "Boiler Efficiency (%)": 90, + "Energy Content, Net Calorific value (kWh/unit)": 1.00, "Price per kWh (p) (inc boiler efficiency)": 7.01, + "CO2eq emission factor kgCO2eq/kWh (Gross CV)": 0.213}, + {"Fuel": "Kerosene", "Price (p)": 62.49, "Unit": "Litre", "Boiler Efficiency (%)": 90, + "Energy Content, Net Calorific value (kWh/unit)": 9.79, "Price per kWh (p) (inc boiler efficiency)": 7.09, + "CO2eq emission factor kgCO2eq/kWh (Gross CV)": 0.298}, + {"Fuel": "Gas oil", "Price (p)": 94.50, "Unit": "Litre", "Boiler Efficiency (%)": 90, + "Energy Content, Net Calorific value (kWh/unit)": 9.96, "Price per kWh (p) (inc boiler efficiency)": 10.54, + "CO2eq emission factor kgCO2eq/kWh (Gross CV)": 0.316}, + {"Fuel": "LPG", "Price (p)": 55.00, "Unit": "Litre", "Boiler Efficiency (%)": 90, + "Energy Content, Net Calorific value (kWh/unit)": 6.78, "Price per kWh (p) (inc boiler efficiency)": 9.01, + "CO2eq emission factor kgCO2eq/kWh (Gross CV)": 0.240}, + {"Fuel": "Butane", "Price (p)": 216.58, "Unit": "Litre", "Boiler Efficiency (%)": 90, + "Energy Content, Net Calorific value (kWh/unit)": 6.64, "Price per kWh (p) (inc boiler efficiency)": 36.24, + "CO2eq emission factor kgCO2eq/kWh (Gross CV)": 0.248}, + {"Fuel": "Propane", "Price (p)": 157.67, "Unit": "Litre", "Boiler Efficiency (%)": 90, + "Energy Content, Net Calorific value (kWh/unit)": 7.22, "Price per kWh (p) (inc boiler efficiency)": 24.25, + "CO2eq emission factor kgCO2eq/kWh (Gross CV)": 0.239}, + {"Fuel": "Kiln Dried (logs)", "Price (p)": 36.52, "Unit": "kg", "Boiler Efficiency (%)": 85, + "Energy Content, Net Calorific value (kWh/unit)": 4.09, "Price per kWh (p) (inc boiler efficiency)": 10.51, + "CO2eq emission factor kgCO2eq/kWh (Gross CV)": 0.024}, + {"Fuel": "Pellets (Bagged)", "Price (p)": 39.62, "Unit": "kg", "Boiler Efficiency (%)": 90, + "Energy Content, Net Calorific value (kWh/unit)": 4.80, "Price per kWh (p) (inc boiler efficiency)": 9.17, + "CO2eq emission factor kgCO2eq/kWh (Gross CV)": 0.049}, + {"Fuel": "Pellets (Blown bulk)", "Price (p)": 33.92, "Unit": "kg", "Boiler Efficiency (%)": 90, + "Energy Content, Net Calorific value (kWh/unit)": 4.80, "Price per kWh (p) (inc boiler efficiency)": 7.85, + "CO2eq emission factor kgCO2eq/kWh (Gross CV)": 0.049}, + {"Fuel": "Smokeless fuel", "Price (p)": 67.26, "Unit": "kg", "Boiler Efficiency (%)": 75, + "Energy Content, Net Calorific value (kWh/unit)": 6.70, "Price per kWh (p) (inc boiler efficiency)": 13.38, + "CO2eq emission factor kgCO2eq/kWh (Gross CV)": 0.404}, + {"Fuel": "Coal", "Price (p)": 48.50, "Unit": "kg", "Boiler Efficiency (%)": 75, + "Energy Content, Net Calorific value (kWh/unit)": 7.95, "Price per kWh (p) (inc boiler efficiency)": 8.13, + "CO2eq emission factor kgCO2eq/kWh (Gross CV)": 0.404}, + {"Fuel": "GSHP", "Price (p)": 28.58, "Unit": "kWh", "Boiler Efficiency (%)": 350, + "Energy Content, Net Calorific value (kWh/unit)": 1.00, "Price per kWh (p) (inc boiler efficiency)": 8.17, + "CO2eq emission factor kgCO2eq/kWh (Gross CV)": 0.079}, + {"Fuel": "ASHP", "Price (p)": 28.58, "Unit": "kWh", "Boiler Efficiency (%)": 294, + "Energy Content, Net Calorific value (kWh/unit)": 1.00, "Price per kWh (p) (inc boiler efficiency)": 9.72, + "CO2eq emission factor kgCO2eq/kWh (Gross CV)": 0.094} + ]) + EPC_BANDS = ["G", "F", "E", "D", "C", "B", "A"] @classmethod @@ -200,6 +248,15 @@ class AnnualBillSavings: return cls.EPC_BANDS[expected_index - 1] + @staticmethod + def cost_per_kwh(price_per_unit, energy_content_per_unit): + """ + Calculate the cost of fuel per kWh given the price per unit in GBP and the energy content per unit in kWh. + """ + cost_per_kwh = price_per_unit / energy_content_per_unit + # Tgis data is returned in pennies so we convert to pounds + return cost_per_kwh / 100 + @classmethod def calculate_recommendation_fuel_cost(cls, kwh, fuel, cop): if fuel == "Electricity": @@ -207,3 +264,22 @@ class AnnualBillSavings: if fuel == "Natural Gas": return (kwh / cop) * cls.GAS_PRICE_CAP + + if fuel == "LPG": + # Get the cost per kwh + price_data = cls.FUEL_DATA[cls.FUEL_DATA["Fuel"] == "LPG"].squeeze() + cost_per_kwh = cls.cost_per_kwh( + price_data["Price (p)"], price_data["Energy Content, Net Calorific value (kWh/unit)"] + ) + + return (kwh / cop) * cost_per_kwh + + if fuel == "Wood": + price_data = cls.FUEL_DATA[cls.FUEL_DATA["Fuel"] == "Pellets (Bagged)"].squeeze() + cost_per_kwh = cls.cost_per_kwh( + price_data["Price (p)"], price_data["Energy Content, Net Calorific value (kWh/unit)"] + ) + + return (kwh / cop) * cost_per_kwh + + raise Exception("Fuel not recognised") diff --git a/recommendations/Recommendations.py b/recommendations/Recommendations.py index 0b9e4c7a..93abdcae 100644 --- a/recommendations/Recommendations.py +++ b/recommendations/Recommendations.py @@ -27,6 +27,8 @@ DESCRIPTIONS_TO_FUEL_TYPES = { "Room heaters, electric": {"fuel": 'Electricity', "cop": 1}, "Electric immersion, standard tariff": {"fuel": 'Electricity', "cop": 1}, "Portable electric heaters assumed for most rooms": {"fuel": 'Electricity', "cop": 1}, + "Boiler and radiators, LPG": {"fuel": 'LPG', "cop": 0.9}, + "Room heaters, dual fuel (mineral and wood)": {"fuel": 'Wood', "cop": 1}, } STARTING_DUMMY_ID_VALUE = -9999 @@ -516,7 +518,7 @@ class Recommendations: mapped = DESCRIPTIONS_TO_FUEL_TYPES[heating_description] heating_fuel = mapped["fuel"] - if hotwater_description == "From main system": + if hotwater_description in ["From main system", "From main system, no cylinder thermostat"]: return { "heating_fuel_type": heating_fuel, "hotwater_fuel_type": heating_fuel, "heating_cop": mapped["cop"], "hotwater_cop": mapped["cop"]