mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
debugging funding optimiser for existing gshp - remove ashp and hhrsh recommendations when gshp in place
This commit is contained in:
parent
bdc5147663
commit
2aecf27900
4 changed files with 269 additions and 6 deletions
|
|
@ -1,11 +1,14 @@
|
|||
from enum import Enum
|
||||
from typing import List
|
||||
import pandas as pd
|
||||
from utils.logger import setup_logger
|
||||
|
||||
from etl.epc_clean.epc_attributes.MainheatAttributes import MainHeatAttributes
|
||||
from backend.app.plan.schemas import VALID_HOUSING_TYPES, WALL_INSULATION_MEASURES, ROOF_INSULATION_MEASURES, \
|
||||
MEASURE_MAP
|
||||
|
||||
logger = setup_logger(__name__)
|
||||
|
||||
|
||||
class EligibilityCaveats(Enum):
|
||||
EPC_RATING = "epc_rating" # EPC requirements not met
|
||||
|
|
@ -637,13 +640,25 @@ class Funding:
|
|||
if self.starting_sap_band in ["Low_C", "High_C", "Low_B", "High_B", "Low_A", "High_A"]:
|
||||
return 0
|
||||
|
||||
pps = filtered_pps_matrix[
|
||||
(filtered_pps_matrix["Pre_Main_Heating_Source"] == pre_heating_system) &
|
||||
(filtered_pps_matrix["Post_Main_Heating_Source"] == "Air to Water ASHP") &
|
||||
(filtered_pps_matrix["Measure_Type"] == "B_Upgrade_nopreHCs")
|
||||
pps_data = filtered_pps_matrix[
|
||||
filtered_pps_matrix["Post_Main_Heating_Source"] == "Air to Water ASHP"
|
||||
]
|
||||
|
||||
if pre_heating_system not in pps_data["Pre_Main_Heating_Source"].values:
|
||||
logger.info(
|
||||
f"No PPS data for ASHP upgrade from {pre_heating_system}, returning 0"
|
||||
)
|
||||
return 0
|
||||
|
||||
pps = pps_data[
|
||||
(pps_data["Pre_Main_Heating_Source"] == pre_heating_system) &
|
||||
(pps_data["Measure_Type"] == "B_Upgrade_nopreHCs")
|
||||
# We assume we'll be making a heating system upgrade
|
||||
]
|
||||
|
||||
# Not every pre heating system will result in PPS, e.g. a ground source heat pump to ASHP upgrade
|
||||
# won't have a PPS.
|
||||
|
||||
if pps.shape[0] != 1:
|
||||
raise ValueError("something went wrong, more than one pps for ashp")
|
||||
return pps.squeeze()["Cost Savings"]
|
||||
|
|
|
|||
163
backend/app/db/models/inspections.py
Normal file
163
backend/app/db/models/inspections.py
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
import enum
|
||||
import pytz
|
||||
import datetime
|
||||
from sqlalchemy import (
|
||||
Column,
|
||||
BigInteger,
|
||||
Text,
|
||||
DateTime,
|
||||
Enum,
|
||||
ForeignKey,
|
||||
)
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# ENUM DEFINITIONS (equivalent to drizzle pgEnum calls)
|
||||
# -------------------------------------------------------------------
|
||||
|
||||
class InspectionArchetype(enum.Enum):
|
||||
BUNGALOW = "Bungalow"
|
||||
FLAT = "Flat"
|
||||
MAISONETTE = "Maisonette"
|
||||
HOUSE = "House"
|
||||
NON_DOMESTIC = "non-domestic"
|
||||
|
||||
|
||||
class InspectionArchetype2(enum.Enum):
|
||||
DETACHED = "detached"
|
||||
MID_TERRACE = "mid-terrace"
|
||||
ENCLOSED_MID_TERRACE = "enclosed mid-terrace"
|
||||
END_TERRACE = "end-terrace"
|
||||
ENCLOSED_END_TERRACE = "enclosed end-terrace"
|
||||
SEMI_DETACHED = "semi-detached"
|
||||
|
||||
|
||||
class InspectionsWallConstruction(enum.Enum):
|
||||
CAVITY = "cavity"
|
||||
SOLID = "solid"
|
||||
SYSTEM_BUILT = "system built"
|
||||
TIMBER_FRAMED = "timber framed"
|
||||
STEEL_FRAMED = "steel framed"
|
||||
RE_WALLED_CAVITY = "re-walled cavity"
|
||||
MANSARD_PRE_FAB = "mansard pre-fab"
|
||||
MANSARD_EWI = "mansard ewi"
|
||||
MANSARD_RE_WALLED = "mansard re-walled"
|
||||
|
||||
|
||||
class InspectionsWallInsulation(enum.Enum):
|
||||
EMPTY_CAVITY = "empty cavity"
|
||||
FILLED_AT_BUILD = "filled at build"
|
||||
PARTIAL = "partial"
|
||||
RETRO_DRILLED = "retro drilled"
|
||||
EWI = "ewi"
|
||||
IWI = "iwi"
|
||||
SOLID_NON_CAVITY = "solid non-cavity"
|
||||
SYSTEM_BUILT = "system built"
|
||||
TIMBER_FRAMED = "timber framed"
|
||||
STEEL_FRAMED = "steel framed"
|
||||
|
||||
|
||||
class InspectionsInsulationMaterial(enum.Enum):
|
||||
EMPTY_50_90 = "empty 50-90"
|
||||
EMPTY_100_PLUS = "empty 100+"
|
||||
EMPTY_30_40 = "empty 30-40"
|
||||
EMPTY_LESS_THAN_30 = "empty less than 30"
|
||||
LOOSE_FIBRE_WOOL = "loose fibre/wool"
|
||||
EPS_CELO_KING = "eps/celo/king"
|
||||
FIBRE_BATTS_WITH_CAVITY = "fibre batts - with cavity"
|
||||
FIBRE_BATTS_NO_CAVITY = "fibre batts - no cavity"
|
||||
LOOSE_BEAD = "loose bead"
|
||||
GLUED_BEAD = "glued bead"
|
||||
FORMALDEHYDE = "formaldehyde"
|
||||
BUBBLE_WRAP = "bubble wrap"
|
||||
POLY_CHUNKS = "poly chunks"
|
||||
|
||||
|
||||
class InspectionBorescoped(enum.Enum):
|
||||
YES = "yes"
|
||||
NO = "no"
|
||||
REFUSED = "refused"
|
||||
|
||||
|
||||
class InspectionsRoofOrientation(enum.Enum):
|
||||
NORTH = "north"
|
||||
EAST = "east"
|
||||
SOUTH = "south"
|
||||
WEST = "west"
|
||||
NORTH_EAST = "north-east"
|
||||
NORTH_WEST = "north-west"
|
||||
SOUTH_EAST = "south-east"
|
||||
SOUTH_WEST = "south-west"
|
||||
N_S_SPLIT = "n/s split"
|
||||
E_W_SPLIT = "e/w split"
|
||||
NE_SW_SPLIT = "ne/sw split"
|
||||
NW_SE_SPLIT = "nw/se split"
|
||||
FLAT_ROOF = "flat roof"
|
||||
NO_ROOF = "no roof"
|
||||
ROOF_TOO_SMALL = "roof too small"
|
||||
ALREADY_HAS_SOLAR_PV = "already has solar pv"
|
||||
|
||||
|
||||
class InspectionsTileHung(enum.Enum):
|
||||
YES = "yes"
|
||||
NO = "no"
|
||||
FIRST_FLOOR_FLATS_TILE_HUNG = "first floor flats are tile hung"
|
||||
|
||||
|
||||
class InspectionsRendered(enum.Enum):
|
||||
NO_RENDER = "no render"
|
||||
INSUFFICIENT_DPC_SPACE = "rendered with “insufficient” space between dpc and render"
|
||||
SUFFICIENT_DPC_SPACE = "rendered with “sufficient” space between dpc and render"
|
||||
|
||||
|
||||
class InspectionsCladding(enum.Enum):
|
||||
NONE = "none"
|
||||
SUFFICIENT_SPACE = "cladded with “sufficient space to fill the wall”"
|
||||
INSUFFICIENT_SPACE = "cladded with “insufficient space to fill the wall”"
|
||||
|
||||
|
||||
class InspectionsAccessIssues(enum.Enum):
|
||||
SEE_NOTES = "see notes"
|
||||
DAMP_ISSUES = "damp issues"
|
||||
FOLIAGE_ON_WALLS = "foliage on walls"
|
||||
BUSHES_AGAINST_WALL = "bushes against wall"
|
||||
TREES_AROUND_ABOVE = "trees around/anove property"
|
||||
HIGH_RISE = "high rise block flats/maisonettes"
|
||||
CONSERVATORY = "conservatory"
|
||||
LEAN_TO = "lean-to"
|
||||
GARAGE = "garage"
|
||||
EXTENSION = "extension"
|
||||
DECKING = "decking"
|
||||
SHED_AGAINST_WALL = "shed against wall"
|
||||
|
||||
|
||||
class InspectionModel(Base):
|
||||
__tablename__ = "inspections"
|
||||
|
||||
id = Column(BigInteger, primary_key=True, autoincrement=True)
|
||||
property_id = Column(BigInteger, ForeignKey("property.id"), nullable=False)
|
||||
|
||||
archetype = Column(Enum(InspectionArchetype), nullable=True)
|
||||
archetype_2 = Column(Enum(InspectionArchetype2), nullable=True)
|
||||
wall_construction = Column(Enum(InspectionsWallConstruction), nullable=True)
|
||||
insulation = Column(Enum(InspectionsWallInsulation), nullable=True)
|
||||
insulation_material = Column(Enum(InspectionsInsulationMaterial), nullable=True)
|
||||
borescoped = Column(Enum(InspectionBorescoped), nullable=True)
|
||||
roof_orientation = Column(Enum(InspectionsRoofOrientation), nullable=True)
|
||||
tile_hung = Column(Enum(InspectionsTileHung), nullable=True)
|
||||
rendered = Column(Enum(InspectionsRendered), nullable=True)
|
||||
cladding = Column(Enum(InspectionsCladding), nullable=True)
|
||||
access_issues = Column(Enum(InspectionsAccessIssues), nullable=True)
|
||||
|
||||
notes = Column(Text)
|
||||
surveyor_name = Column(Text)
|
||||
|
||||
created_at = Column(
|
||||
DateTime, nullable=False, default=datetime.datetime.now(pytz.utc)
|
||||
)
|
||||
uploaded_at = Column(
|
||||
DateTime, nullable=False, default=datetime.datetime.now(pytz.utc)
|
||||
)
|
||||
|
|
@ -1393,3 +1393,85 @@ def test_private_epc_e_solar_with_heating_and_minimum_insulation_produces_uplift
|
|||
assert funding.eco4_uplift and funding.eco4_uplift > 0
|
||||
# And total funding should include that uplift
|
||||
assert funding.eco4_funding and funding.eco4_funding > 0
|
||||
|
||||
|
||||
def test_existing_gshp_to_ashp():
|
||||
r = {'phase': 3, 'parts': [], 'type': 'heating', 'measure_type': 'air_source_heat_pump',
|
||||
'description': 'Install a 5KW air source heat pump, and upgrade heating controls to Smart Thermostats, '
|
||||
'room sensors and smart radiator valves (time & temperature zone control). Ensure you have a '
|
||||
'single tariff',
|
||||
'starting_u_value': None, 'new_u_value': None, 'sap_points': 7.7, 'already_installed': False,
|
||||
'simulation_config': {'mainheat_energy_eff_ending': 'Good', 'hot_water_energy_eff_ending': 'Average',
|
||||
'has_air_source_heat_pump_ending': True, 'has_ground_source_heat_pump_ending': False,
|
||||
'extra_features_ending': None,
|
||||
'thermostatic_control_ending': 'time and temperature zone control',
|
||||
'switch_system_ending': None, 'multiple_room_thermostats_ending': False,
|
||||
'mainheatc_energy_eff_ending': 'Very Good'},
|
||||
'description_simulation': {'mainheat-description': 'Air source heat pump, radiators, electric',
|
||||
'mainheat-energy-eff': 'Good', 'hot-water-energy-eff': 'Average',
|
||||
'hotwater-description': 'From main system',
|
||||
'mainheatcont-description': 'Time and temperature zone control',
|
||||
'mainheatc-energy-eff': 'Very Good'}, 'total': 13188.996000000001,
|
||||
'contingency': 3145.8150000000005, 'contingency_rate': 0.35, 'vat': 2080.666, 'labour_hours': 44.7,
|
||||
'labour_days': 6.0, 'innovation_rate': 0, 'recommendation_id': '6_phase=3',
|
||||
'efficiency': 13188.996000000001, 'co2_equivalent_savings': 0.4999999999999998,
|
||||
'heat_demand': 53.20000000000002, 'kwh_savings': 801.5000000000005,
|
||||
'energy_cost_savings': 327.31316785714296
|
||||
}
|
||||
|
||||
funding = Funding(
|
||||
project_scores_matrix=mock_project_scores_matrix,
|
||||
partial_project_scores_matrix=mock_partial_scores_matrix,
|
||||
whlg_eligible_postcodes=mock_whlg_postcodes,
|
||||
eco4_social_cavity_abs_rate=13.5,
|
||||
eco4_social_solid_abs_rate=17,
|
||||
eco4_private_cavity_abs_rate=13.5,
|
||||
eco4_private_solid_abs_rate=17,
|
||||
gbis_social_cavity_abs_rate=21,
|
||||
gbis_social_solid_abs_rate=25,
|
||||
gbis_private_cavity_abs_rate=22,
|
||||
gbis_private_solid_abs_rate=28,
|
||||
tenure="Private",
|
||||
)
|
||||
|
||||
(
|
||||
pps, ppf, iu, ups
|
||||
) = funding.get_innovation_uplift(
|
||||
measure=r,
|
||||
starting_sap=62,
|
||||
floor_area=69,
|
||||
is_cavity=True,
|
||||
current_wall_uvalue=0.7,
|
||||
is_partial=False,
|
||||
existing_li_thickness=200,
|
||||
mainheating={
|
||||
'original_description': 'Ground source heat pump, radiators, electric',
|
||||
'clean_description': 'Ground source heat pump, radiators, electric', 'has_radiators': True,
|
||||
'has_fan_coil_units': False, 'has_pipes_in_screed_above_insulation': False,
|
||||
'has_pipes_in_insulated_timber_floor': False, 'has_pipes_in_concrete_slab': False, 'has_boiler': False,
|
||||
'has_air_source_heat_pump': False, 'has_room_heaters': False, 'has_electric_storage_heaters': False,
|
||||
'has_warm_air': False, 'has_electric_underfloor_heating': False, 'has_electric_ceiling_heating': False,
|
||||
'has_community_scheme': False, 'has_ground_source_heat_pump': True, 'has_no_system_present': False,
|
||||
'has_portable_electric_heaters': False, 'has_water_source_heat_pump': False,
|
||||
'has_electric_heat_pump': False, 'has_micro-cogeneration': False, 'has_solar_assisted_heat_pump': False,
|
||||
'has_exhaust_source_heat_pump': False, 'has_community_heat_pump': False, 'has_hot-water-only': False,
|
||||
'has_electric': True, 'has_mains_gas': False, 'has_wood_logs': False, 'has_coal': False, 'has_oil': False,
|
||||
'has_wood_pellets': False, 'has_anthracite': False, 'has_dual_fuel_mineral_and_wood': False,
|
||||
'has_smokeless_fuel': False, 'has_lpg': False, 'has_b30k': False, 'has_mineral_and_wood': False,
|
||||
'has_dual_fuel_appliance': False, 'has_assumed': False, 'has_electricaire': False,
|
||||
'has_assumed_for_most_rooms': False, 'has_underfloor_heating': False
|
||||
},
|
||||
main_fuel={
|
||||
'original_description': 'electricity (not community)',
|
||||
'clean_description': 'Electricity not community', 'fuel_type': 'electricity', 'tariff_type': None,
|
||||
'is_community': False, 'no_individual_heating_or_community_network': False,
|
||||
'complex_fuel_type': None
|
||||
},
|
||||
mainheat_energy_eff="Poor",
|
||||
)
|
||||
|
||||
# All should be zero
|
||||
assert pps == 0
|
||||
assert ppf == 0
|
||||
assert iu == 0
|
||||
assert ups == 0
|
||||
|
|
|
|||
|
|
@ -103,6 +103,7 @@ class HeatingRecommender:
|
|||
self.property.main_heating["has_electric"] or self.property.main_heating["has_electricaire"]
|
||||
)
|
||||
self.has_ashp = self.property.main_heating["has_air_source_heat_pump"]
|
||||
self.has_gshp = self.property.main_heating["has_ground_source_heat_pump"]
|
||||
self.has_room_heaters = (
|
||||
self.property.main_heating["has_room_heaters"] or
|
||||
self.property.main_heating["has_portable_electric_heaters"]
|
||||
|
|
@ -151,8 +152,10 @@ class HeatingRecommender:
|
|||
"underfloor heating" not in self.property.main_heating["clean_description"]
|
||||
)
|
||||
|
||||
# If the property has a ground source heat pump, or air source heat pump, we don't recommend HHRSH
|
||||
|
||||
return (
|
||||
hhr_suitable and (not ashp_only_heating_recommendation) and not self.has_ashp and
|
||||
hhr_suitable and (not ashp_only_heating_recommendation) and not self.has_ashp and not self.has_gshp and
|
||||
("high_heat_retention_storage_heater" in measures)
|
||||
)
|
||||
|
||||
|
|
@ -345,7 +348,7 @@ class HeatingRecommender:
|
|||
if (
|
||||
self.property.is_ashp_valid(measures=measures) and
|
||||
non_invasive_ashp_recommendation["suitable"] and
|
||||
not self.has_ashp
|
||||
not self.has_ashp and not self.has_gshp
|
||||
):
|
||||
self.recommend_air_source_heat_pump(
|
||||
phase=phase,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue