added base unit tests

This commit is contained in:
Khalim Conn-Kowlessar 2025-08-01 18:44:49 +01:00
parent c970cc81ca
commit 9a558c5bb5

View file

@ -1,64 +1,278 @@
import pytest import pytest
import pandas as pd import pandas as pd
from utils.s3 import read_csv_from_s3 from backend.Funding import Funding, EligibilityCaveats
from backend.Funding import Funding
def get_funding_data(): @pytest.fixture
""" def mock_project_scores_matrix():
This function retrieves the eco project scores matrix and the warm homes local grant funding data data = []
:return: floor_segments = ["0-72", "73-97", "98-199", "200"]
""" starting_bands = ["Low_G", "High_G", "Low_F", "High_F", "Low_E", "High_E", "Low_D", "High_D", "Low_C", "High_C"]
project_scores_matrix = read_csv_from_s3( finishing_bands = ["Low_C", "High_C", "Low_B"] # covers likely improvement targets
bucket_name="retrofit-data-dev",
filepath="funding/ECO4 Full Project Scores Matrix.csv", cost = 50.0
for floor in floor_segments:
for start in starting_bands:
for finish in finishing_bands:
if start != finish: # skip identical start/finish (no SAP movement)
data.append({
"Floor Area Segment": floor,
"Starting Band": start,
"Finishing Band": finish,
"Cost Savings": cost
})
cost += 5.0 # increment to create variety
return pd.DataFrame(data)
@pytest.fixture
def mock_partial_scores_matrix():
return pd.DataFrame([{"dummy": "data"}]) # not used for eligibility tests yet
@pytest.fixture
def mock_whlg_postcodes():
return pd.DataFrame([{"Postcode": "ab12cd"}])
### -------------------------
### PRIVATE RENTED SECTOR (PRS)
### -------------------------
def test_eco4_prs_eligible_with_swi(mock_project_scores_matrix, mock_partial_scores_matrix, mock_whlg_postcodes):
funding = Funding(
project_scores_matrix=mock_project_scores_matrix,
partial_project_scores_matrix=mock_partial_scores_matrix,
whlg_eligible_postcodes=mock_whlg_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",
) )
project_scores_matrix = pd.DataFrame(project_scores_matrix)
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( # The property is:
bucket_name="retrofit-data-dev", # 1) private,
filepath="funding/ECO4_Partial_Project_Scores_Matrix_v6.csv", # 2) EPC E
# 3) is getting a solid was measure
# so it's eligible for ECO4
measures = [{"type": "internal_wall_insulation", "is_innovation": False}]
funding.check_funding(
measures=measures,
starting_sap=50, # EPC E
ending_sap=69,
floor_area=80,
mainheat_description="Boiler and radiators, mains gas",
heating_control_description="Programmer, room thermostat and TRVs",
is_cavity=True,
council_tax_band="B"
) )
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( assert funding.eco4_eligible
bucket_name="retrofit-data-dev", assert EligibilityCaveats.TENANT_ON_BENEFITS_OR_LOW_INCOME in funding.eco4_eligibility_caveats
filepath="funding/whlg eligible postcodes.csv",
def test_eco4_prs_not_eligible_high_epc(mock_project_scores_matrix, mock_partial_scores_matrix, mock_whlg_postcodes):
"""Should NOT be eligible if EPC is too high (C or above)."""
funding = Funding(
project_scores_matrix=mock_project_scores_matrix,
partial_project_scores_matrix=mock_partial_scores_matrix,
whlg_eligible_postcodes=mock_whlg_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",
) )
whlg_eligible_postcodes = pd.DataFrame(whlg_eligible_postcodes)
return project_scores_matrix, partial_project_scores_matrix, whlg_eligible_postcodes measures = [{"type": "internal_wall_insulation", "is_innovation": False}]
funding.check_funding(
measures=measures,
starting_sap=72, # EPC C (too high)
ending_sap=75,
floor_area=80,
mainheat_description="Boiler and radiators, mains gas",
heating_control_description="Programmer, room thermostat and TRVs",
is_cavity=True,
council_tax_band="B"
)
assert not funding.eco4_eligible
class TestFunding: def test_gbis_prs_general_eligibility(mock_project_scores_matrix, mock_partial_scores_matrix, mock_whlg_postcodes):
"""PRS EPC DG & council tax band AD should trigger GBIS general route."""
funding = Funding(
project_scores_matrix=mock_project_scores_matrix,
partial_project_scores_matrix=mock_partial_scores_matrix,
whlg_eligible_postcodes=mock_whlg_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",
)
def test_prs(self): measures = [{"type": "internal_wall_insulation", "is_innovation": False}]
fps_matrix, pps_matrix, whlg_eligible_postcodes = get_funding_data() funding.check_funding(
funding = Funding( measures=measures,
project_scores_matrix=fps_matrix, starting_sap=65, # EPC D
partial_project_scores_matrix=pps_matrix, ending_sap=70,
whlg_eligible_postcodes=whlg_eligible_postcodes, floor_area=80,
social_cavity_abs_rate=13.5, mainheat_description="Boiler and radiators, mains gas",
social_solid_abs_rate=17, heating_control_description="Programmer, room thermostat and TRVs",
private_cavity_abs_rate=13.5, is_cavity=True,
private_solid_abs_rate=17, council_tax_band="A"
tenure="Private", )
)
measures_1 = [ assert funding.gbis_eligible
{"type": "internal_wall_insulation", "is_innovation": False},
{"type": "solar_pv", "is_innovation": True},
]
funding.check_funding(
measures=measures_1, def test_gbis_prs_low_income_caveat(mock_project_scores_matrix, mock_partial_scores_matrix, mock_whlg_postcodes):
starting_sap=54, """PRS EPC DG should flag low-income caveat when low-income route is used."""
ending_sap=69, funding = Funding(
floor_area=73, project_scores_matrix=mock_project_scores_matrix,
mainheat_description="Boiler and radiators, mains gas", partial_project_scores_matrix=mock_partial_scores_matrix,
heating_control_description="Programmer, room thermostat and TRVs", whlg_eligible_postcodes=mock_whlg_postcodes,
is_cavity=True 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 = [{"type": "cavity_wall_insulation", "is_innovation": False}]
funding.check_funding(
measures=measures,
starting_sap=60, # EPC D
ending_sap=70,
floor_area=80,
mainheat_description="Boiler and radiators, mains gas",
heating_control_description="Programmer, room thermostat and TRVs",
is_cavity=True,
council_tax_band="B"
)
assert funding.gbis_eligible
assert EligibilityCaveats.TENANT_ON_BENEFITS_OR_LOW_INCOME in funding.gbis_eligibility_caveats
### -------------------------
### SOCIAL HOUSING
### -------------------------
def test_eco4_sh_epc_e_eligible(mock_project_scores_matrix, mock_partial_scores_matrix, mock_whlg_postcodes):
"""EPC E social housing should be ECO4 eligible without innovation."""
funding = Funding(
project_scores_matrix=mock_project_scores_matrix,
partial_project_scores_matrix=mock_partial_scores_matrix,
whlg_eligible_postcodes=mock_whlg_postcodes,
social_cavity_abs_rate=13.5,
social_solid_abs_rate=17,
private_cavity_abs_rate=13.5,
private_solid_abs_rate=17,
tenure="Social",
)
measures = [{"type": "internal_wall_insulation", "is_innovation": False}]
funding.check_funding(
measures=measures,
starting_sap=50, # EPC E
ending_sap=69,
floor_area=80,
mainheat_description="Boiler and radiators, mains gas",
heating_control_description="Programmer, room thermostat and TRVs",
is_cavity=True
)
assert funding.eco4_eligible
def test_eco4_sh_epc_d_requires_innovation(mock_project_scores_matrix, mock_partial_scores_matrix, mock_whlg_postcodes):
"""EPC D social housing should require an innovation measure."""
funding = Funding(
project_scores_matrix=mock_project_scores_matrix,
partial_project_scores_matrix=mock_partial_scores_matrix,
whlg_eligible_postcodes=mock_whlg_postcodes,
social_cavity_abs_rate=13.5,
social_solid_abs_rate=17,
private_cavity_abs_rate=13.5,
private_solid_abs_rate=17,
tenure="Social",
)
measures = [{"type": "internal_wall_insulation", "is_innovation": False}]
funding.check_funding(
measures=measures,
starting_sap=60, # EPC D
ending_sap=69,
floor_area=80,
mainheat_description="Boiler and radiators, mains gas",
heating_control_description="Programmer, room thermostat and TRVs",
is_cavity=True
)
assert not funding.eco4_eligible
assert EligibilityCaveats.INNOVATION_REQUIRED in funding.eco4_eligibility_caveats
def test_eco4_sh_solar_pv_requires_heating(mock_project_scores_matrix, mock_partial_scores_matrix, mock_whlg_postcodes):
"""Solar PV as innovation measure requires ASHP or HHRSH."""
funding = Funding(
project_scores_matrix=mock_project_scores_matrix,
partial_project_scores_matrix=mock_partial_scores_matrix,
whlg_eligible_postcodes=mock_whlg_postcodes,
social_cavity_abs_rate=13.5,
social_solid_abs_rate=17,
private_cavity_abs_rate=13.5,
private_solid_abs_rate=17,
tenure="Social",
)
measures = [{"type": "solar_pv", "is_innovation": True}]
funding.check_funding(
measures=measures,
starting_sap=60, # EPC D
ending_sap=69,
floor_area=80,
mainheat_description="Boiler and radiators, mains gas",
heating_control_description="Programmer, room thermostat and TRVs",
is_cavity=True
)
assert not funding.eco4_eligible
assert EligibilityCaveats.SOLAR_NEEDS_HEATING in funding.eco4_eligibility_caveats
def test_eco4_sh_solar_pv_with_heating_is_ok(mock_project_scores_matrix, mock_partial_scores_matrix,
mock_whlg_postcodes):
"""Solar PV innovation with ASHP should pass EPC D innovation rule."""
funding = Funding(
project_scores_matrix=mock_project_scores_matrix,
partial_project_scores_matrix=mock_partial_scores_matrix,
whlg_eligible_postcodes=mock_whlg_postcodes,
social_cavity_abs_rate=13.5,
social_solid_abs_rate=17,
private_cavity_abs_rate=13.5,
private_solid_abs_rate=17,
tenure="Social",
)
measures = [
{"type": "solar_pv", "is_innovation": True},
{"type": "air_source_heat_pump", "is_innovation": False}
]
funding.check_funding(
measures=measures,
starting_sap=60, # EPC D
ending_sap=69,
floor_area=80,
mainheat_description="Boiler and radiators, mains gas",
heating_control_description="Programmer, room thermostat and TRVs",
is_cavity=True
)
assert funding.eco4_eligible
assert EligibilityCaveats.SOLAR_NEEDS_HEATING not in funding.eco4_eligibility_caveats