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"