Files
next-tjwater-server/tests/unit/test_scada_replay_monitoring.py
T
jiang 26fa5d1909
Generic Container CI/CD / test-build-publish (push) Successful in 3m39s
Server CI/CD v2 / build-test-publish-and-deploy (push) Successful in 3m39s
feat(scada): add project monitoring replay
2026-11-04 14:27:21 +08:00

109 lines
3.7 KiB
Python

from datetime import datetime, timedelta, timezone
from uuid import uuid4
from app.domain.schemas.scada_replay import ScadaReplayCreate
from app.services import scada_replay_monitoring
TARGET = datetime(2026, 10, 16, tzinfo=timezone.utc)
PRESSURE_IDS = [f"P{index}" for index in range(1, 6)]
FLOW_IDS = ["F1"]
def _document():
config = ScadaReplayCreate.model_validate({
"request_id": str(uuid4()),
"start_time": TARGET - timedelta(days=15),
"end_time": TARGET + timedelta(hours=1),
"device_ids": PRESSURE_IDS + FLOW_IDS,
"rules": [
{"rule_id": "pressure_drop", "device_ids": PRESSURE_IDS,
"kind": "relative_change", "direction": "decrease", "threshold": 20, "unit": "m"},
{"rule_id": "outlet_flow", "device_ids": FLOW_IDS,
"kind": "relative_change", "direction": "increase", "threshold": 20, "unit": "m3/h"},
],
})
return {
"replay_id": str(uuid4()),
"config": config.model_dump(mode="json"),
"monitoring_interval_seconds": 900,
"monitoring_config": {
"rule_version": "burst-monitor-v1",
"pressure_device_ids": PRESSURE_IDS,
"outlet_flow_device_ids": FLOW_IDS,
},
}
def _rows():
rows = []
count = 15 * 96
start = TARGET - timedelta(minutes=15 * (count - 1))
for index in range(count):
at = start + timedelta(minutes=15 * index)
for device_id in PRESSURE_IDS:
rows.append({"time": at.isoformat(), "device_id": device_id,
"cleaned_value": 70 if at == TARGET else 100,
"quality": "valid"})
rows.append({"time": at.isoformat(), "device_id": "F1",
"cleaned_value": 150 if at == TARGET else 100,
"quality": "valid"})
return rows
def test_multiple_indicators_create_suspected_burst_result(monkeypatch):
monkeypatch.setattr(scada_replay_monitoring, "run_burst_detection", lambda **_: {
"rows": [{"Score": -0.05}],
"summary": {"latest_sensor_rankings": []},
})
result = scada_replay_monitoring.evaluate_monitoring_period(
_document(), _rows(),
period_start=TARGET - timedelta(minutes=15),
period_end=TARGET,
batch_sequence=1440,
)
assert result["status"] == "suspected_burst"
assert result["pressure"]["hit_device_ids"] == PRESSURE_IDS
assert result["outlet_flow"]["hit_device_ids"] == FLOW_IDS
assert result["burst_detection"]["hit"] is True
def test_single_indicator_stays_attention(monkeypatch):
monkeypatch.setattr(scada_replay_monitoring, "run_burst_detection", lambda **_: {
"rows": [{"Score": 0.1}],
"summary": {"latest_sensor_rankings": []},
})
rows = _rows()
for row in rows:
if row["device_id"] == "F1" and row["time"] == TARGET.isoformat():
row["cleaned_value"] = 100
result = scada_replay_monitoring.evaluate_monitoring_period(
_document(), rows,
period_start=TARGET - timedelta(minutes=15),
period_end=TARGET,
batch_sequence=1440,
)
assert result["status"] == "attention"
assert result["pressure"]["hit"] is True
assert result["outlet_flow"]["hit"] is False
assert result["burst_detection"]["hit"] is False
def test_missing_project_roles_is_data_gap_not_normal():
document = _document()
document["monitoring_config"] = None
result = scada_replay_monitoring.evaluate_monitoring_period(
document, [],
period_start=TARGET - timedelta(minutes=15),
period_end=TARGET,
batch_sequence=1,
)
assert result["status"] == "insufficient_data"
assert result["burst_detection"]["status"] == "not_run"