Files
bjkim1 21ebb16fb7 SPipe 데모 v2 (2026-10-30) 서버 배포본
- SPipe_server_2026-10-30.zip 내용을 풀어서 등록 (압축 파일은 올리지 않음)
- README에 데모 v2 서버 업데이트 순서 추가, 저장소에 없는 문서 목차 정리
- 패치내역을 docs/로 이동, _worklog.md 추가
- 임시 파일(backend/temp.*)과 API 키가 든 파일은 제외
2026-10-30 11:00:46 +09:00

88 lines
3.5 KiB
Python

"""backend/batch.py against the real EPANET DLL."""
from pathlib import Path
import numpy as np
from backend.batch import Network, parse_time
from backend.epanet import EN
NET1 = (Path(__file__).resolve().parents[2] / "public" / "Net1.inp").read_bytes()
def test_open_lookup_and_baseline():
with Network(NET1) as net:
assert net.node_count == 11 and net.link_count == 13
assert net.node_ids[net.node_index("22") - 1] == "22"
res = net.simulate(node_props=(EN.PRESSURE,), link_props=(EN.FLOW,))
assert len(res.times) == 25 and res.times[0] == 0 and res.times[-1] == 24 * 3600
assert res.nodes[EN.PRESSURE].shape == (25, 11)
assert res.links[EN.FLOW].shape == (25, 13)
def test_windowed_extra_demand_only_acts_in_window():
with Network(NET1) as net:
j = net.node_index("22")
base = net.simulate(nodes=[j]).nodes[EN.PRESSURE][:, 0]
net.window_pattern("FIREWIN", 6 * 3600, 9 * 3600)
cat = net.add_demand(j, 500.0, "FIREWIN", "fire")
fire = net.simulate(nodes=[j]).nodes[EN.PRESSURE][:, 0]
hours = np.arange(25)
inside = (hours >= 6) & (hours < 9)
assert np.all(fire[inside] < base[inside] - 1.0)
# Tank levels carry the draw-down a little past the window; before it, nothing changes.
assert np.allclose(fire[hours < 6], base[hours < 6], atol=1e-6)
net.delete_demand(j, cat)
again = net.simulate(nodes=[j]).nodes[EN.PRESSURE][:, 0]
assert np.allclose(again, base, atol=1e-6)
def test_timer_control_closes_pipe_in_window():
with Network(NET1) as net:
p = net.link_index("12")
c1 = net.add_timer_control(p, 0, 3 * 3600)
c2 = net.add_timer_control(p, 1, 5 * 3600)
flow = net.simulate(node_props=(), link_props=(EN.FLOW,), links=[p]).links[EN.FLOW][:, 0]
assert abs(flow[3]) < 1e-6 and abs(flow[4]) < 1e-6
assert abs(flow[2]) > 1e-3 and abs(flow[6]) > 1e-3
net.delete_control(c2)
net.delete_control(c1)
def test_parse_time():
assert parse_time("6:30") == 6 * 3600 + 1800
assert parse_time(2.5) == 9000
assert parse_time(None, 7) == 7
def test_fireflow_job_and_available_flow():
from backend.jobs import job_fireflow
r = job_fireflow(NET1, {"nodes": ["22"], "flow": 500, "start": 6, "end": 9, "residual": 20, "threshold": 50})
assert r["windowLabel"] == "6:00~9:00"
h = r["hydrants"][0]
assert h["firePressure"] < h["basePressure"] - 1
assert r["nodes"][0]["diff"] <= r["nodes"][-1]["diff"] # most affected first
# available flow keeps the hydrant at (just above) the residual pressure
q = h["available"]
assert q > 0
with Network(NET1) as net:
j = net.node_index("22")
net.window_pattern("W", 6 * 3600, 9 * 3600)
c = net.add_demand(j, q, "W", "x")
p = net.simulate(nodes=[j]).nodes[EN.PRESSURE][6:9, 0].min()
assert p >= 20 - 1e-6
net.delete_demand(j, c)
net.add_demand(j, q * 1.02, "W", "x")
assert net.simulate(nodes=[j]).nodes[EN.PRESSURE][6:9, 0].min() < 20 + 0.5
def test_fireflow_rejects_bad_input():
import pytest
from backend.batch import BatchError
from backend.jobs import job_fireflow
with pytest.raises(BatchError):
job_fireflow(NET1, {"nodes": [], "flow": 10})
with pytest.raises(BatchError):
job_fireflow(NET1, {"nodes": ["NOPE"], "flow": 10})