mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-07-12 13:29:04 +00:00
fixed tests
This commit is contained in:
parent
e0f897bf44
commit
ffbfe4992a
2 changed files with 113 additions and 131 deletions
|
|
@ -14,7 +14,7 @@ class GainOptimiser:
|
||||||
self,
|
self,
|
||||||
components: list[list[Mapping[str, int | float | str]]],
|
components: list[list[Mapping[str, int | float | str]]],
|
||||||
max_cost: float | int,
|
max_cost: float | int,
|
||||||
max_gain: float | int,
|
max_gain: float | int | None,
|
||||||
allow_slack: bool = True,
|
allow_slack: bool = True,
|
||||||
verbose: bool = False
|
verbose: bool = False
|
||||||
):
|
):
|
||||||
|
|
|
||||||
|
|
@ -1,76 +1,34 @@
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from recommendations.optimiser.funding_optimiser import build_heat_pump_paths
|
from recommendations.optimiser.funding_optimiser import (
|
||||||
from recommendations.optimiser.funding_optimiser import run_optimizer
|
build_heat_pump_paths,
|
||||||
|
run_optimizer,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class DummyProp:
|
# ---------------------------------------------------------------------
|
||||||
"""Minimal property stub exposing just what your code reads."""
|
# Heat pump path tests (unchanged – these are fine)
|
||||||
|
# ---------------------------------------------------------------------
|
||||||
def __init__(self):
|
|
||||||
self.data = {
|
|
||||||
"current-energy-rating": "E", # or "D" for the special Social+D path
|
|
||||||
"current-energy-efficiency": 55, # numeric SAP points used in eligibility calc
|
|
||||||
"mainheat-energy-eff": "Very Good",
|
|
||||||
}
|
|
||||||
self.has_ventilation = False
|
|
||||||
self.floor_area = 70.0
|
|
||||||
self.main_heating_controls = {"clean_description": "time and temperature zone control"}
|
|
||||||
self.walls = {'original_description': 'Solid brick, as built, no insulation (assumed)',
|
|
||||||
'thermal_transmittance': None,
|
|
||||||
'thermal_transmittance_unit': None, 'is_cavity_wall': False, 'is_filled_cavity': False,
|
|
||||||
'is_solid_brick': True,
|
|
||||||
'is_system_built': False, 'is_timber_frame': False, 'is_granite_or_whinstone': False,
|
|
||||||
'is_as_built': True,
|
|
||||||
'is_cob': False, 'is_assumed': True, 'is_sandstone_or_limestone': False,
|
|
||||||
'insulation_thickness': 'none',
|
|
||||||
'external_insulation': False, 'internal_insulation': False}
|
|
||||||
|
|
||||||
self.main_heating = {
|
|
||||||
'original_description': 'Boiler and radiators, mains gas',
|
|
||||||
'clean_description': 'Boiler and radiators, mains gas',
|
|
||||||
'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': True,
|
|
||||||
'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': False, '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': False, 'has_mains_gas':
|
|
||||||
True,
|
|
||||||
'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
|
|
||||||
}
|
|
||||||
|
|
||||||
self.main_fuel = {
|
|
||||||
'original_description': 'mains gas (not community)', 'clean_description': 'Mains gas not community',
|
|
||||||
'fuel_type': 'mains gas', 'tariff_type': None, 'is_community': False,
|
|
||||||
'no_individual_heating_or_community_network': False, 'complex_fuel_type': None
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def p():
|
|
||||||
return DummyProp()
|
|
||||||
|
|
||||||
|
|
||||||
def test_build_heat_pump_paths():
|
def test_build_heat_pump_paths():
|
||||||
eg1 = build_heat_pump_paths([], ["loft_insulation"])
|
eg1 = build_heat_pump_paths([], ["loft_insulation"])
|
||||||
|
|
||||||
assert eg1 == [{'AND': ['loft_insulation', 'air_source_heat_pump']}]
|
assert eg1 == [{'AND': ['loft_insulation', 'air_source_heat_pump']}]
|
||||||
|
|
||||||
eg2 = build_heat_pump_paths(["internal_wall_insulation", "external_wall_insulation"], ["loft_insulation"])
|
eg2 = build_heat_pump_paths(
|
||||||
|
["internal_wall_insulation", "external_wall_insulation"],
|
||||||
|
["loft_insulation"],
|
||||||
|
)
|
||||||
|
|
||||||
assert eg2 == [{'AND': ['internal_wall_insulation', 'loft_insulation', 'air_source_heat_pump']},
|
assert eg2 == [
|
||||||
{'AND': ['external_wall_insulation', 'loft_insulation', 'air_source_heat_pump']}]
|
{'AND': ['internal_wall_insulation', 'loft_insulation', 'air_source_heat_pump']},
|
||||||
|
{'AND': ['external_wall_insulation', 'loft_insulation', 'air_source_heat_pump']},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------
|
||||||
|
# run_optimizer tests
|
||||||
|
# ---------------------------------------------------------------------
|
||||||
|
|
||||||
def test_run_optimizer_empty_input():
|
def test_run_optimizer_empty_input():
|
||||||
solution, cost, gain = run_optimizer([])
|
solution, cost, gain = run_optimizer([])
|
||||||
assert solution is None
|
assert solution is None
|
||||||
|
|
@ -78,134 +36,158 @@ def test_run_optimizer_empty_input():
|
||||||
assert gain == 0.0
|
assert gain == 0.0
|
||||||
|
|
||||||
|
|
||||||
def test_uses_gain_optimiser_when_budget_provided(monkeypatch):
|
# ---------------------------------------------------------------------
|
||||||
captured_args = {}
|
# StrategicOptimiser mocking boundary
|
||||||
|
# ---------------------------------------------------------------------
|
||||||
|
|
||||||
class FakeGainOptimiser:
|
def test_budget_and_target_are_passed_correctly(monkeypatch):
|
||||||
def __init__(self, measures, max_cost, max_gain, allow_slack):
|
captured = {}
|
||||||
captured_args["measures"] = measures
|
|
||||||
captured_args["max_cost"] = max_cost
|
class FakeStrategicOptimiser:
|
||||||
captured_args["max_gain"] = max_gain
|
def __init__(
|
||||||
captured_args["allow_slack"] = allow_slack
|
self,
|
||||||
self.solution = [{"cost": 100}]
|
components,
|
||||||
|
budget=None,
|
||||||
|
target_gain=None,
|
||||||
|
allow_slack=False,
|
||||||
|
verbose=False,
|
||||||
|
):
|
||||||
|
captured["components"] = components
|
||||||
|
captured["budget"] = budget
|
||||||
|
captured["target_gain"] = target_gain
|
||||||
|
captured["allow_slack"] = allow_slack
|
||||||
|
|
||||||
|
self.solution = [{"cost": 100, "gain": 5}]
|
||||||
|
self.solution_cost = 100
|
||||||
self.solution_gain = 5
|
self.solution_gain = 5
|
||||||
|
|
||||||
def setup(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def solve(self):
|
def solve(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
"recommendations.optimiser.funding_optimiser.GainOptimiser",
|
"recommendations.optimiser.funding_optimiser.StrategicOptimiser",
|
||||||
FakeGainOptimiser
|
FakeStrategicOptimiser,
|
||||||
)
|
)
|
||||||
|
|
||||||
measures = [[{"cost": 100, "gain": 5}]]
|
|
||||||
|
|
||||||
solution, cost, gain = run_optimizer(
|
solution, cost, gain = run_optimizer(
|
||||||
measures,
|
[[{"cost": 100, "gain": 5}]],
|
||||||
budget=500,
|
budget=500,
|
||||||
sub_target_gain=10,
|
sub_target_gain=10,
|
||||||
allow_slack=True
|
allow_slack=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
assert captured_args["max_cost"] == 500
|
assert captured["budget"] == 500
|
||||||
assert captured_args["max_gain"] == 10
|
assert captured["target_gain"] == 10
|
||||||
assert captured_args["allow_slack"] is True
|
assert captured["allow_slack"] is True
|
||||||
|
|
||||||
assert cost == 100
|
assert cost == 100
|
||||||
assert gain == 5
|
assert gain == 5
|
||||||
|
assert solution == [{"cost": 100, "gain": 5}]
|
||||||
|
|
||||||
|
|
||||||
def test_sub_target_gain_zero_sets_max_gain_zero(monkeypatch):
|
def test_sub_target_gain_zero_is_passed_as_zero(monkeypatch):
|
||||||
captured_args = {}
|
captured = {}
|
||||||
|
|
||||||
class FakeGainOptimiser:
|
class FakeStrategicOptimiser:
|
||||||
def __init__(self, measures, max_cost, max_gain, allow_slack):
|
def __init__(
|
||||||
captured_args["max_gain"] = max_gain
|
self,
|
||||||
|
components,
|
||||||
|
budget=None,
|
||||||
|
target_gain=None,
|
||||||
|
allow_slack=False,
|
||||||
|
verbose=False,
|
||||||
|
):
|
||||||
|
captured["target_gain"] = target_gain
|
||||||
self.solution = []
|
self.solution = []
|
||||||
self.solution_gain = 0
|
self.solution_cost = 0.0
|
||||||
|
self.solution_gain = 0.0
|
||||||
def setup(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def solve(self):
|
def solve(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
"recommendations.optimiser.funding_optimiser.GainOptimiser",
|
"recommendations.optimiser.funding_optimiser.StrategicOptimiser",
|
||||||
FakeGainOptimiser
|
FakeStrategicOptimiser,
|
||||||
)
|
)
|
||||||
|
|
||||||
measures = [[{"cost": 100, "gain": 5}]]
|
|
||||||
|
|
||||||
run_optimizer(
|
run_optimizer(
|
||||||
measures,
|
[[{"cost": 100, "gain": 5}]],
|
||||||
budget=500,
|
budget=500,
|
||||||
sub_target_gain=0
|
sub_target_gain=0,
|
||||||
)
|
)
|
||||||
|
|
||||||
assert captured_args["max_gain"] == 0
|
assert captured["target_gain"] == 0
|
||||||
|
|
||||||
|
|
||||||
def test_sub_target_gain_none_sets_max_gain_infinity(monkeypatch):
|
def test_sub_target_gain_none_becomes_infinity(monkeypatch):
|
||||||
captured_args = {}
|
captured = {}
|
||||||
|
|
||||||
class FakeGainOptimiser:
|
class FakeStrategicOptimiser:
|
||||||
def __init__(self, measures, max_cost, max_gain, allow_slack):
|
def __init__(
|
||||||
captured_args["max_gain"] = max_gain
|
self,
|
||||||
|
components,
|
||||||
|
budget=None,
|
||||||
|
target_gain=None,
|
||||||
|
allow_slack=False,
|
||||||
|
verbose=False,
|
||||||
|
):
|
||||||
|
captured["target_gain"] = target_gain
|
||||||
self.solution = []
|
self.solution = []
|
||||||
self.solution_gain = 0
|
self.solution_cost = 0.0
|
||||||
|
self.solution_gain = 0.0
|
||||||
def setup(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def solve(self):
|
def solve(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
"recommendations.optimiser.funding_optimiser.GainOptimiser",
|
"recommendations.optimiser.funding_optimiser.StrategicOptimiser",
|
||||||
FakeGainOptimiser
|
FakeStrategicOptimiser,
|
||||||
)
|
)
|
||||||
|
|
||||||
measures = [[{"cost": 100, "gain": 5}]]
|
|
||||||
|
|
||||||
run_optimizer(
|
run_optimizer(
|
||||||
measures,
|
[[{"cost": 100, "gain": 5}]],
|
||||||
budget=500,
|
budget=500,
|
||||||
sub_target_gain=None
|
sub_target_gain=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
assert captured_args["max_gain"] == float("inf")
|
assert captured["target_gain"] == None
|
||||||
|
|
||||||
|
|
||||||
def test_uses_cost_optimiser_when_no_budget(monkeypatch):
|
def test_target_only_case(monkeypatch):
|
||||||
captured_args = {}
|
captured = {}
|
||||||
|
|
||||||
class FakeCostOptimiser:
|
class FakeStrategicOptimiser:
|
||||||
def __init__(self, measures, min_gain):
|
def __init__(
|
||||||
captured_args["min_gain"] = min_gain
|
self,
|
||||||
self.solution = [{"cost": 50}]
|
components,
|
||||||
|
budget=None,
|
||||||
|
target_gain=None,
|
||||||
|
allow_slack=False,
|
||||||
|
verbose=False,
|
||||||
|
):
|
||||||
|
captured["budget"] = budget
|
||||||
|
captured["target_gain"] = target_gain
|
||||||
|
|
||||||
|
self.solution = [{"cost": 50, "gain": 10}]
|
||||||
|
self.solution_cost = 50
|
||||||
self.solution_gain = 10
|
self.solution_gain = 10
|
||||||
|
|
||||||
def setup(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def solve(self):
|
def solve(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
"recommendations.optimiser.funding_optimiser.CostOptimiser",
|
"recommendations.optimiser.funding_optimiser.StrategicOptimiser",
|
||||||
FakeCostOptimiser
|
FakeStrategicOptimiser,
|
||||||
)
|
)
|
||||||
|
|
||||||
measures = [[{"cost": 50, "gain": 10}]]
|
|
||||||
|
|
||||||
solution, cost, gain = run_optimizer(
|
solution, cost, gain = run_optimizer(
|
||||||
measures,
|
[[{"cost": 50, "gain": 10}]],
|
||||||
sub_target_gain=10
|
sub_target_gain=10,
|
||||||
)
|
)
|
||||||
|
|
||||||
assert captured_args["min_gain"] == 10
|
assert captured["budget"] is None
|
||||||
|
assert captured["target_gain"] == 10
|
||||||
|
|
||||||
assert cost == 50
|
assert cost == 50
|
||||||
assert gain == 10
|
assert gain == 10
|
||||||
|
assert solution == [{"cost": 50, "gain": 10}]
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue