mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
refactored the handling of dual heating recommendations and fixing coverage of heating types in property class
This commit is contained in:
parent
3624b34dd0
commit
005c6b844a
3 changed files with 140 additions and 60 deletions
|
|
@ -1221,7 +1221,8 @@ class Property:
|
|||
None: "Natural Gas (Community Scheme)",
|
||||
"mains gas": "Natural Gas (Community Scheme)",
|
||||
"biomass": "Smokeless Fuel",
|
||||
"electricity": "Electricity"
|
||||
"electricity": "Electricity",
|
||||
"biogas": "Smokeless Fuel",
|
||||
}
|
||||
if self.main_fuel["fuel_type"] in fuel_map: # We assume when None as it's unknown
|
||||
self.heating_energy_source = fuel_map[self.main_fuel["fuel_type"]]
|
||||
|
|
|
|||
|
|
@ -1,36 +1,36 @@
|
|||
import ast
|
||||
import json
|
||||
# import ast
|
||||
# import json
|
||||
from copy import deepcopy
|
||||
from dataclasses import replace
|
||||
from datetime import datetime
|
||||
# from dataclasses import replace
|
||||
# from datetime import datetime
|
||||
|
||||
import random
|
||||
from tqdm import tqdm
|
||||
import pandas as pd
|
||||
# import pandas as pd
|
||||
import numpy as np
|
||||
from etl.epc.Record import EPCRecord
|
||||
from backend.SearchEpc import SearchEpc
|
||||
from sqlalchemy.exc import IntegrityError, OperationalError
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from starlette.responses import Response
|
||||
# from backend.SearchEpc import SearchEpc
|
||||
# from sqlalchemy.exc import IntegrityError, OperationalError
|
||||
# from sqlalchemy.orm import sessionmaker
|
||||
# from starlette.responses import Response
|
||||
|
||||
from backend.app.config import get_settings, get_prediction_buckets
|
||||
from backend.app.db.connection import db_engine
|
||||
from backend.app.db.functions.materials_functions import get_materials
|
||||
from backend.app.db.functions.portfolio_functions import aggregate_portfolio_recommendations
|
||||
from backend.app.db.functions.property_functions import (
|
||||
create_property, create_property_details_epc, create_property_targets, update_property_data,
|
||||
update_or_create_property_spatial_details
|
||||
)
|
||||
from backend.app.db.functions.recommendations_functions import (
|
||||
create_plan, upload_recommendations, create_scenario
|
||||
)
|
||||
from backend.app.db.functions.funding_functions import upload_funding
|
||||
from backend.app.db.functions.energy_assessment_functions import get_latest_assessment_by_uprn
|
||||
from backend.app.db.models.portfolio import rating_lookup
|
||||
# from backend.app.config import get_settings, get_prediction_buckets
|
||||
# from backend.app.db.connection import db_engine
|
||||
# from backend.app.db.functions.materials_functions import get_materials
|
||||
# from backend.app.db.functions.portfolio_functions import aggregate_portfolio_recommendations
|
||||
# from backend.app.db.functions.property_functions import (
|
||||
# create_property, create_property_details_epc, create_property_targets, update_property_data,
|
||||
# update_or_create_property_spatial_details
|
||||
# )
|
||||
# from backend.app.db.functions.recommendations_functions import (
|
||||
# create_plan, upload_recommendations, create_scenario
|
||||
# )
|
||||
# from backend.app.db.functions.funding_functions import upload_funding
|
||||
# from backend.app.db.functions.energy_assessment_functions import get_latest_assessment_by_uprn
|
||||
# from backend.app.db.models.portfolio import rating_lookup
|
||||
from backend.app.plan.schemas import PlanTriggerRequest, WALL_INSULATION_MEASURES, ROOF_INSULATION_MEASURES
|
||||
from backend.app.plan.utils import get_cleaned
|
||||
from backend.app.utils import sap_to_epc
|
||||
# from backend.app.plan.utils import get_cleaned
|
||||
# from backend.app.utils import sap_to_epc
|
||||
import backend.app.assumptions as assumptions
|
||||
|
||||
from backend.ml_models.api import ModelApi
|
||||
|
|
@ -41,13 +41,13 @@ from recommendations.optimiser.CostOptimiser import CostOptimiser
|
|||
from recommendations.optimiser.GainOptimiser import GainOptimiser
|
||||
import recommendations.optimiser.optimiser_functions as optimiser_functions
|
||||
from recommendations.Recommendations import Recommendations
|
||||
from utils.logger import setup_logger
|
||||
from utils.s3 import read_dataframe_from_s3_parquet, read_csv_from_s3, read_excel_from_s3
|
||||
from backend.ml_models.Valuation import PropertyValuation
|
||||
|
||||
from etl.bill_savings.KwhData import KwhData
|
||||
from etl.spatial.OpenUprnClient import OpenUprnClient
|
||||
from etl.find_my_epc.RetrieveFindMyEpc import RetrieveFindMyEpc
|
||||
# from utils.logger import setup_logger
|
||||
# from utils.s3 import read_dataframe_from_s3_parquet, read_csv_from_s3, read_excel_from_s3
|
||||
# from backend.ml_models.Valuation import PropertyValuation
|
||||
#
|
||||
# from etl.bill_savings.KwhData import KwhData
|
||||
# from etl.spatial.OpenUprnClient import OpenUprnClient
|
||||
# from etl.find_my_epc.RetrieveFindMyEpc import RetrieveFindMyEpc
|
||||
|
||||
from backend.Funding import Funding
|
||||
from recommendations.optimiser.funding_optimiser import optimise_with_funding_paths
|
||||
|
|
@ -91,7 +91,7 @@ costs_by_floor_area = costs_by_floor_area.groupby("current-energy-efficiency")[
|
|||
].mean().reset_index()
|
||||
|
||||
sample_epc_data = epc_data[pd.to_datetime(epc_data["LODGEMENT_DATE"]) >= "2015-01-01"].drop_duplicates("UPRN").sample(
|
||||
20000).reset_index(drop=True)
|
||||
3000).reset_index(drop=True)
|
||||
|
||||
# TODO: In Property find_energy_sources, sort out biomass community heating - what fuel type
|
||||
# TODO: We might be able to remove find_energy_sources entirely and remove estimate_electrical_consumption. It's used
|
||||
|
|
|
|||
|
|
@ -10,6 +10,9 @@ from etl.epc_clean.epc_attributes.MainheatAttributes import MainHeatAttributes
|
|||
from etl.epc_clean.epc_attributes.HotWaterAttributes import HotWaterAttributes
|
||||
from etl.epc_clean.epc_attributes.MainFuelAttributes import MainFuelAttributes
|
||||
from recommendations.HeatingControlRecommender import HeatingControlRecommender
|
||||
from utils.logger import setup_logger
|
||||
|
||||
logger = setup_logger()
|
||||
|
||||
|
||||
class HeatingRecommender:
|
||||
|
|
@ -44,6 +47,22 @@ class HeatingRecommender:
|
|||
]
|
||||
}
|
||||
},
|
||||
"Boiler and radiators, mains gas, electric underfloor heating": {
|
||||
"boiler": {
|
||||
"mainheating_description": "Boiler and radiators, mains gas, electric underfloor heating",
|
||||
"recommendation_description": "Upgrade the existing boiler to a new, more efficient condensing "
|
||||
"boiler. ",
|
||||
"controls_suffix": "Manual charge controls"
|
||||
},
|
||||
# These are the heating types we need to produce a dual heating recommendation
|
||||
"dual": {
|
||||
"recommendation_description": "Upgrade the existing boiler to a new condensing boiler",
|
||||
"types": [
|
||||
# type 1
|
||||
"boiler_upgrade",
|
||||
]
|
||||
}
|
||||
},
|
||||
"Portable electric heaters assumed for most rooms, room heaters, electric": {
|
||||
"hhr": {
|
||||
"mainheating_description": "Electric storage heaters, radiators",
|
||||
|
|
@ -127,7 +146,7 @@ class HeatingRecommender:
|
|||
n_trues += 1
|
||||
|
||||
if n_trues > 2 or n_trues == 0:
|
||||
raise Exception("Implement me")
|
||||
raise NotImplementedError("Implement me, zero or more than two heating systemss")
|
||||
if n_trues == 1:
|
||||
return False
|
||||
|
||||
|
|
@ -917,9 +936,11 @@ class HeatingRecommender:
|
|||
if self.property.main_heating_controls["clean_description"] != self.high_heat_retention_contols_desc:
|
||||
if self.dual_heating:
|
||||
|
||||
controls_prefix = self.DUAL_HEATING_DESCRIPTIONS[
|
||||
self.property.main_heating["clean_description"]
|
||||
]["hhr"]["controls_prefix"]
|
||||
controls_prefix = self._map_dual_heating_description(
|
||||
backup_map_to_description="current_controls",
|
||||
output_type="controls_prefix",
|
||||
recommendation_type="hhr"
|
||||
)
|
||||
|
||||
if controls_prefix == "current_controls":
|
||||
description_prefix = self.property.main_heating_controls["clean_description"]
|
||||
|
|
@ -951,9 +972,11 @@ class HeatingRecommender:
|
|||
|
||||
# We check if the property has dual heating in place with a boiler and storage heaters
|
||||
if self.dual_heating:
|
||||
new_heating_description = self.DUAL_HEATING_DESCRIPTIONS[
|
||||
self.property.main_heating["clean_description"]
|
||||
]["hhr"]["mainheating_description"]
|
||||
new_heating_description = self._map_dual_heating_description(
|
||||
backup_map_to_description="Electric storage heaters",
|
||||
output_type="mainheating_description",
|
||||
recommendation_type="hhr"
|
||||
)
|
||||
new_hot_water_description = self.property.hotwater["clean_description"] # We keep the hot water system
|
||||
else:
|
||||
new_heating_description = "Electric storage heaters"
|
||||
|
|
@ -1010,10 +1033,12 @@ class HeatingRecommender:
|
|||
product=hhrsh_product
|
||||
)
|
||||
if self.dual_heating:
|
||||
description = self.DUAL_HEATING_DESCRIPTIONS[
|
||||
self.property.main_heating["clean_description"]
|
||||
]["hhr"]["recommendation_description"]
|
||||
|
||||
description = self._map_dual_heating_description(
|
||||
backup_map_to_description="Install high heat retention electric storage heaters with an appropriate "
|
||||
"off-peak tariff.",
|
||||
output_type="recommendation_description",
|
||||
recommendation_type="hhr"
|
||||
)
|
||||
else:
|
||||
description = "Install high heat retention electric storage heaters with an appropriate off-peak tariff."
|
||||
|
||||
|
|
@ -1102,6 +1127,60 @@ class HeatingRecommender:
|
|||
|
||||
return max(num_heated_rooms * 1.5, 6)
|
||||
|
||||
def _map_dual_heating_description(
|
||||
self, backup_map_to_description, output_type, recommendation_type
|
||||
):
|
||||
"""
|
||||
Utility function to handle dual heating systems
|
||||
:param backup_map_to_description:
|
||||
:return:
|
||||
"""
|
||||
|
||||
if backup_map_to_description not in [
|
||||
# Recommendation descriptions - these are the textual descriptions shown in the front end
|
||||
"Upgrade to a new condensing boiler.",
|
||||
"Install high heat retention electric storage heaters with an appropriate off-peak tariff.",
|
||||
# Simulation descriptions - this is the new EPC description we simulate with in the case
|
||||
# of single heating
|
||||
"Boiler and radiators, mains gas",
|
||||
"Electric storage heaters",
|
||||
# Suffixes allowed
|
||||
"",
|
||||
# Controls prefixes
|
||||
"current_controls"
|
||||
]:
|
||||
raise ValueError(f"Invalid backup_map_to_description, given {backup_map_to_description}")
|
||||
|
||||
if output_type not in [
|
||||
"recommendation_description",
|
||||
"mainheating_description",
|
||||
"controls_suffix",
|
||||
"controls_prefix",
|
||||
]:
|
||||
raise ValueError(f"Invalid output_type, given {output_type}")
|
||||
|
||||
if recommendation_type not in [
|
||||
"boiler",
|
||||
]:
|
||||
raise ValueError(f"Given invalid recommendation type {recommendation_type}")
|
||||
|
||||
# "Upgrade to a new condensing boiler."
|
||||
if self.dual_heating:
|
||||
|
||||
# We check if we have a mapped description
|
||||
if self.property.main_heating["clean_description"] not in self.DUAL_HEATING_DESCRIPTIONS:
|
||||
logger.warning(
|
||||
f"We have a dual heating system that hasn't been mapped, defaulting to single "
|
||||
f"{self.property.main_heating['clean_description']}"
|
||||
)
|
||||
return backup_map_to_description
|
||||
|
||||
return self.DUAL_HEATING_DESCRIPTIONS[
|
||||
self.property.main_heating["clean_description"]
|
||||
][recommendation_type][output_type]
|
||||
|
||||
return backup_map_to_description
|
||||
|
||||
def recommend_boiler_upgrades(self, phase, system_change, exising_room_heaters):
|
||||
"""
|
||||
This boiler recommendation will only recommend a like-for-like upgrade, since changing the system
|
||||
|
|
@ -1137,12 +1216,11 @@ class HeatingRecommender:
|
|||
|
||||
if has_inefficient_space_heating or has_inefficient_water:
|
||||
|
||||
if self.dual_heating:
|
||||
description = self.DUAL_HEATING_DESCRIPTIONS[
|
||||
self.property.main_heating["clean_description"]
|
||||
]["boiler"]["recommendation_description"]
|
||||
else:
|
||||
description = "Upgrade to a new condensing boiler."
|
||||
description = self._map_dual_heating_description(
|
||||
backup_map_to_description="Upgrade to a new condensing boiler.",
|
||||
output_type="recommendation_description",
|
||||
recommendation_type="boiler"
|
||||
)
|
||||
|
||||
new_heating_eff = (
|
||||
"Good" if self.property.data["mainheat-energy-eff"] in ["Very Poor", "Poor", "Average"]
|
||||
|
|
@ -1167,13 +1245,12 @@ class HeatingRecommender:
|
|||
if system_change:
|
||||
# Installation of a boiler improves the hot water system so we need to reflect this in
|
||||
# the outcome of the recommendation
|
||||
if self.dual_heating:
|
||||
new_heating_description = self.DUAL_HEATING_DESCRIPTIONS[
|
||||
self.property.main_heating["clean_description"]
|
||||
]["boiler"]["mainheating_description"]
|
||||
else:
|
||||
new_heating_description = "Boiler and radiators, mains gas"
|
||||
|
||||
new_heating_description = self._map_dual_heating_description(
|
||||
backup_map_to_description="Boiler and radiators, mains gas",
|
||||
output_type="mainheating_description",
|
||||
recommendation_type="boiler"
|
||||
)
|
||||
new_hotwater_description = "From main system"
|
||||
new_fuel_description = "mains gas (not community)"
|
||||
|
||||
|
|
@ -1239,9 +1316,11 @@ class HeatingRecommender:
|
|||
# If the property did not previously have a boiler, we combine
|
||||
controls_recommender = HeatingControlRecommender(self.property)
|
||||
if self.dual_heating:
|
||||
description_suffix = self.DUAL_HEATING_DESCRIPTIONS[
|
||||
self.property.main_heating["clean_description"]
|
||||
]["boiler"]["controls_suffix"]
|
||||
description_suffix = self._map_dual_heating_description(
|
||||
backup_map_to_description="",
|
||||
output_type="controls_suffix",
|
||||
recommendation_type="boiler"
|
||||
)
|
||||
else:
|
||||
description_suffix = ""
|
||||
controls_recommender.recommend(
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue