433 lines
23 KiB
Python
433 lines
23 KiB
Python
"""SCADA display coverage is independent of whether a device supports a conclusion."""
|
|
from datetime import datetime, timedelta, timezone
|
|
from uuid import uuid4
|
|
|
|
import pytest
|
|
|
|
from app.domain.domain_errors import DomainError
|
|
from app.domain.schemas.operational_data import TaskDataSnapshot
|
|
from app.domain.schemas.operational_task import AgentArtifactsCommand, AgentRunCommand, TaskPresentationEventCommand
|
|
from app.services.operational_tasks import OperationalTaskService
|
|
from tests.support.operational_task_repository import TaskRepository, _active_run, _context, _task_document
|
|
|
|
|
|
DEVICES = ["486033", "542017", "541061", "524529"]
|
|
END = datetime(2026, 7, 16, tzinfo=timezone.utc)
|
|
START = END - timedelta(hours=3)
|
|
|
|
|
|
@pytest.fixture
|
|
def anyio_backend():
|
|
return "asyncio"
|
|
|
|
|
|
async def investigation():
|
|
context = _context()
|
|
task = _task_document(context, END, lease=END + timedelta(minutes=1))
|
|
task["workflow"] = "scada_anomaly"
|
|
repository = TaskRepository(task, _active_run(task, END))
|
|
service = OperationalTaskService(repository, context, set(), clock=lambda: END)
|
|
readings = []
|
|
for day in range(8):
|
|
slots = range(13) if day == 0 else range(10, 13)
|
|
for slot in slots:
|
|
for device in DEVICES:
|
|
readings.append({"device_id": device, "time": (START + timedelta(minutes=15 * slot, days=-day)).isoformat(),
|
|
"monitored_value": float(slot + 1), "cleaned_value": float(slot + 1)})
|
|
evidence, _ = await service.record_data_snapshot(task["task_id"], TaskDataSnapshot(
|
|
source="query_scada_readings", source_ref="mcp:observations", queried_at=END,
|
|
origin="system_data", truncated=False, data={
|
|
"window": {"start": (START - timedelta(days=7)).isoformat(), "end": END.isoformat()},
|
|
"data": {"devices": [{"device_id": device, "device_type": "pipe_flow", "measurement_unit": "m3/h",
|
|
"transmission_frequency": "0:15:00"} for device in DEVICES],
|
|
"readings": readings, "coverage": {"mode": "all_scada", "query_complete": True}},
|
|
},
|
|
))
|
|
return task, repository, service, evidence.evidence_id
|
|
|
|
|
|
def coverage(comparison_devices):
|
|
slots = [(START + timedelta(minutes=15 * i)).isoformat() for i in range(13)]
|
|
common = {"coverage": {device: {"covered": slots} for device in DEVICES}, "metric": "flow", "window_start": START.isoformat(), "window_end": END.isoformat(), "gaps": []}
|
|
return {"version": 1, "targets": DEVICES.copy(), "charts": {
|
|
"overview": {**common, "role": "current", "device_ids": DEVICES.copy()},
|
|
**{f"comparison-{device}": {**common, "role": "comparison", "device_ids": [device],
|
|
"coverage": {device: {"covered": slots}}}
|
|
for device in comparison_devices},
|
|
}}
|
|
|
|
|
|
def final_report(evidence_id):
|
|
return {"stage": "final", "outcome": "concluded", "title": "调查结果",
|
|
"current_conclusion": "524529 仅作参考", "conclusion_boundary": "待现场核验",
|
|
"evidence_refs": [evidence_id], "data_gaps": [], "scada_device_ids": DEVICES}
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_final_rejects_four_target_devices_with_three_comparisons():
|
|
task, repository, service, evidence_id = await investigation()
|
|
repository.scada_presentations = {(task["task_id"], repository.run["run_id"]): coverage(DEVICES[:3])}
|
|
with pytest.raises(DomainError) as failure:
|
|
await service.publish_artifacts(task["task_id"], AgentArtifactsCommand(
|
|
request_id=uuid4(), source_session_id="session-1", report=final_report(evidence_id),
|
|
))
|
|
assert failure.value.code == "SCADA_PRESENTATION_INCOMPLETE"
|
|
assert "524529" in failure.value.message
|
|
assert not repository.reports
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_final_accepts_all_four_devices():
|
|
task, repository, service, evidence_id = await investigation()
|
|
repository.scada_presentations = {(task["task_id"], repository.run["run_id"]): coverage(DEVICES)}
|
|
result = await service.publish_artifacts(task["task_id"], AgentArtifactsCommand(
|
|
request_id=uuid4(), source_session_id="session-1", report=final_report(evidence_id),
|
|
))
|
|
assert result.report_revision == 1
|
|
|
|
|
|
def chart(role="current", devices=None):
|
|
devices = DEVICES if devices is None else devices
|
|
comparison = role == "comparison"
|
|
series = []
|
|
for device in devices:
|
|
for day in (range(8) if comparison else [0]):
|
|
slots = range(13) if day == 0 else range(10, 13)
|
|
series.append({"name": f"{device} {day}", "device_id": device, "day_offset": day,
|
|
"type": "scatter" if day else "line",
|
|
"data": [float(index + 1) if index in slots else None for index in range(13)],
|
|
"sample_times": [(START + timedelta(minutes=15 * index, days=-day)).isoformat()
|
|
if index in slots else None for index in range(13)]})
|
|
return {"title": "流量观测", "chart_type": "time_comparison" if comparison else "line",
|
|
"x_data": [(START + timedelta(minutes=15 * index, hours=8)).strftime("%H:%M") for index in range(13)],
|
|
"series": series, "scada": {"role": role, "metric": "flow", "unit": "m3/h",
|
|
"window_start": START.isoformat(), "window_end": END.isoformat()}}
|
|
|
|
|
|
async def publish(task, repository, service, evidence_id, chart_id, data):
|
|
command = TaskPresentationEventCommand(
|
|
request_id=uuid4(), run_id=repository.run["run_id"], source_session_id="session-1",
|
|
command={"action": "open_chart", "chart_id": chart_id, "chart": data, "evidence_refs": [evidence_id]},
|
|
)
|
|
return await service.publish_presentation_event(task["task_id"], command)
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_publications_require_complete_targets_and_retry_without_a_browser():
|
|
task, repository, service, evidence_id = await investigation()
|
|
await publish(task, repository, service, evidence_id, "overview", chart())
|
|
for device in DEVICES[:3]:
|
|
await publish(task, repository, service, evidence_id, device, chart("comparison", [device]))
|
|
interim = {**final_report(evidence_id), "stage": "interim"}
|
|
await service.publish_artifacts(task["task_id"], AgentArtifactsCommand(
|
|
request_id=uuid4(), source_session_id="session-1", report=interim,
|
|
))
|
|
final = AgentArtifactsCommand(request_id=uuid4(), source_session_id="session-1", report=final_report(evidence_id))
|
|
with pytest.raises(DomainError) as failure:
|
|
await service.publish_artifacts(task["task_id"], final)
|
|
assert failure.value.code == "SCADA_PRESENTATION_INCOMPLETE"
|
|
assert failure.value.refs == ["524529"]
|
|
await publish(task, repository, service, evidence_id, "524529", chart("comparison", ["524529"]))
|
|
await service.publish_presentation_event(task["task_id"], TaskPresentationEventCommand(
|
|
request_id=uuid4(), run_id=repository.run["run_id"], source_session_id="session-1",
|
|
command={"action": "close_chart", "chart_id": "524529"},
|
|
))
|
|
result = await service.publish_artifacts(task["task_id"], final)
|
|
assert result.task.latest_report.report.stage == "final"
|
|
# Neither retained event history nor a browser acknowledgement is required.
|
|
repository.events.clear()
|
|
complete = await service.complete_run(task["task_id"], repository.run["run_id"], AgentRunCommand(
|
|
request_id=uuid4(), expected_revision=repository.run["revision"], source_session_id="session-1",
|
|
))
|
|
assert complete.latest_run.status == "succeeded"
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_successful_completion_rechecks_coverage_after_final_report():
|
|
task, repository, service, evidence_id = await investigation()
|
|
repository.scada_presentations[(task["task_id"], repository.run["run_id"])] = coverage(DEVICES)
|
|
await service.publish_artifacts(task["task_id"], AgentArtifactsCommand(
|
|
request_id=uuid4(), source_session_id="session-1", report=final_report(evidence_id),
|
|
))
|
|
# Replacing a chart cannot erase its fourth target from the required set.
|
|
await publish(task, repository, service, evidence_id, "overview", chart(devices=DEVICES[:3]))
|
|
with pytest.raises(DomainError) as failure:
|
|
await service.complete_run(task["task_id"], repository.run["run_id"], AgentRunCommand(
|
|
request_id=uuid4(), expected_revision=repository.run["revision"], source_session_id="session-1",
|
|
))
|
|
assert failure.value.code == "SCADA_PRESENTATION_INCOMPLETE"
|
|
assert "524529" in failure.value.message
|
|
assert repository.run["status"] == "running"
|
|
|
|
|
|
@pytest.mark.anyio
|
|
@pytest.mark.parametrize("missing_current", [False, True])
|
|
async def test_missing_observations_keep_the_object_and_server_derived_gaps(missing_current):
|
|
task, repository, service, evidence_id = await investigation()
|
|
data = repository.evidence[evidence_id]["data_snapshot"]["data"]["data"]
|
|
data["readings"] = [row for row in data["readings"] if row["device_id"] != "524529"
|
|
or not missing_current and datetime.fromisoformat(row["time"]) >= START]
|
|
value = chart("comparison", ["524529"])
|
|
for series in value["series"]:
|
|
if missing_current or series["day_offset"]:
|
|
series["data"] = [None] * 13
|
|
series["sample_times"] = [None] * 13
|
|
await publish(task, repository, service, evidence_id, "524529", value)
|
|
saved = repository.events[-1]["metadata"]["command"]["chart"]
|
|
assert len(saved["scada"]["gaps"]) == (8 if missing_current else 7)
|
|
assert sum(gap["missing_count"] for gap in saved["scada"]["gaps"]) == (34 if missing_current else 21)
|
|
document = await repository.get_scada_presentation(task["task_id"], repository.run["run_id"])
|
|
assert document["targets"] == ["524529"]
|
|
assert document["charts"]["524529"]["device_ids"] == ["524529"]
|
|
|
|
|
|
@pytest.mark.anyio
|
|
@pytest.mark.parametrize("defect,code", [
|
|
("null_existing_value", "SCADA_CHART_DATA_MISMATCH"),
|
|
("invented_value", "SCADA_CHART_DATA_MISMATCH"),
|
|
("wrong_window", "SCADA_CHART_WINDOW_INVALID"),
|
|
("missing_day", "SCADA_CHART_INVALID"),
|
|
("wrong_unit", "SCADA_CHART_INVALID"),
|
|
("unqueried_history", "SCADA_CHART_EVIDENCE_REQUIRED"),
|
|
])
|
|
async def test_scada_chart_cannot_fake_coverage(defect, code):
|
|
task, repository, service, evidence_id = await investigation()
|
|
value = chart("comparison", ["524529"])
|
|
if defect == "null_existing_value":
|
|
value["series"][1]["data"][10] = None
|
|
value["series"][1]["sample_times"][10] = None
|
|
elif defect == "invented_value":
|
|
value["series"][0]["data"][0] = 999.
|
|
elif defect == "wrong_window":
|
|
value["scada"]["window_end"] = (END - timedelta(minutes=15)).isoformat()
|
|
elif defect == "missing_day":
|
|
value["series"].pop()
|
|
elif defect == "wrong_unit":
|
|
value["scada"]["unit"] = "m"
|
|
elif defect == "unqueried_history":
|
|
repository.evidence[evidence_id]["data_snapshot"]["data"]["window"]["start"] = START.isoformat()
|
|
with pytest.raises(DomainError) as failure:
|
|
await publish(task, repository, service, evidence_id, "524529", value)
|
|
assert failure.value.code == code
|
|
assert not repository.scada_presentations
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_previous_run_charts_never_satisfy_a_new_run():
|
|
task, repository, service, evidence_id = await investigation()
|
|
original = repository.run["run_id"]
|
|
repository.scada_presentations[(task["task_id"], original)] = coverage(DEVICES)
|
|
task["execution_generation"] += 1
|
|
repository.run = _active_run(task, END)
|
|
repository.runs[repository.run["run_id"]] = repository.run
|
|
with pytest.raises(DomainError) as failure:
|
|
await service.publish_artifacts(task["task_id"], AgentArtifactsCommand(
|
|
request_id=uuid4(), source_session_id="session-1", report=final_report(evidence_id),
|
|
))
|
|
assert failure.value.code == "SCADA_PRESENTATION_INCOMPLETE"
|
|
assert original != repository.run["run_id"]
|
|
assert (task["task_id"], original) in repository.scada_presentations
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_one_device_cannot_inflate_comparison_object_count_with_a_new_chart_id():
|
|
task, repository, service, evidence_id = await investigation()
|
|
await publish(task, repository, service, evidence_id, "524529", chart("comparison", ["524529"]))
|
|
with pytest.raises(DomainError) as failure:
|
|
await publish(task, repository, service, evidence_id, "524529-duplicate", chart("comparison", ["524529"]))
|
|
assert failure.value.code == "SCADA_CHART_DUPLICATE_DEVICE"
|
|
assert failure.value.refs == ["524529"]
|
|
await publish(task, repository, service, evidence_id, "524529", chart("comparison", ["524529"]))
|
|
assert len(repository.scada_presentations[(task["task_id"], repository.run["run_id"])]["charts"]) == 1
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_server_frozen_boundary_proves_unavailable_history_without_fabricating_a_query():
|
|
task, repository, service, evidence_id = await investigation()
|
|
replay_id = str(uuid4())
|
|
task["source"]["replay_context"] = {"replay_id": replay_id, "knowledge_cutoff": END.isoformat(), "batch_sequence": 3}
|
|
snapshot = repository.evidence[evidence_id]["data_snapshot"]["data"]
|
|
snapshot["window"]["start"] = START.isoformat()
|
|
snapshot["data"]["coverage"].update(replay_id=replay_id, batch_sequence=3, frozen_start=START.isoformat())
|
|
value = chart("comparison", ["524529"])
|
|
for series in value["series"][1:]:
|
|
series["data"] = [None] * 13
|
|
series["sample_times"] = [None] * 13
|
|
await publish(task, repository, service, evidence_id, "524529", value)
|
|
gaps = repository.events[-1]["metadata"]["command"]["chart"]["scada"]["gaps"]
|
|
assert len(gaps) == 7
|
|
assert {gap["reason"] for gap in gaps} == {"outside_frozen_window"}
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_interim_monitoring_table_targets_cannot_be_removed_from_a_later_final_report():
|
|
task, repository, service, evidence_id = await investigation()
|
|
incomplete = coverage(DEVICES[:3])
|
|
incomplete["targets"] = DEVICES[:3]
|
|
incomplete["charts"]["overview"]["device_ids"] = DEVICES[:3]
|
|
repository.scada_presentations[(task["task_id"], repository.run["run_id"])] = incomplete
|
|
await service.publish_artifacts(task["task_id"], AgentArtifactsCommand(
|
|
request_id=uuid4(), source_session_id="session-1", report={**final_report(evidence_id), "stage": "interim"},
|
|
))
|
|
with pytest.raises(DomainError) as failure:
|
|
await service.publish_artifacts(task["task_id"], AgentArtifactsCommand(
|
|
request_id=uuid4(), source_session_id="session-1", report={**final_report(evidence_id), "scada_device_ids": DEVICES[:3]},
|
|
))
|
|
assert failure.value.code == "SCADA_PRESENTATION_INCOMPLETE"
|
|
assert failure.value.refs == ["524529"]
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_narrative_diagram_references_do_not_declare_business_display_targets():
|
|
task, repository, service, evidence_id = await investigation()
|
|
selected = DEVICES[:3]
|
|
document = coverage(selected)
|
|
document["targets"] = selected
|
|
document["charts"]["overview"]["device_ids"] = selected
|
|
repository.scada_presentations[(task["task_id"], repository.run["run_id"])] = document
|
|
report = {**final_report(evidence_id), "scada_device_ids": selected, "diagram": {
|
|
"title": "待选参考测点", "mermaid": "flowchart LR\nreference[参考测点]", "node_refs": [{
|
|
"node_id": "reference", "label": "参考测点", "map_targets": [{"source_id": "scada", "feature_id": "524529"}],
|
|
}],
|
|
}}
|
|
result = await service.publish_artifacts(task["task_id"], AgentArtifactsCommand(
|
|
request_id=uuid4(), source_session_id="session-1", report=report,
|
|
))
|
|
assert result.report_revision == 1
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_unaligned_cutoff_keeps_actual_quarter_hour_samples():
|
|
task, repository, service, evidence_id = await investigation()
|
|
task["trigger"]["occurred_at"] = (END + timedelta(minutes=8)).isoformat()
|
|
value = chart("comparison", ["524529"])
|
|
value["scada"].update(window_start=(START + timedelta(minutes=8)).isoformat(),
|
|
window_end=(END + timedelta(minutes=8)).isoformat())
|
|
value["x_data"] = value["x_data"][1:]
|
|
for item in value["series"]:
|
|
item["data"] = item["data"][1:]
|
|
item["sample_times"] = item["sample_times"][1:]
|
|
await publish(task, repository, service, evidence_id, "unaligned", value)
|
|
|
|
|
|
@pytest.mark.anyio
|
|
@pytest.mark.parametrize("frequency", ["15min", "15分钟"])
|
|
async def test_wholly_missing_devices_accept_supported_cadence_formats(frequency):
|
|
task, repository, service, evidence_id = await investigation()
|
|
data = repository.evidence[evidence_id]["data_snapshot"]["data"]["data"]
|
|
data["readings"] = []
|
|
for device in data["devices"]:
|
|
device["transmission_frequency"] = frequency
|
|
value = chart("comparison", ["524529"])
|
|
for item in value["series"]:
|
|
item["data"] = [None] * 13
|
|
item["sample_times"] = [None] * 13
|
|
await publish(task, repository, service, evidence_id, "missing", value)
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_one_minute_chunks_require_union_of_verified_slots():
|
|
task, repository, service, evidence_id = await investigation()
|
|
data = repository.evidence[evidence_id]["data_snapshot"]["data"]["data"]
|
|
data["devices"] = [{**data["devices"][0], "transmission_frequency": "1min"}]
|
|
device = DEVICES[0]
|
|
data["readings"] = [{"device_id": device, "time": (START + timedelta(minutes=i, days=-day)).isoformat(),
|
|
"monitored_value": float(i + 1)}
|
|
for day in range(8) for i in (range(181) if day == 0 else range(178, 181))]
|
|
|
|
def chunk(role, first, stop, missing_current=False):
|
|
value = chart(role, [device])
|
|
value["x_data"] = [(START + timedelta(minutes=i, hours=8)).strftime("%H:%M") for i in range(first, stop)]
|
|
for item in value["series"]:
|
|
day = item["day_offset"]
|
|
present = lambda i: (not day or i >= 178) and not (missing_current and day == 0)
|
|
item["data"] = [float(i + 1) if present(i) else None for i in range(first, stop)]
|
|
item["sample_times"] = [(START + timedelta(minutes=i, days=-day)).isoformat() if present(i) else None
|
|
for i in range(first, stop)]
|
|
return value
|
|
|
|
report = {**final_report(evidence_id), "scada_device_ids": [device]}
|
|
for role in ("current", "comparison"):
|
|
await publish(task, repository, service, evidence_id, role + "-first", chunk(role, 0, 100))
|
|
with pytest.raises(DomainError) as failure:
|
|
await service.publish_artifacts(task["task_id"], AgentArtifactsCommand(
|
|
request_id=uuid4(), source_session_id="session-1", report=report))
|
|
assert failure.value.code == "SCADA_PRESENTATION_INCOMPLETE"
|
|
with pytest.raises(DomainError) as duplicate:
|
|
await publish(task, repository, service, evidence_id, "overlap", chunk("comparison", 99, 181))
|
|
assert duplicate.value.code == "SCADA_CHART_DUPLICATE_DEVICE"
|
|
for role in ("current", "comparison"):
|
|
await publish(task, repository, service, evidence_id, role + "-last", chunk(role, 100, 181))
|
|
await service.publish_artifacts(task["task_id"], AgentArtifactsCommand(
|
|
request_id=uuid4(), source_session_id="session-1", report=report))
|
|
# Missing current data still permits the last chunk's historical dates across midnight.
|
|
data["readings"] = [row for row in data["readings"] if datetime.fromisoformat(row["time"]) < START]
|
|
await publish(task, repository, service, evidence_id, "comparison-last", chunk("comparison", 100, 181, True))
|
|
|
|
|
|
@pytest.mark.parametrize("start,end,labels,historical", [
|
|
("2026-07-16T05:08:00+08:00", "2026-07-16T08:08:00+08:00", ["07:45", "08:00"],
|
|
["2026-07-15T07:45:00+08:00", "2026-07-15T08:00:00+08:00"]),
|
|
("2026-07-15T22:08:00+08:00", "2026-07-16T01:08:00+08:00", ["00:45", "01:00"],
|
|
["2026-07-15T00:45:00+08:00", "2026-07-15T01:00:00+08:00"]),
|
|
])
|
|
def test_missing_current_chunk_uses_its_first_slot_date(start, end, labels, historical):
|
|
from app.services.time_comparison_chart import validate_time_comparison_chart
|
|
|
|
validate_time_comparison_chart({"chart_type": "time_comparison", "x_data": labels,
|
|
"scada": {"role": "comparison", "window_start": start, "window_end": end},
|
|
"series": [{"type": "line", "data": [None, None], "sample_times": [None, None]},
|
|
{"type": "scatter", "data": [1., 2.], "sample_times": historical}]},
|
|
allow_missing_current=True)
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_missing_current_preserves_observed_historical_sampling_phase():
|
|
task, repository, service, evidence_id = await investigation()
|
|
data = repository.evidence[evidence_id]["data_snapshot"]["data"]["data"]
|
|
data["readings"] = [{**row, "time": (datetime.fromisoformat(row["time"]) + timedelta(minutes=5)).isoformat()}
|
|
for row in data["readings"] if datetime.fromisoformat(row["time"]) < START]
|
|
value = chart("comparison", ["524529"])
|
|
value["x_data"] = [(START + timedelta(minutes=15 * i + 5, hours=8)).strftime("%H:%M") for i in range(12)]
|
|
for item in value["series"]:
|
|
day = item["day_offset"]
|
|
item["data"] = [float(i + 1) if day and i >= 9 else None for i in range(12)]
|
|
item["sample_times"] = [(START + timedelta(minutes=15 * i + 5, days=-day)).isoformat()
|
|
if day and i >= 9 else None for i in range(12)]
|
|
# The final three corresponding slots are now 23:20,23:35,23:50 UTC.
|
|
for day in range(1, 8):
|
|
data["readings"].append({"device_id": "524529", "time": (START + timedelta(minutes=140, days=-day)).isoformat(),
|
|
"monitored_value": 10.})
|
|
await publish(task, repository, service, evidence_id, "historical-phase", value)
|
|
|
|
|
|
@pytest.mark.anyio
|
|
@pytest.mark.parametrize("point_only", [False, True])
|
|
async def test_six_hour_devices_with_no_scheduled_slots_require_full_absence_proof(point_only):
|
|
task, repository, service, evidence_id = await investigation()
|
|
end = END + timedelta(hours=2)
|
|
start = end - timedelta(hours=3)
|
|
task["trigger"]["occurred_at"] = end.isoformat()
|
|
snapshot = repository.evidence[evidence_id]["data_snapshot"]["data"]
|
|
snapshot["window"] = {"start": (end if point_only else start - timedelta(days=7)).isoformat(), "end": end.isoformat()}
|
|
snapshot["data"]["readings"] = []
|
|
for device in snapshot["data"]["devices"]:
|
|
device["transmission_frequency"] = "6:00:00"
|
|
for role in ("current", "comparison"):
|
|
value = chart(role, ["524529"])
|
|
value["scada"].update(window_start=start.isoformat(), window_end=end.isoformat())
|
|
value["x_data"] = ["10:00"]
|
|
for item in value["series"]:
|
|
item["data"], item["sample_times"] = [None], [None]
|
|
if point_only:
|
|
with pytest.raises(DomainError) as failure:
|
|
await publish(task, repository, service, evidence_id, role, value)
|
|
assert failure.value.code == "SCADA_CHART_EVIDENCE_REQUIRED"
|
|
else:
|
|
await publish(task, repository, service, evidence_id, role, value)
|
|
gaps = repository.events[-1]["metadata"]["command"]["chart"]["scada"]["gaps"]
|
|
assert all(gap["reason"] == "no_scheduled_samples" for gap in gaps)
|
|
if not point_only:
|
|
await service.publish_artifacts(task["task_id"], AgentArtifactsCommand(
|
|
request_id=uuid4(), source_session_id="session-1", report={**final_report(evidence_id), "scada_device_ids": ["524529"]}))
|