"""Criticality, service area (trace), VSP and energy jobs against the real DLL.""" import re from pathlib import Path import pytest from backend.batch import BatchError from backend.jobs import job_criticality, job_energy, job_servicearea, job_vsp PUBLIC = Path(__file__).resolve().parents[2] / "public" NET1 = (PUBLIC / "Net1.inp").read_bytes() NET3 = (PUBLIC / "Net3.inp").read_bytes() def test_criticality_ranks_the_supply_main_first(): r = job_criticality(NET1, {"threshold": 40}) assert len(r["links"]) == 12 # pipes only, the pump is skipped top = r["links"][0] # Pipe 10 is the only path from the source: closing it cuts off every junction. assert top["id"] == "10" and top["affectedNodes"] == 8 and top["disconnected"] loops = [e for e in r["links"] if e["id"] in ("111", "112", "121", "122")] assert all(e["affectedNodes"] == 0 for e in loops) def test_criticality_subset_and_limits(): r = job_criticality(NET1, {"threshold": 40, "links": ["111"]}) assert [e["id"] for e in r["links"]] == ["111"] with pytest.raises(BatchError): job_criticality(NET1, {"links": ["nope"]}) def test_service_area_shares_sum_to_at_most_100(): r = job_servicearea(NET3, {}) assert r["sources"] == ["River", "Lake"] assert r["unreached"] == 0 and r["notes"] # 24 h run extended to 72 h assert max(sum(v["shares"].values()) for v in r["nodes"].values()) <= 100.5 assert r["nodes"]["River"]["source"] == "River" and r["nodes"]["Lake"]["source"] == "Lake" def test_vsp_hits_the_target_or_reports_the_bound(): r = job_vsp(NET1, {"pump": "9", "node": "31", "target": 125, "hours": [0, 18], "maxSpeed": 1.5}) by_hour = {row["hour"]: row for row in r["rows"]} assert by_hour[0]["status"] == "max" and by_hour[0]["pressure"] < 125 assert by_hour[18]["status"] == "ok" and abs(by_hour[18]["pressure"] - 125) < 0.05 assert 1.0 < by_hour[18]["speed"] < 1.5 with pytest.raises(BatchError): job_vsp(NET1, {"pump": "10", "node": "31", "target": 100}) # a pipe def test_energy_report_matches_epanet_energy_section(): r = job_energy(NET1, {}) (p,) = r["pumps"] # EPANET's own Net1 energy report: 57.71 % utilisation, 75 % efficiency, ~96 kW average. assert p["utilization"] == pytest.approx(57.71, abs=0.05) assert p["efficiency"] == pytest.approx(75.0) assert p["avgKw"] == pytest.approx(96.25, abs=0.5) assert p["kwhPerM3"] > 0 and r["totalCost"] == 0.0 # Net1 has no price priced = re.sub(rb"Global Price\s+0\.0", b"Global Price 0.1", NET1) assert priced != NET1 assert job_energy(priced, {})["pumps"][0]["cost"] == pytest.approx(p["kwh"] * 0.1, rel=1e-3)