diff --git a/backend/app/db/functions/tests/test_portfolio_functions.py b/backend/app/db/functions/tests/test_portfolio_functions.py index 957f6663..5e20646c 100644 --- a/backend/app/db/functions/tests/test_portfolio_functions.py +++ b/backend/app/db/functions/tests/test_portfolio_functions.py @@ -21,6 +21,7 @@ from backend.app.db.models.recommendations import ( ScenarioModel, ) from domain.modelling.portfolio_goal import PortfolioGoal +from tests.utilities.floats import assert_float_matches def _rec( @@ -83,14 +84,14 @@ def test_aggregation_sums_default_measures_linked_by_plan_id( # Assert — the default measures' sums land on the Scenario row scenario = db_session.query(ScenarioModel).filter_by(id=7).one() assert scenario.cost is not None - assert abs(scenario.cost - 1500.0) <= 1e-9 # 1000 + 500 + assert_float_matches(scenario.cost, 1500.0) # 1000 + 500 assert scenario.energy_savings is not None - assert abs(scenario.energy_savings - 800.0) <= 1e-9 # Σ kwh_savings + assert_float_matches(scenario.energy_savings, 800.0) # Σ kwh_savings assert scenario.energy_cost_savings is not None - assert abs(scenario.energy_cost_savings - 200.0) <= 1e-9 # 120 + 80 + assert_float_matches(scenario.energy_cost_savings, 200.0) # 120 + 80 assert scenario.co2_equivalent_savings is not None - assert abs(scenario.co2_equivalent_savings - 0.7) <= 1e-9 # 0.5 + 0.2 + assert_float_matches(scenario.co2_equivalent_savings, 0.7) # 0.5 + 0.2 assert scenario.total_work_hours is not None - assert abs(scenario.total_work_hours - 8.0) <= 1e-9 # 4 + 4 + assert_float_matches(scenario.total_work_hours, 8.0) # 4 + 4 assert scenario.property_valuation_increase == 2500.0 assert scenario.labour_days == 3.0 diff --git a/backend/documents_parser/tests/test_heating_systems_corpus.py b/backend/documents_parser/tests/test_heating_systems_corpus.py index ab7889e4..07e02215 100644 --- a/backend/documents_parser/tests/test_heating_systems_corpus.py +++ b/backend/documents_parser/tests/test_heating_systems_corpus.py @@ -59,6 +59,7 @@ from domain.sap10_calculator.rdsap.cert_to_inputs import ( cert_to_demand_inputs, cert_to_inputs, ) +from tests.utilities.floats import assert_float_matches _CORPUS_ROOT = ( @@ -984,9 +985,13 @@ def test_oil_6_no_room_thermostat_applies_table_4c2_minus_5pp_space_efficiency() # Assert — Table 4b 80% winter less the Table 4c(2) -5pp interlock # penalty = 75% (matches worksheet (210)). - assert abs(inputs.main_heating_efficiency - 0.75) <= 1e-9, ( - f"oil 6 space efficiency {inputs.main_heating_efficiency:.4f} " - f"!= 0.75 (Table 4b 0.80 - Table 4c(2) 0.05 interlock penalty)" + assert_float_matches( + inputs.main_heating_efficiency, + 0.75, + msg=( + f"oil 6 space efficiency {inputs.main_heating_efficiency:.4f} " + f"!= 0.75 (Table 4b 0.80 - Table 4c(2) 0.05 interlock penalty)" + ), ) @@ -1012,9 +1017,13 @@ def test_oil_6_absent_room_thermostat_applies_table_4f_pump_1_3_multiplier() -> # Assert — 41 x 1.3 (circulation pump) + 100 (oil flue fan/pump) = # 153.3 kWh (matches worksheet (231)). - assert abs(inputs.pumps_fans_kwh_per_yr - 153.3) <= 1e-9, ( - f"oil 6 pumps/fans {inputs.pumps_fans_kwh_per_yr:.4f} kWh " - f"!= 153.3 (41 x 1.3 absent-room-thermostat pump + 100 oil aux)" + assert_float_matches( + inputs.pumps_fans_kwh_per_yr, + 153.3, + msg=( + f"oil 6 pumps/fans {inputs.pumps_fans_kwh_per_yr:.4f} kWh " + f"!= 153.3 (41 x 1.3 absent-room-thermostat pump + 100 oil aux)" + ), ) diff --git a/backend/documents_parser/tests/test_summary_pdf_mapper_chain.py b/backend/documents_parser/tests/test_summary_pdf_mapper_chain.py index 83d1e094..7a54fdfd 100644 --- a/backend/documents_parser/tests/test_summary_pdf_mapper_chain.py +++ b/backend/documents_parser/tests/test_summary_pdf_mapper_chain.py @@ -51,6 +51,7 @@ from domain.sap10_calculator.rdsap.cert_to_inputs import ( heat_transmission_section_from_cert, ) from domain.sap10_ml.rdsap_uvalues import u_party_wall +from tests.utilities.floats import assert_float_matches from tests.domain.sap10_calculator.worksheet import ( _elmhurst_worksheet_000474 as _w000474, _elmhurst_worksheet_000477 as _w000477, @@ -1565,8 +1566,8 @@ def test_extension_party_wall_type_read_independently_of_as_main_wall() -> None: f"expected Main=4 (CU, U=0.5) + Ext=0 (Unable, U=0.25), got {party_codes}" ) # The two map to different SAP party-wall U-values. - assert abs(u_party_wall(4) - 0.5) <= 1e-9 - assert abs(u_party_wall(0) - 0.25) <= 1e-9 + assert_float_matches(u_party_wall(4), 0.5) + assert_float_matches(u_party_wall(0), 0.25) def test_summary_mapper_raises_on_unmapped_glazing_type_label() -> None: diff --git a/scripts/eon/find_epc_data.py b/scripts/eon/find_epc_data.py index df2a9ee3..d963cbf5 100644 --- a/scripts/eon/find_epc_data.py +++ b/scripts/eon/find_epc_data.py @@ -17,10 +17,10 @@ from infrastructure.epc_client.epc_client_service import EpcClientService # Reduced-Field Synthesis mapper (ADR-0027) re-maps so the SAP10 calculator can # re-score them. The commented rows are non-20.0.0 neighbours kept for context. UPRNS: list[int] = [ - # 10003318624, # 20.0.0 Flat 1, 6 Alexandra Gardens, PO38 1EE + 10003318624, # 20.0.0 Flat 1, 6 Alexandra Gardens, PO38 1EE # 10003318625, # 20.0.0 Flat 2, 6 Alexandra Gardens, PO38 1EE # 10003318626, # 20.0.0 Flat 3, 6 Alexandra Gardens, PO38 1EE - 10003318698, # 17.1 Flat 4, 6 Alexandra Gardens, PO38 1EE + # 10003318698, # 17.1 Flat 4, 6 Alexandra Gardens, PO38 1EE # 100062430247, # 20.0.0 Flat 5, Adelaide Court, Adelaide Place, PO33 3DG # 100062430248, # 20.0.0 Flat 6, Adelaide Court, Adelaide Place, PO33 3DG # 100062430250, # 20.0.0 Flat 8, Adelaide Court, Adelaide Place, PO33 3DG diff --git a/tests/utilities/floats.py b/tests/utilities/floats.py new file mode 100644 index 00000000..77aa44bb --- /dev/null +++ b/tests/utilities/floats.py @@ -0,0 +1,29 @@ +"""Human-readable float assertions for tests. + +Replaces the cryptic ``assert abs(actual - expected) <= 1e-9`` idiom with a +named helper that says what it checks and prints a useful message on failure. +""" + +from __future__ import annotations + +from typing import Optional + + +def assert_float_matches( + actual: float, + expected: float, + *, + tol: float = 1e-9, + msg: Optional[str] = None, +) -> None: + """Assert ``actual`` equals ``expected`` within an absolute tolerance. + + ``tol`` defaults to ``1e-9`` for exact-arithmetic checks; pass a looser + value (e.g. ``tol=1e-4``) where the comparison is physically approximate. + """ + diff = abs(actual - expected) + detail = f"\n{msg}" if msg else "" + assert diff <= tol, ( + f"expected {expected!r} but got {actual!r} " + f"(|diff| {diff:g} > tol {tol:g}){detail}" + )