109 lines
3.7 KiB
Python
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"
|