mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
working on slides, debugging recommendations
This commit is contained in:
parent
7e973d7955
commit
6e802b1f58
5 changed files with 240 additions and 15 deletions
|
|
@ -1,6 +1,6 @@
|
|||
# Assumes that the average efficiency of an air source heat pump is 250%, taking the median of the 200-400% range,
|
||||
# which is often quoted as a sensible efficiency range for air source heat pumps.
|
||||
PESSIMISTIC_ASHPY_EFFICIENCY = 200
|
||||
PESSIMISTIC_ASHP_EFFICIENCY = 200
|
||||
AVERAGE_ASHP_EFFICIENCY = 300
|
||||
|
||||
# Conservative estimate of the proportion of electricity that will be consumed, whereas the rest will
|
||||
|
|
|
|||
|
|
@ -269,10 +269,13 @@ def make_asset_list():
|
|||
|
||||
# We add a patch to one of the units because there's no data for the built form
|
||||
# We would be able to handle this automatically in the future, when using OS API
|
||||
patches = [{
|
||||
"uprn": "10033266220",
|
||||
"built-form": "Semi-Detached",
|
||||
}]
|
||||
patches = [
|
||||
{
|
||||
"uprn": "10033266220",
|
||||
"built-form": "Semi-Detached",
|
||||
},
|
||||
{'uprn': '10033266219', 'built-form': 'Semi-Detached'}
|
||||
]
|
||||
|
||||
# Store patches in s3
|
||||
patches_filename = f"{USER_ID}/{PORTFOLIO_ID}/patches.json"
|
||||
|
|
|
|||
214
etl/customers/newhaven/slides.py
Normal file
214
etl/customers/newhaven/slides.py
Normal file
|
|
@ -0,0 +1,214 @@
|
|||
import pandas as pd
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from backend.app.db.connection import db_engine
|
||||
from backend.app.db.models.recommendations import Recommendation, Plan, PlanRecommendations, Scenario
|
||||
from backend.app.db.models.portfolio import PropertyModel, PropertyDetailsEpcModel
|
||||
|
||||
|
||||
def get_data(portfolio_id, scenario_ids):
|
||||
session = sessionmaker(bind=db_engine)()
|
||||
session.begin()
|
||||
|
||||
# Get properties and their details for a specific portfolio
|
||||
properties_query = session.query(
|
||||
PropertyModel,
|
||||
PropertyDetailsEpcModel
|
||||
).join(
|
||||
PropertyDetailsEpcModel, PropertyModel.id == PropertyDetailsEpcModel.property_id
|
||||
).filter(
|
||||
PropertyModel.portfolio_id == portfolio_id # Filter by portfolio ID
|
||||
).all()
|
||||
|
||||
# Transform properties data to include all fields dynamically
|
||||
properties_data = [
|
||||
{**{col.name: getattr(prop.PropertyModel, col.name) for col in PropertyModel.__table__.columns},
|
||||
**{col.name: getattr(prop.PropertyDetailsEpcModel, col.name) for col in
|
||||
PropertyDetailsEpcModel.__table__.columns}}
|
||||
for prop in properties_query
|
||||
]
|
||||
|
||||
# Get property IDs from fetched properties
|
||||
|
||||
# Get plans linked to the fetched properties
|
||||
plans_query = session.query(Plan).filter(Plan.scenario_id.in_(scenario_ids)).all()
|
||||
|
||||
# Transform plans data to include all fields dynamically
|
||||
plans_data = [
|
||||
{col.name: getattr(plan, col.name) for col in Plan.__table__.columns}
|
||||
for plan in plans_query
|
||||
]
|
||||
|
||||
# Extract plan IDs for filtering recommendations through PlanRecommendations
|
||||
plan_ids = [plan['id'] for plan in plans_data]
|
||||
|
||||
# Get recommendations through PlanRecommendations for those plans and that are default
|
||||
recommendations_query = session.query(
|
||||
Recommendation,
|
||||
Plan.scenario_id
|
||||
).join(
|
||||
PlanRecommendations, Recommendation.id == PlanRecommendations.recommendation_id
|
||||
).join(
|
||||
Plan, Plan.id == PlanRecommendations.plan_id # Join with Plan to access scenario_id
|
||||
).filter(
|
||||
PlanRecommendations.plan_id.in_(plan_ids),
|
||||
Recommendation.default == True # Filtering for default recommendations
|
||||
).all()
|
||||
|
||||
# Transform recommendations data to include all fields dynamically and include scenario_id
|
||||
recommendations_data = [
|
||||
{**{col.name: getattr(rec.Recommendation, col.name) if hasattr(rec, 'Recommendation') else getattr(rec,
|
||||
col.name) for
|
||||
col in Recommendation.__table__.columns},
|
||||
"Scenario ID": rec.scenario_id}
|
||||
for rec in recommendations_query
|
||||
]
|
||||
|
||||
session.close()
|
||||
|
||||
return properties_data, plans_data, recommendations_data
|
||||
|
||||
|
||||
def slides():
|
||||
# Prepares the information required for the slides
|
||||
|
||||
# Right now this is the second version of the nehaven portfolio
|
||||
portfolio_id = 90
|
||||
# Look at one scenario at a time, otherwise this is agony
|
||||
scenario_ids = [47, 48, 49]
|
||||
|
||||
properties_data, plans_data, recommendations_data = get_data(portfolio_id, scenario_ids)
|
||||
|
||||
properties_df = pd.DataFrame(properties_data)
|
||||
plans_df = pd.DataFrame(plans_data)
|
||||
recommendations_df = pd.DataFrame(recommendations_data)
|
||||
|
||||
if properties_df.shape[0] != 2553:
|
||||
raise ValueError("The number of unique properties is not 2553")
|
||||
|
||||
def estimate_post_retrofit_heating_hotwater_kwh(recommendations_df, scenario_ids):
|
||||
# Get the recommendations for the scenario, default
|
||||
scenario_comparison_df = []
|
||||
scenario_comparison_df_2 = []
|
||||
for scenario_id in scenario_ids:
|
||||
# Get the recommendations for the scenario, default
|
||||
scenario_recommendations = recommendations_df[
|
||||
(recommendations_df["Scenario ID"] == scenario_id) &
|
||||
(recommendations_df["default"] == True)
|
||||
].copy()
|
||||
|
||||
scenario_recommendations['ligting_kwh'] = scenario_recommendations.apply(
|
||||
lambda x: x['kwh_savings'] if x['type'] == 'low_energy_lighting' else 0,
|
||||
axis=1)
|
||||
scenario_recommendations['solar_kwh'] = scenario_recommendations.apply(
|
||||
lambda x: x['kwh_savings'] if x['type'] == 'solar_pv' else 0, axis=1)
|
||||
|
||||
if scenario_recommendations['solar_kwh'].sum() > 0:
|
||||
blah
|
||||
|
||||
# Set 'Estimated Kwh Savings' to zero where specific kwh columns are used
|
||||
scenario_recommendations['Estimated Kwh Savings'] = scenario_recommendations.apply(
|
||||
lambda x: 0 if x['type'] in ['low_energy_lighting', 'solar_pv'] else x[
|
||||
'kwh_savings'], axis=1)
|
||||
|
||||
grouped_data = scenario_recommendations.groupby(['property_id']).agg({
|
||||
'Estimated Kwh Savings': 'sum',
|
||||
'ligting_kwh': 'sum',
|
||||
'solar_kwh': 'sum'
|
||||
}).reset_index()
|
||||
|
||||
comparison = properties_df.drop_duplicates().merge(
|
||||
grouped_data, on=["property_id"], how="left"
|
||||
)
|
||||
|
||||
comparison["Post Retrofit Heating & Hotwater kwh"] = (
|
||||
comparison["current_energy_demand_heating_hotwater"] - \
|
||||
comparison["Estimated Kwh Savings"]
|
||||
)
|
||||
|
||||
avgs = comparison[['current_energy_demand_heating_hotwater', 'Post Retrofit Heating & Hotwater kwh']].mean()
|
||||
|
||||
# We now, for properties that have a plan, do a before and after
|
||||
with_savings = comparison[~pd.isnull(comparison["Estimated Kwh Savings"])]
|
||||
|
||||
avgs2 = with_savings[
|
||||
['current_energy_demand_heating_hotwater', 'Post Retrofit Heating & Hotwater kwh']].mean()
|
||||
avgs2["difference"] = avgs2["current_energy_demand_heating_hotwater"] - avgs2[
|
||||
"Post Retrofit Heating & Hotwater kwh"]
|
||||
avgs2["percentage_reduction"] = 100 * avgs2["difference"] / avgs2["current_energy_demand_heating_hotwater"]
|
||||
|
||||
scenario_comparison_df.append({"scenario_id": scenario_id, **avgs})
|
||||
scenario_comparison_df_2.append({"scenario_id": scenario_id, **avgs2})
|
||||
|
||||
scenario_comparison_df = pd.DataFrame(scenario_comparison_df)
|
||||
scenario_comparison_df_2 = pd.DataFrame(scenario_comparison_df_2)
|
||||
|
||||
return scenario_comparison_df, scenario_comparison_df_2
|
||||
|
||||
# TODO: How do we factor in solar PV
|
||||
|
||||
# Q1: What is the baseline heating and energy demand for the properties in the portfolio - baseline?
|
||||
heating_hotwater_kwh = (
|
||||
properties_df[['current_energy_demand', 'current_energy_demand_heating_hotwater']]
|
||||
.mean()
|
||||
)
|
||||
|
||||
# Q2: For each scenario, what is the £ per kwh reduction?
|
||||
# Calculate total kwh savings
|
||||
kwh_plan_impact = estimate_post_retrofit_heating_hotwater_kwh(properties_df, recommendations_df)
|
||||
|
||||
z = df[
|
||||
(df["Recommendation Default Status"] == True) &
|
||||
(df["Plan Name"].isin(['Demand Reduction – cavity & roof insulation']))
|
||||
]
|
||||
z2 = z[z["Property ID"] == 25215]
|
||||
# Find duplicated property ID, recommendationt type combos
|
||||
z = z[z.duplicated(subset=["Property ID", "Recommendation Type"])]
|
||||
|
||||
for plan_name in df["Plan Name"].unique():
|
||||
# Get default recs
|
||||
default_recs = df[
|
||||
(df["Recommendation Default Status"] == True) &
|
||||
(df["Plan Name"] == plan_name)
|
||||
].copy()
|
||||
if default_recs["Recommendation ID"].duplicated().sum():
|
||||
raise Exception("somethign went wrong")
|
||||
|
||||
default_recs["Recommendation Type"].unique()
|
||||
|
||||
# We now calculate the total savings
|
||||
total_savings = default_recs["Estimated Kwh Savings"].sum()
|
||||
total_cost = default_recs["Recommendation Cost"].sum()
|
||||
|
||||
kwh_savings = df[
|
||||
df["Recommendation Default Status"] == True
|
||||
].groupby("Plan Name")[["Estimated Kwh Savings", "Recommendation Cost"]].sum().rename(
|
||||
columns={"Estimated Kwh Savings": "Total Kwh Savings", "Recommendation Cost": "Total Cost"}
|
||||
).reset_index()
|
||||
|
||||
kwh_savings["Cost per Kwh Saved"] = kwh_savings["Total Cost"] / kwh_savings["Total Kwh Savings"]
|
||||
|
||||
# Q3: For each scenario, we want to answer what the heating and hot water kwh looks like after retrofit
|
||||
# We need to take recommndations that affect just the heating and hot water
|
||||
|
||||
# By property
|
||||
|
||||
df["Type Mapped"] = df["Recommendation Type"].copy().replace(
|
||||
{
|
||||
"loft_insulation": "roof_insulation",
|
||||
"room_roof_insulation": "roof_insulation",
|
||||
"flat_roof_insulation": "roof_insulation",
|
||||
"hot_water_tank_insulation": "other",
|
||||
"cylinder_thermostat": "other",
|
||||
"sealing_open_fireplace": "other",
|
||||
}
|
||||
)
|
||||
|
||||
# Group by 'Plan Name' and 'Recommendation Type' and count unique 'Property ID'
|
||||
recommendation_summary = df.groupby(['Plan Name', 'Type Mapped']).agg({
|
||||
'Property ID': 'nunique'
|
||||
}).reset_index()
|
||||
|
||||
recommendation_summary.columns = ['Plan Name', 'Type Mapped', 'Number of Properties']
|
||||
recommendation_summary["Percentage of Properties"] = 100 * (
|
||||
recommendation_summary["Number of Properties"] / df["Property ID"].nunique()
|
||||
)
|
||||
|
|
@ -74,7 +74,6 @@ class FloorRecommendations(Definitions):
|
|||
u_value = self.property.floor["thermal_transmittance"]
|
||||
property_type = self.property.data["property-type"]
|
||||
floor_area = self.property.insulation_floor_area
|
||||
year_built = self.property.year_built
|
||||
|
||||
if self.property.floor["another_property_below"] | (self.property.floor["insulation_thickness"] in [
|
||||
"average", "above average"
|
||||
|
|
@ -95,14 +94,16 @@ class FloorRecommendations(Definitions):
|
|||
|
||||
if u_value:
|
||||
|
||||
# By being built more recently than this, it means that the property was likely build with soild
|
||||
# concrete floors with insulation already
|
||||
if year_built < self.PART_L_YEAR_CUTOFF:
|
||||
raise NotImplementedError("Not investigated this use case")
|
||||
|
||||
if u_value <= self.BUILDING_REGULATIONS_PART_L_MAX_U_VALUE:
|
||||
# The floor is already compliant
|
||||
return
|
||||
# In this case where we have the u-value of a floor, we likely don't have any other information about it
|
||||
# so there is no recommendation that we can practically make
|
||||
if (
|
||||
self.property.floor["is_suspended"] or
|
||||
self.property.floor["is_to_unheated_space"] or
|
||||
self.property.floor["is_to_external_air"] or
|
||||
self.property.floor["is_solid"]
|
||||
):
|
||||
raise ValueError("This should not be possible")
|
||||
return
|
||||
|
||||
if u_value is None:
|
||||
u_value = get_floor_u_value(
|
||||
|
|
|
|||
|
|
@ -20,7 +20,9 @@ import backend.app.assumptions as assumptions
|
|||
|
||||
ASHP_COP = 3
|
||||
DESCRIPTIONS_TO_FUEL_TYPES = {
|
||||
"Air source heat pump, radiators, electric": {"fuel": "Electricity", "cop": ASHP_COP},
|
||||
"Air source heat pump, radiators, electric": {
|
||||
"fuel": "Electricity", "cop": assumptions.AVERAGE_ASHP_EFFICIENCY / 100
|
||||
},
|
||||
"Boiler and radiators, mains gas": {"fuel": 'Natural Gas', "cop": 0.9},
|
||||
'Electric storage heaters': {"fuel": 'Electricity', "cop": 1},
|
||||
"Electric immersion, off-peak": {"fuel": 'Electricity', "cop": 1},
|
||||
|
|
@ -46,6 +48,11 @@ DESCRIPTIONS_TO_FUEL_TYPES = {
|
|||
"Boiler and radiators, dual fuel (mineral and wood)": {"fuel": "Wood Logs", "cop": 0.9},
|
||||
"Electric immersion, standard tariff, plus solar": {"fuel": "Electricity + Solar Thermal", "cop": 1},
|
||||
"From main system, flue gas heat recovery": {"fuel": "Natural Gas", "cop": 0.9},
|
||||
"Electric underfloor heating": {"fuel": "Electricity", "cop": 1},
|
||||
"No system present: electric immersion assumed": {"fuel": "Electricity", "cop": 1},
|
||||
"Air source heat pump, underfloor, electric": {
|
||||
"fuel": "Electricity", "cop": assumptions.AVERAGE_ASHP_EFFICIENCY / 100
|
||||
},
|
||||
}
|
||||
STARTING_DUMMY_ID_VALUE = -9999
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue