fleshing out prs eligibility, adding social

This commit is contained in:
Khalim Conn-Kowlessar 2025-08-01 18:34:27 +01:00
parent 6c6a44abfe
commit c970cc81ca
2 changed files with 205 additions and 522 deletions

View file

@ -6,431 +6,26 @@ from typing import List
from backend.app.plan.schemas import HousingType
class FundingOld:
"""
Given a property, this class identifies if the home is possibly eligible for funding under
the various funding schemes. It will also calculate the expected amount of funding available
and flag any tenant specific requirements that need to be considered to the funding to be attained
"""
SCHEMES = ["eco4", "gbis", "whlg"]
ECO_SAP_SCORE_THREHOLDS = [
{'Band': 'High_A', 'From': 96.0, 'Up to': 100.0, 'Mid-point': 98.0},
{'Band': 'Low_A', 'From': 92.0, 'Up to': 96.0, 'Mid-point': 94.0},
{'Band': 'High_B', 'From': 86.0, 'Up to': 91.0, 'Mid-point': 88.5},
{'Band': 'Low_B', 'From': 81.0, 'Up to': 86.0, 'Mid-point': 83.5},
{'Band': 'High_C', 'From': 74.5, 'Up to': 80.0, 'Mid-point': 77.25},
{'Band': 'Low_C', 'From': 69.0, 'Up to': 74.5, 'Mid-point': 71.75},
{'Band': 'High_D', 'From': 61.5, 'Up to': 68.0, 'Mid-point': 64.75},
{'Band': 'Low_D', 'From': 55.0, 'Up to': 61.5, 'Mid-point': 58.25},
{'Band': 'High_E', 'From': 46.5, 'Up to': 54.0, 'Mid-point': 50.25},
{'Band': 'Low_E', 'From': 39.0, 'Up to': 46.5, 'Mid-point': 42.75},
{'Band': 'High_F', 'From': 29.5, 'Up to': 38.0, 'Mid-point': 33.75},
{'Band': 'Low_F', 'From': 21.0, 'Up to': 29.5, 'Mid-point': 25.25},
{'Band': 'High_G', 'From': 10.5, 'Up to': 20.0, 'Mid-point': 15.25},
{'Band': 'Low_G', 'From': 1.0, 'Up to': 10.5, 'Mid-point': 5.75}
]
def __init__(
self,
tenure: HousingType,
starting_epc,
starting_sap,
postcode,
floor_area,
council_tax_band,
property_recommendations,
project_scores_matrix,
whlg_eligible_postcodes,
gbis_abs_rate: int,
eco4_abs_rate: int,
):
"""
Use Pydantic to validate the parameter types
:param tenure: Indicates if the property is a social or private home
:param starting_epc: The current EPC rating of the property
:param starting_sap: The current SAP score for the property
:param floor_area: The total floor area of the property
:param council_tax_band: The council tax band of the property
:param property_recommendations: The recommendations for the property
:param project_scores_matrix: The matrix of project scores for ECO4
:param whlg_eligible_postcodes: The postcodes eligible for WHLG
:param gbis_abs_rate: The assumed £/abs achieved by the installer for GBIS
:param eco4_abs_rate: The assumed £/abs achieved by the installer for ECO4
"""
# TODO: Things we need to include:
# 1) Amount of funding
# 2) Fundable measures, as a subset of measures may be fundable, not all
self.tenure = tenure
self.starting_epc = starting_epc
self.starting_sap = starting_sap
self.postcode = postcode
self.starting_eco_band = self.sap_to_eco_band(self.starting_sap)
self.floor_area_segment = self.classify_floor_area(floor_area)
self.gbis_abs_rate = gbis_abs_rate
self.eco4_abs_rate = eco4_abs_rate
self.council_tax_band = council_tax_band
self.recommendations = property_recommendations
self.measure_types = list({r["measure_type"] for r in property_recommendations if r["default"]})
# Load in the eco4 project scores matrix
# Filter the matrix on scores relevant to this property
self.project_scores_matrix = project_scores_matrix[
(project_scores_matrix["Floor Area Segment"] == self.floor_area_segment) &
(project_scores_matrix["Starting Band"] == self.starting_eco_band)
]
# The postcode column is already lower case
self.whlg_eligible_postcodes = whlg_eligible_postcodes[
whlg_eligible_postcodes["Postcode"] == self.postcode.lower()
]
# Store the final outputs
self.gbis_eligibiltiy = {}
self.eco4_eligibility = {}
self.whlg_eligibility = {}
def output(
self,
scheme: str,
eligible: bool,
types: List[str],
measure_types: List[str],
project_score: float,
estimated_funding: float,
notify_tenant_benefits_requirements: bool,
notify_council_tax_band_requirements: bool,
notify_tenant_low_income_requirements: bool,
innovation_required: bool,
):
""""
"""
if scheme not in self.SCHEMES:
raise ValueError("Scheme not recognised")
return {
"scheme": scheme,
"eligible": eligible,
"type": types,
"measure_types": measure_types,
"project_score": project_score,
"estimated_funding": estimated_funding,
"requires_benefits": notify_tenant_benefits_requirements,
"requires_council_tax_band": notify_council_tax_band_requirements,
"requires_low_income": notify_tenant_low_income_requirements,
"innovation_required": innovation_required,
}
@staticmethod
def classify_floor_area(floor_area):
if floor_area <= 72:
return "0-72"
if floor_area <= 97:
return "73-97"
if floor_area <= 199:
return "98-199"
return "200"
def eco4(self):
"""
Checks if a property is eligible for ECO4
:return:
"""
pass
def find_gbis_measures(self, measures):
"""
The best measure is one that:
1) Creates some SAP movement, therefore enables eligiblity
2) Generates the most funding
3) Has a reasonable ROI
:return:
"""
measure_table = pd.DataFrame([
m for m in self.recommendations if
(m["type"] in measures) or (m["measure_type"] in measures) and m["default"]
])
measure_table["post_install_sap"] = measure_table["sap_points"] + self.starting_sap
# We classify the movement
measure_table["Finishing Band"] = np.floor(measure_table["post_install_sap"]).apply(
lambda points: self.sap_to_eco_band(points)
)
# Remove any measures that generate zero SAP movement
measure_table = measure_table[measure_table["Finishing Band"] != self.starting_eco_band]
if measure_table.empty:
raise NotImplementedError("No measures available, handle me!")
# We merge on the project matrix, on post install band
measure_table = measure_table.merge(
self.project_scores_matrix, how="left", on="Finishing Band"
)
# Cost Savings is the abs
measure_table["estimated_funding"] = measure_table["Cost Savings"] * self.gbis_abs_rate
# We cap any estimated funding at the install cost
measure_table["estimated_funding"] = np.where(
measure_table["estimated_funding"] >= measure_table["total"],
measure_table["total"],
measure_table["estimated_funding"]
)
# Sort by the measure that will cost the client the least, per sap point
measure_table["cost_minus_funding"] = measure_table["total"] - measure_table["estimated_funding"]
measure_table["cost_minus_funding_per_sap"] = measure_table["cost_minus_funding"] / measure_table["sap_points"]
measure_table = measure_table.sort_values(["cost_minus_funding_per_sap", "total"], ascending=[True, False])
return measure_table[
["type", "measure_type", "Cost Savings", "estimated_funding"]
].rename(columns={"Cost Savings": "project_score"}).to_dict("records")
def sap_to_eco_band(self, sap_points):
"""
Giuven a sap point score, this function will classify the points into the SAP half-band
:param sap_points:
:return:
"""
if sap_points > 100:
return "High_A"
classification = [
x for x in self.ECO_SAP_SCORE_THREHOLDS if (x["From"] <= sap_points) and (sap_points <= x["Up to"])
]
if len(classification) != 1:
raise Exception("We should have a single classifcation for SAP points to half band")
return classification[0]['Band']
def gbis_prs(self):
"""
Checks if a private rental is eligible for GBIS. There are the following possible options
1) General Eligibilty, contigent on EPC D-G and council tax band A-D. Excludes CWI, LI and heating
controls
2) Low income group - contigent on EPC D-G and tenant must receive benefits. Excludes heating controls
3) GBIS Flex route 1, 3 - Great British Insulation Scheme Routes 1 and 3 are for pre-installation
SAP bands D-G for owner-occupied households, D-E for private rented sector households
(Including F & G if exempt from MEES). If houseold is low income. Excludes heating controls
4) GBIS Flex route 2 - EPC E - G and low income household. Excludes heating controls
Eligible measures:
Solid wall
pitched roof
flat roof
under floor
solid floor park home and
room in-roof insulation
:return:
"""
valid_measures = [
"internal_wall_insulation",
"external_wall_insulation",
"flat_roof_insulation",
"suspended_floor_insulation",
"room_roof_insulation",
# Not available for every eligiblity type
"cavity_wall_insulation",
"loft_insulation",
]
# General Eligibility
if (
(self.starting_epc in ["G", "D", "E", "F"]) and
any(
[measure in valid_measures for measure in self.measure_types
if measure not in ["cavity_wall_insulation", "loft_insulation"]]
) and
(self.council_tax_band in [None, "A", "B", "C", "D"])
):
# This function pulls out the various measures that can provide funding under GBIS
recommended_measures = self.find_gbis_measures(
measures=[m for m in valid_measures if m not in ["cavity_wall_insulation", "loft_insulation"]]
)
# If the council tax band is missing, we nofify the customer that this is a requirement that
# should be checked
return [
self.output(
scheme="gbis",
eligible=True,
types=[m["type"]], # This is single measure so we only have one type
measure_types=[m["measure_type"]],
project_score=m["project_score"],
estimated_funding=m["estimated_funding"],
notify_tenant_benefits_requirements=False,
notify_council_tax_band_requirements=self.council_tax_band is None,
notify_tenant_low_income_requirements=False,
innovation_required=False
) for m in recommended_measures
]
# Low income/flex
if (
(self.starting_sap in ["G", "D", "E", "F"]) and
any([measure in valid_measures for measure in self.measure_types])
):
# Find the best measure, and can also include CWI/LI but requires the tenant to be
# low inome or on benefits
# We find the best measure for GBIS
recommended_measures = self.find_gbis_measures(measures=valid_measures)
return [
self.output(
scheme="gbis",
eligible=True,
types=[m["type"]], # This is single measure so we only have one type
measure_types=[m["measure_type"]],
project_score=m["project_score"],
estimated_funding=m["estimated_funding"],
notify_tenant_benefits_requirements=True,
notify_council_tax_band_requirements=False,
notify_tenant_low_income_requirements=True,
innovation_required=False
) for m in recommended_measures
]
# Otherwise, no funding availability
return []
def gbis_social(self):
"""
Because this is social housing, we have two typical means for eligibility
1) EPC D, where an innovation measure is required
2) EPC G-E, where an innovation measure isn't required
:return:
"""
valid_measures = [
"internal_wall_insulation",
"external_wall_insulation",
"flat_roof_insulation",
"suspended_floor_insulation",
"room_roof_insulation",
# Not available for every eligiblity type
"cavity_wall_insulation",
"loft_insulation",
"heating_control"
]
recommended_measures = self.find_gbis_measures(
measures=valid_measures
)
# All measures are available
if self.starting_sap == "D":
return [
self.output(
scheme="gbis",
eligible=True,
types=[m["type"]], # This is single measure so we only have one type
measure_types=[m["measure_type"]],
project_score=m["project_score"],
estimated_funding=m["estimated_funding"],
notify_tenant_benefits_requirements=False,
notify_council_tax_band_requirements=False,
notify_tenant_low_income_requirements=False,
innovation_required=True
) for m in recommended_measures
]
if self.starting_sap in ["G", "F", "E"]:
return [
self.output(
scheme="gbis",
eligible=True,
types=[m["type"]], # This is single measure so we only have one type
measure_types=[m["measure_type"]],
project_score=m["project_score"],
estimated_funding=m["estimated_funding"],
notify_tenant_benefits_requirements=False,
notify_council_tax_band_requirements=False,
notify_tenant_low_income_requirements=False,
innovation_required=False
) for m in recommended_measures
]
return []
def gbis(self):
"""
Check if a property is eligible for GBIS
:return:
"""
if self.tenure == "Private":
self.gbis_eligibiltiy = self.gbis_prs()
return
if self.tenure == "Social":
self.gbis_eligibiltiy = self.gbis_social()
raise NotImplementedError("Implement social/oo")
def whlg(self):
if self.tenure == "Social":
# We can't do anything for social housing
self.whlg_eligibility = []
return
if not self.whlg_eligible_postcodes.empty:
raise Exception("Implement me")
# self.whlg_eligibility = [
# self.output(
# scheme,
# eligible,
# types,
# measure_types,
# project_score: float,
# estimated_funding: float,
# notify_tenant_benefits_requirements: bool,
# notify_council_tax_band_requirements: bool,
# notify_tenant_low_income_requirements: bool,
# innovation_required: bool,
# )
# ]
def eco4(self):
if self.tenure == "Private":
self.eco4_eligibiltiy = self.eco4_prs()
return
def check_eligibiltiy(self):
"""
This function instigates the checking process
:return:
"""
self.gbis()
# self.eco4()
self.whlg()
class EligibilityCaveats(Enum):
TENANT_ON_BENEFITS_OR_LOW_INCOME = "tenant_on_benefits_or_low_income"
INNOVATION_REQUIRED = "innovation_required"
SOLAR_NEEDS_HEATING = "solar_needs_heating"
class Funding:
"""
New class to handle funding calculation
Handles eligibility and funding calculations for ECO4 & GBIS (PRS + Social Housing).
"""
def __init__(
self,
tenure: HousingType,
tenure: str, # 'Private' or 'Social'
social_cavity_abs_rate: float,
social_solid_abs_rate: float,
private_cavity_abs_rate: float,
private_solid_abs_rate: float,
project_scores_matrix,
partial_project_scores_matrix,
whlg_eligible_postcodes
):
self.tenure = tenure
@ -443,10 +38,17 @@ class Funding:
self.ending_sap_band = None
self.floor_area_band = None
self.project_scores_matrix = project_scores_matrix
self.partial_project_scores_matrix = partial_project_scores_matrix
self.whlg_eligible_postcodes = whlg_eligible_postcodes
self.eco4_eligible = False
self.eligbility_caveat = None
self.eco4_eligibility_caveats = []
self.gbis_eligible = False
self.gbis_eligibility_caveats = []
# -----------------------
# Utility Helpers
# -----------------------
@staticmethod
def get_sap_band(sap_score_number):
@ -466,151 +68,219 @@ class Funding:
("High_G", 10.5, 21),
("Low_G", 1, 10.5),
]
for band, lower, upper in bands:
if lower <= sap_score_number < upper:
return band
return None
@staticmethod
def get_floor_area_band(floor_area):
if floor_area <= 72:
return "0-72"
if floor_area <= 97:
return "73-97"
if floor_area <= 199:
return "98-199"
return "200"
def eco4_prs_eligibility(
self, starting_sap: int, measures: List, mainheat_description: str, heating_control_description: str
):
def _split_measures(self, measures: List[dict]):
"""
Handles the eligibility criteria for private rental properties under eco
:return:
Extracts measure types and flags innovation.
measures: list of dicts like {"type": "solar_pv", "is_innovation": True}
"""
measure_types = [m["type"] for m in measures]
has_innovation = any(m.get("is_innovation", False) for m in measures)
innovation_measures = [m["type"] for m in measures if m.get("is_innovation", False)]
return measure_types, has_innovation, innovation_measures
# Help to heat group
# 1) EPC E - G
# 2) Must receive one of SWI, FTCH, renewable heating or DHC
# 3) Tenant must be on benefits
# -----------------------
# Private Rented Sector
# -----------------------
# We don't consider the tenant being on benefits - we just notify the end user that this is a requirement
def eco4_prs_eligibility(self, starting_sap: int, measure_types: List, mainheat_description: str,
heating_control_description: str):
"""
ECO4 PRS eligibility:
- EPC EG
- Must include SWI, FTCH, renewable heating, or DHC
- Tenant must be on benefits (flagged)
"""
meets_epc = starting_sap <= 54 # EPC EG
meets_epc = starting_sap <= 54
has_solid_wall = "internal_wall_insulation" in measures or "external_wall_insulation" in measures
# We check if the property has a heating system that means solar pv counts as a renewable heating system
has_swi = "internal_wall_insulation" in measure_types or "external_wall_insulation" in measure_types
has_renewable = "air_source_heat_pump" in measure_types or "ground_source_heat_pump" in measure_types
has_ftch = "first_time_central_heating" in measure_types
has_dhc = "district_heating_connection" in measure_types
has_eligible_electric_heating = any(x in mainheat_description for x in [
"air source heat pump", "ground source heat pump", "boiler and radiators, electric"
]) | (("electric storage heaters" in mainheat_description) and
(heating_control_description.lower() == "controls for high heat retention storage heaters")
)
]) or (
("electric storage heaters" in mainheat_description)
and (
heating_control_description.lower() == "controls for high heat "
"retention storage heaters")
)
# Counts as renewable heating
solar_renweable_heating = has_eligible_electric_heating & ("solar_pv" in measures)
# Is a renewable heating
ashp = "air_source_heat_pump" in measures
solar_counts_as_renewable = has_eligible_electric_heating and "solar_pv" in measure_types
# Meets the EPC criteria, has the measure requirement and tenant must be on benefits
if meets_epc & (solar_renweable_heating or ashp or has_solid_wall):
if meets_epc and (has_swi or has_renewable or has_ftch or has_dhc or solar_counts_as_renewable):
self.eco4_eligible = True
self.eligbility_caveat = EligibilityCaveats.TENANT_ON_BENEFITS_OR_LOW_INCOME
self.eco4_eligibility_caveats.append(EligibilityCaveats.TENANT_ON_BENEFITS_OR_LOW_INCOME)
def gbis_prs_eligibility(self, starting_sap: int, council_tax_band: str, measure_types: List):
"""
GBIS PRS eligibility:
- General route: Council Tax Band & EPC DG
- Low-income route: tenant on benefits (flagged)
"""
gbis_measures = {
"general": [
"internal_wall_insulation", "external_wall_insulation",
"flat_roof_insulation", "suspended_floor_insulation",
"room_roof_insulation", "solid_floor_insulation", "park_home_insulation"
],
"low_income": [
"internal_wall_insulation", "external_wall_insulation",
"flat_roof_insulation", "suspended_floor_insulation",
"room_roof_insulation", "solid_floor_insulation",
"cavity_wall_insulation", "loft_insulation", "park_home_insulation"
]
}
meets_epc = starting_sap <= 69 # EPC DG
# General route
if meets_epc and council_tax_band in ["A", "B", "C", "D", "E"]:
if any(m in gbis_measures["general"] for m in measure_types):
self.gbis_eligible = True
# Low-income route
if meets_epc and any(m in gbis_measures["low_income"] for m in measure_types):
self.gbis_eligible = True
self.gbis_eligibility_caveats.append(EligibilityCaveats.TENANT_ON_BENEFITS_OR_LOW_INCOME)
# -----------------------
# Social Housing
# -----------------------
def eco4_sh_eligibility(self, starting_sap: int, measure_types: List, has_innovation: bool,
innovation_measures: List):
"""
ECO4 Social Housing eligibility.
- EPC EG: eligible, no income check.
- EPC D: innovation measure required.
If solar PV is the innovation measure, must also install ASHP or HHRSH.
"""
meets_epc = starting_sap <= 69
if not meets_epc:
return
return False
# EPC D innovation rule
if 55 <= starting_sap <= 68: # EPC D
if not has_innovation:
self.eco4_eligible = False
self.eco4_eligibility_caveats.append(EligibilityCaveats.INNOVATION_REQUIRED)
return
def gbis_prs_eligibiltiy(self):
if "solar_pv" in innovation_measures and not any(
m in measure_types for m in ["air_source_heat_pump", "high_heat_retention_storage_heater"]
):
self.eco4_eligible = False
self.eco4_eligibility_caveats.append(EligibilityCaveats.SOLAR_NEEDS_HEATING)
return
self.eco4_eligible = True
def gbis_sh_eligibility(self, starting_sap: int, measure_types: List, has_innovation: bool,
innovation_measures: List):
"""
Determines if a project is eligible for GBIS funding for private rental properties
GBIS Social Housing eligibility.
- EPC EG: insulation measures OK.
- EPC D: innovation measure required (same solar PV + heating rule).
"""
meets_epc = starting_sap <= 69
if not meets_epc:
return
if 55 <= starting_sap <= 68: # EPC D
if not has_innovation:
self.gbis_eligible = False
self.gbis_eligibility_caveats.append(EligibilityCaveats.INNOVATION_REQUIRED)
return
if "solar_pv" in innovation_measures and not any(
m in measure_types for m in ["air_source_heat_pump", "high_heat_retention_storage_heater"]
):
self.gbis_eligible = False
self.gbis_eligibility_caveats.append(EligibilityCaveats.SOLAR_NEEDS_HEATING)
return
self.gbis_eligible = True
# -----------------------
# Score Lookup
# -----------------------
def calculate_full_project_abs(self):
# Filter the project scores matrix
"""Look up ABS score for full projects."""
data = self.project_scores_matrix[
(self.project_scores_matrix["Floor Area Segment"] == self.floor_area_band) &
(self.project_scores_matrix["Starting Band"] == self.starting_sap_band) &
(self.project_scores_matrix["Finishing Band"] == self.ending_sap_band)
]
if data.emtpy:
raise ValueError("Missing abs rate, check the project scores matrix")
if data.empty:
raise ValueError("Missing ABS rate, check the project scores matrix")
return data["Cost Savings"].values[0]
# -----------------------
# Main Entry Point
# -----------------------
def check_funding(
self, measures: List,
self, measures: List[dict],
starting_sap: int,
ending_sap: int,
floor_area: float,
mainheat_description: str,
heating_control_description: str,
is_cavity: bool
is_cavity: bool,
council_tax_band: str = None
):
"""
Given a list of measures, this function will check if the package of measures is fundable
:param measures:
:param starting_sap:
:param ending_sap:
:param floor_area:
:param mainheat_description:
:param heating_control_description:
:param is_cavity: Indicates if the property has cavity wall insulation
:return:
Given a list of measures, check ECO4/GBIS eligibility.
"""
# If it's an E or D, should get to an EPC C
if starting_sap >= 55 and ending_sap < 69:
raise NotImplementedError("This property doesn't have sufficient SAP movement")
if starting_sap <= 38 & ending_sap <= 55:
# F or G should get to D
raise NotImplementedError("Implement F or G to D eligibility")
# Normalize measures
measure_types, has_innovation, innovation_measures = self._split_measures(measures)
# Track EPC bands and floor area
self.starting_sap_band = self.get_sap_band(starting_sap)
self.ending_sap_band = self.get_sap_band(ending_sap)
self.floor_area_band = self.get_floor_area_band(floor_area)
########################
# Private
########################
# 1) ECO4
# 2) GBIS
if self.tenure == "Private":
self.eco4_prs_eligibility(
starting_sap=starting_sap,
measures=measures,
mainheat_description=mainheat_description,
heating_control_description=heating_control_description
)
# ECO4 PRS
self.eco4_prs_eligibility(starting_sap, measure_types, mainheat_description, heating_control_description)
# GBIS PRS
self.gbis_prs_eligibility(starting_sap, council_tax_band or "", measure_types)
# Need to implement
# 1) Package has to include an insulation measure
# 2) We should use the funding for the measure that has the largest partial project score
# TODO: check the rules around GBIS eligibility and heating controls
self.gbis_prs_eligibiltiy()
if self.eco4_eligible:
eco4_abs = self.calculate_full_project_abs()
eco4_funding = eco4_abs * (self.private_cavity_abs_rate if is_cavity else self.private_solid_abs_rate)
return {"eco4_funding": eco4_funding}
if not is_eco4_eligible:
return
eco4_abs = self.calculate_full_project_abs()
# We estimate rates now
eco4_funding = (
eco4_abs * self.private_cavity_abs_rate if is_cavity else eco4_abs & self.private_solid_abs_rate
)
elif self.tenure == "Social":
# ECO4 Social
self.eco4_sh_eligibility(starting_sap, measure_types, has_innovation, innovation_measures)
# GBIS Social
self.gbis_sh_eligibility(starting_sap, measure_types, has_innovation, innovation_measures)
########################
# Social
########################
# 1) ECO4
# 2) GBIS
if self.eco4_eligible:
eco4_abs = self.calculate_full_project_abs()
eco4_funding = eco4_abs * (self.social_cavity_abs_rate if is_cavity else self.social_solid_abs_rate)
return {"eco4_funding": eco4_funding}
if self.tenure == "Social":
pass
raise NotImplementedError("Only implemented for Private or Social housing")
else:
raise NotImplementedError("Only 'Private' and 'Social' tenures are supported.")

View file

@ -17,35 +17,48 @@ def get_funding_data():
project_scores_matrix.columns = ['Floor Area Segment', 'Starting Band', 'Finishing Band', 'Cost Savings']
project_scores_matrix["Cost Savings"] = project_scores_matrix["Cost Savings"].astype(float)
partial_project_scores_matrix = read_csv_from_s3(
bucket_name="retrofit-data-dev",
filepath="funding/ECO4_Partial_Project_Scores_Matrix_v6.csv",
)
partial_project_scores_matrix = pd.DataFrame(partial_project_scores_matrix)
partial_project_scores_matrix["Cost Savings"] = partial_project_scores_matrix["Cost Savings"].astype(float)
whlg_eligible_postcodes = read_csv_from_s3(
bucket_name="retrofit-data-dev",
filepath="funding/whlg eligible postcodes.csv",
)
whlg_eligible_postcodes = pd.DataFrame(whlg_eligible_postcodes)
return project_scores_matrix, whlg_eligible_postcodes
return project_scores_matrix, partial_project_scores_matrix, whlg_eligible_postcodes
# class TestFunding:
#
# def test_prs(self):
# eco_project_scores_matrix, whlg_eligible_postcodes = get_funding_data()
# funding = Funding(
# project_scores_matrix=eco_project_scores_matrix,
# whlg_eligible_postcodes=whlg_eligible_postcodes,
# social_cavity_abs_rate=13.5,
# social_solid_abs_rate=17,
# private_cavity_abs_rate=13.5,
# private_solid_abs_rate=17,
# tenure="Private",
# )
#
# measures_1 = ["internal_wall_insulation", "solar_pv"]
# funding.check_funding(
# measures=measures_1,
# starting_sap=54,
# ending_sap=69,
# floor_area=73,
# mainheat_description="Boiler and radiators, mains gas",
# heating_control_description="Programmer, room thermostat and TRVs",
# is_cavity=True
# )
class TestFunding:
def test_prs(self):
fps_matrix, pps_matrix, whlg_eligible_postcodes = get_funding_data()
funding = Funding(
project_scores_matrix=fps_matrix,
partial_project_scores_matrix=pps_matrix,
whlg_eligible_postcodes=whlg_eligible_postcodes,
social_cavity_abs_rate=13.5,
social_solid_abs_rate=17,
private_cavity_abs_rate=13.5,
private_solid_abs_rate=17,
tenure="Private",
)
measures_1 = [
{"type": "internal_wall_insulation", "is_innovation": False},
{"type": "solar_pv", "is_innovation": True},
]
funding.check_funding(
measures=measures_1,
starting_sap=54,
ending_sap=69,
floor_area=73,
mainheat_description="Boiler and radiators, mains gas",
heating_control_description="Programmer, room thermostat and TRVs",
is_cavity=True
)