feat(investigations): add event impact and work-order feedback flows
Add isolated burst and pollution impact evidence, parallel work-order workflows, persisted notifications and graph updates. Checkpoint current code. Known follow-ups remain for large pollution result previews and graceful geometry failure.
This commit is contained in:
@@ -807,6 +807,60 @@ def test_combined_monitoring_keeps_rule_evidence_and_quality_notifications(monke
|
||||
asyncio.run(run())
|
||||
|
||||
|
||||
@pytest.mark.parametrize("create_tasks", [False, True])
|
||||
def test_pollution_detection_creates_own_investigation_even_during_period_diagnosis(monkeypatch, create_tasks):
|
||||
from app.services.scada_normal_profile import diagnostic_signal, pollution_signal
|
||||
from app.services.scada_replay_processing import detect_frame
|
||||
|
||||
_, service, *_ = setup(monkeypatch)
|
||||
push = AsyncMock()
|
||||
create_task = AsyncMock(return_value=SimpleNamespace(task_id=uuid4()))
|
||||
advance = AsyncMock()
|
||||
monkeypatch.setattr("app.infra.db.postgresql.task_alerts.TaskAlertRepository", lambda *_: object())
|
||||
monkeypatch.setattr("app.services.task_alerts.TaskAlertService", lambda *_args, **_kwargs: SimpleNamespace(push=push))
|
||||
monkeypatch.setattr("app.infra.db.postgresql.operational_tasks.OperationalTaskRepository", lambda *_: object())
|
||||
monkeypatch.setattr("app.services.operational_tasks.OperationalTaskService",
|
||||
lambda *_, **_kwargs: SimpleNamespace(create=create_task, advance_replay_investigation=advance))
|
||||
config = ScadaReplayCreate(request_id=uuid4(), start_time=START, end_time=START + timedelta(hours=1),
|
||||
device_ids=["Q1"], rules=[{"rule_id": "chlorine", "device_ids": ["Q1"],
|
||||
"kind": "bounds", "lower": 0.1, "unit": "mg/L", "metric_name": "余氯"}])
|
||||
document = {"replay_id": str(uuid4()), "config": config.model_dump(mode="json"),
|
||||
"devices": [{"device_id": "Q1", "device_type": "quality", "transmission_frequency": "0:15:00"}]}
|
||||
|
||||
async def run():
|
||||
nonlocal document
|
||||
# One continuous quality episode creates exactly one task, without any
|
||||
# pressure roles, burst score or complaint.
|
||||
for step, value in enumerate((0.05, 0.04, 0.2, 0.3)):
|
||||
at = START + timedelta(minutes=15 * step)
|
||||
document, changes = detect_frame(document, [{"device_id": "Q1", "time": at.isoformat(),
|
||||
"quality": "valid", "cleaned_value": value, "monitored_value": value}], config, at)
|
||||
for event in changes:
|
||||
assert pollution_signal(document, event)
|
||||
assert not diagnostic_signal(document, event)
|
||||
await ScadaReplayService._create_investigations(service, document, changes,
|
||||
{"sequence": step + 1, "at": at.isoformat()}, create_tasks=create_tasks)
|
||||
create_task.assert_awaited_once()
|
||||
command = create_task.await_args.args[0]
|
||||
assert command.workflow == command.source.event_kind == "water_pollution"
|
||||
assert command.task_type == "event_investigation"
|
||||
assert command.source.replay_context.batch_sequence == 1
|
||||
assert command.trigger.kind == "event"
|
||||
assert "投诉" not in command.title and "爆管" not in command.summary
|
||||
assert [call.args[0].event_type for call in push.await_args_list] == ["raised", "resolved"]
|
||||
assert all(call.args[0].condition_id == f"operational-task:{create_task.return_value.task_id}"
|
||||
for call in push.await_args_list)
|
||||
advance.assert_awaited_once()
|
||||
asyncio.run(run())
|
||||
|
||||
|
||||
def test_invalid_quality_data_is_not_a_pollution_detection():
|
||||
from app.services.scada_normal_profile import pollution_signal
|
||||
document = {"devices": [{"device_id": "Q1", "device_type": "quality"}]}
|
||||
assert not pollution_signal(document, {"category": "data_quality", "device_ids": ["Q1"]})
|
||||
assert not pollution_signal(document, {"device_ids": ["unknown"]})
|
||||
|
||||
|
||||
def test_failed_incident_publication_rolls_back_state_and_retries_same_task_request(monkeypatch):
|
||||
repo, service, _, _, create = setup(monkeypatch)
|
||||
create_task = AsyncMock(return_value=SimpleNamespace(task_id=uuid4()))
|
||||
|
||||
Reference in New Issue
Block a user