mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
added optimisation tests
This commit is contained in:
parent
5edc8b691f
commit
eecb9070cb
1 changed files with 135 additions and 0 deletions
135
recommendations/tests/test_optimiser_functions.py
Normal file
135
recommendations/tests/test_optimiser_functions.py
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
import pytest
|
||||
from types import SimpleNamespace
|
||||
from recommendations.optimiser import optimiser_functions
|
||||
|
||||
|
||||
class TestPrepareInputMeasures:
|
||||
def test_returns_expected_structure_without_ventilation(self):
|
||||
recs = [
|
||||
[ # loft insulation measure
|
||||
{"recommendation_id": "loft1", "type": "loft_insulation", "total": 100, "kwh_savings": 200,
|
||||
"energy_cost_savings": 10, "has_battery": False},
|
||||
],
|
||||
]
|
||||
measures = optimiser_functions.prepare_input_measures(recs, goal="Energy Savings", needs_ventilation=False)
|
||||
assert isinstance(measures, list)
|
||||
assert measures[0][0]["id"] == "loft1"
|
||||
assert measures[0][0]["cost"] == 100
|
||||
assert measures[0][0]["gain"] == 200
|
||||
|
||||
def test_bundles_ventilation_when_needed(self, monkeypatch):
|
||||
# patch measures_needing_ventilation so that "wall_insulation" needs ventilation
|
||||
monkeypatch.setattr(optimiser_functions.assumptions, "measures_needing_ventilation", ["wall_insulation"])
|
||||
recs = [
|
||||
[{"recommendation_id": "wall1", "type": "internal_wall_insulation", "total": 500, "kwh_savings": 300,
|
||||
"energy_cost_savings": 5, "has_battery": False}],
|
||||
[{"recommendation_id": "vent1", "type": "mechanical_ventilation", "total": 50, "kwh_savings": 30,
|
||||
"energy_cost_savings": 5, "has_battery": False}]
|
||||
]
|
||||
measures = optimiser_functions.prepare_input_measures(recs, goal="Energy Savings", needs_ventilation=True)
|
||||
wall_option = measures[0][0]
|
||||
assert wall_option["cost"] == 550
|
||||
assert wall_option["gain"] == 330
|
||||
assert "+mechanical_ventilation" in wall_option["type"]
|
||||
|
||||
def test_filters_out_negative_cost_savings(self):
|
||||
recs = [
|
||||
[{"recommendation_id": "bad1", "type": "loft_insulation", "total": 200, "kwh_savings": 100,
|
||||
"energy_cost_savings": -5, "has_battery": False}],
|
||||
]
|
||||
measures = optimiser_functions.prepare_input_measures(recs, goal="Energy Savings", needs_ventilation=False)
|
||||
assert measures == [] # should skip negative cost saving recs
|
||||
|
||||
|
||||
class TestCalculateFixedGain:
|
||||
def test_no_required_measures_returns_zero(self):
|
||||
fixed_gain = optimiser_functions.calculate_fixed_gain(
|
||||
[], {}, SimpleNamespace(id="P1"), needs_ventilation=False
|
||||
)
|
||||
assert fixed_gain == 0
|
||||
|
||||
def test_sums_max_sap_points_per_type(self, monkeypatch):
|
||||
monkeypatch.setattr(optimiser_functions.assumptions, "measures_needing_ventilation", ["wall_insulation"])
|
||||
required_measures = [
|
||||
[{"type": "internal_wall_insulation", "sap_points": 5},
|
||||
{"type": "internal_wall_insulation", "sap_points": 10}],
|
||||
[{"type": "loft_insulation", "sap_points": 3}]
|
||||
]
|
||||
recommendations = {"P1": [[{"type": "mechanical_ventilation", "sap_points": 2}]]}
|
||||
prop = SimpleNamespace(id="P1")
|
||||
gain = optimiser_functions.calculate_fixed_gain(
|
||||
required_measures, recommendations, prop, needs_ventilation=True
|
||||
)
|
||||
# Should take max of wall (10) + loft (3) + ventilation (2)
|
||||
assert gain == 15
|
||||
|
||||
|
||||
class TestCalculateGain:
|
||||
def test_returns_none_for_energy_savings_goal(self):
|
||||
body = SimpleNamespace(goal="Energy Savings")
|
||||
prop = SimpleNamespace(data={"current-energy-efficiency": "50"})
|
||||
gain = optimiser_functions.calculate_gain(body, prop, fixed_gain=0)
|
||||
assert gain is None
|
||||
|
||||
def test_calculates_gain_for_epc(self, monkeypatch):
|
||||
# patch cost optimiser calculation
|
||||
monkeypatch.setattr(optimiser_functions.CostOptimiser, "calculate_sap_gain_with_slack", lambda x: x + 1)
|
||||
monkeypatch.setattr(optimiser_functions, "epc_to_sap_lower_bound", lambda goal_value: 69)
|
||||
|
||||
body = SimpleNamespace(goal="Increasing EPC", goal_value="C", simulate_sap_10=False)
|
||||
prop = SimpleNamespace(data={"current-energy-efficiency": "50"})
|
||||
gain = optimiser_functions.calculate_gain(body, prop, fixed_gain=2)
|
||||
# epc_to_sap_lower_bound (69) - current (50) = 10 + slack (1) = 11 - fixed_gain (2) = 9
|
||||
assert gain == 18.5
|
||||
|
||||
|
||||
class TestAddRequiredMeasures:
|
||||
def test_adds_cheapest_required_measure(self):
|
||||
property_id = "P1"
|
||||
required_measures = [
|
||||
[{"recommendation_id": "a", "total": 100, "sap_points": 5, "type": "loft_insulation"},
|
||||
{"recommendation_id": "b", "total": 80, "sap_points": 6, "type": "loft_insulation"}]
|
||||
]
|
||||
recommendations = {
|
||||
"P1": [[{"recommendation_id": "a", "total": 100, "sap_points": 5, "type": "loft_insulation"},
|
||||
{"recommendation_id": "b", "total": 80, "sap_points": 6, "type": "loft_insulation"}]]
|
||||
}
|
||||
selected = set()
|
||||
result = optimiser_functions.add_required_measures(property_id, required_measures, recommendations, selected)
|
||||
# cheapest should be b
|
||||
assert "b" in selected
|
||||
assert any(rec["id"] == "b" for rec in result)
|
||||
|
||||
|
||||
class TestAddBestPracticeMeasures:
|
||||
def test_adds_ventilation_and_trickle_vents(self, monkeypatch):
|
||||
monkeypatch.setattr(optimiser_functions.assumptions, "measures_needing_ventilation", ["wall_insulation"])
|
||||
property_id = "P1"
|
||||
solution = [{"type": "internal_wall_insulation", "id": "w1", "gain": 10, "cost": 100}]
|
||||
recommendations = {
|
||||
"P1": [
|
||||
[{"type": "mechanical_ventilation", "recommendation_id": "vent1"}],
|
||||
[{"type": "trickle_vents", "recommendation_id": "trickle1"}]
|
||||
]
|
||||
}
|
||||
selected = set()
|
||||
updated = optimiser_functions.add_best_practice_measures(property_id, solution, recommendations, selected)
|
||||
assert "vent1" in updated
|
||||
assert "trickle1" in updated
|
||||
|
||||
|
||||
class TestFlattenRecommendationsWithDefaults:
|
||||
def test_marks_selected_and_flattens(self):
|
||||
property_id = "P1"
|
||||
recommendations = {
|
||||
"P1": [
|
||||
[{"recommendation_id": "a", "foo": 1}, {"recommendation_id": "b", "foo": 2}],
|
||||
[{"recommendation_id": "c", "foo": 3}]
|
||||
]
|
||||
}
|
||||
selected = {"b", "c"}
|
||||
result = optimiser_functions.flatten_recommendations_with_defaults(property_id, recommendations, selected)
|
||||
# All recs should now have a default key
|
||||
assert all("default" in rec for rec in result)
|
||||
assert next(r for r in result if r["recommendation_id"] == "b")["default"] is True
|
||||
assert next(r for r in result if r["recommendation_id"] == "a")["default"] is False
|
||||
Loading…
Add table
Reference in a new issue