M8-T13: add district heating pricing profile

This commit is contained in:
2026-08-23 21:22:06 +02:00
parent b812d5ac46
commit 0fb51d338c
6 changed files with 461 additions and 9 deletions
+111 -3
View File
@@ -104,6 +104,15 @@ _TIBBER_VALUES: dict[str, Any] = {
},
}
_DISTRICT_HEATING_VALUES: dict[str, Any] = {
"variable": {
"heating": "12.34",
"hot_water_heating": "1.20",
"hot_water": "2.30",
"hot_water_tax": "0.10",
},
}
def _manual_payload(**overrides) -> dict[str, Any]:
base: dict[str, Any] = {
@@ -127,6 +136,17 @@ def _tibber_payload(**overrides) -> dict[str, Any]:
return base
def _district_heating_payload(**overrides) -> dict[str, Any]:
base: dict[str, Any] = {
"name": "District Heating",
"kind": "district_heating",
"currency": "EUR",
"values": _DISTRICT_HEATING_VALUES,
}
base.update(overrides)
return base
# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------
@@ -156,7 +176,7 @@ def test_profiles_unauthenticated_returns_401(contracts_client):
assert resp.status_code == 401
def test_profiles_returns_both_kinds(contracts_client):
def test_profiles_returns_all_kinds(contracts_client):
client, _ = contracts_client
_login(client)
resp = client.get("/api/energy/profiles")
@@ -166,6 +186,7 @@ def test_profiles_returns_both_kinds(contracts_client):
kinds = {p["kind"] for p in body["profiles"]}
assert "manual" in kinds
assert "tibber" in kinds
assert "district_heating" in kinds
def test_profiles_contain_structure(contracts_client):
@@ -177,9 +198,12 @@ def test_profiles_contain_structure(contracts_client):
for profile in body["profiles"]:
assert "kind" in profile
assert "label" in profile
assert "energy" in profile
assert "standing" in profile
assert "credits" in profile
if profile["kind"] == "district_heating":
assert "variable" in profile
else:
assert "energy" in profile
assert "credits" in profile
# ---------------------------------------------------------------------------
@@ -346,6 +370,57 @@ def test_create_tibber_contract_success(contracts_client):
assert len(body["versions"]) == 1
def test_create_district_heating_contract_normalises_snapshot_and_scope(contracts_client):
client, _ = contracts_client
_login(client)
response = client.post(
"/api/energy/contracts",
json=_district_heating_payload(),
headers={"X-CSRF-Token": _CSRF},
)
assert response.status_code == 201
body = response.json()
assert body["kind"] == "district_heating"
assert body["scope"] == "thermal"
assert body["versions"][0]["values"] == {
"variable": _DISTRICT_HEATING_VALUES["variable"],
"standing": {
"heating_network": "0", "metering": "0", "delivery_set": "0",
"hot_water_network": "0", "other": "0",
},
}
assert client.get("/api/energy/contracts").json()["items"] == []
assert client.get("/api/energy/contracts?scope=thermal").json()["total"] == 1
mismatch = client.post(
"/api/energy/contracts",
json=_district_heating_payload(scope="electricity"),
headers={"X-CSRF-Token": _CSRF},
)
assert mismatch.status_code == 422
@pytest.mark.parametrize(
"values",
[
{"variable": {"heating": "1"}},
{"variable": {**_DISTRICT_HEATING_VALUES["variable"], "heating": -1}},
{"variable": {**_DISTRICT_HEATING_VALUES["variable"], "heating": 1.5}},
{"variable": {**_DISTRICT_HEATING_VALUES["variable"], "extra": "1"}},
],
)
def test_create_district_heating_rejects_invalid_decimal_values(contracts_client, values):
client, engine = contracts_client
_login(client)
response = client.post(
"/api/energy/contracts",
json=_district_heating_payload(values=values),
headers={"X-CSRF-Token": _CSRF},
)
assert response.status_code == 422
with Session(engine) as session:
assert session.execute(select(EnergyContract)).scalars().all() == []
def test_create_contract_defaults_effective_from(contracts_client):
"""When effective_from is omitted, the version is created with a recent timestamp.
@@ -815,3 +890,36 @@ def test_add_version_closes_previous_and_appends(contracts_client):
.all()
)
assert len(all_versions) == 2
def test_district_heating_version_timeline_keeps_normalised_snapshots(contracts_client):
client, _ = contracts_client
_login(client)
t0 = datetime(2026, 1, 1, tzinfo=UTC)
created = client.post(
"/api/energy/contracts",
json=_district_heating_payload(effective_from=t0.isoformat()),
headers={"X-CSRF-Token": _CSRF},
)
assert created.status_code == 201
contract_id = created.json()["id"]
t1 = datetime(2026, 6, 1, tzinfo=UTC)
updated_values = {
"variable": {**_DISTRICT_HEATING_VALUES["variable"], "heating": "13.500"},
"standing": {"metering": "10.00"},
}
response = client.post(
f"/api/energy/contracts/{contract_id}/versions",
json={"effective_from": t1.isoformat(), "values": updated_values},
headers={"X-CSRF-Token": _CSRF},
)
assert response.status_code == 201
old, new = response.json()["versions"]
assert old["effective_to"] is not None
assert old["values"]["variable"]["heating"] == "12.34"
assert new["effective_to"] is None
assert new["values"]["variable"]["heating"] == "13.500"
assert new["values"]["standing"] == {
"heating_network": "0", "metering": "10.00", "delivery_set": "0",
"hot_water_network": "0", "other": "0",
}