41 lines
1.9 KiB
Python
41 lines
1.9 KiB
Python
"""Require persisted complete SCADA input before final investigation conclusions."""
|
||
|
||
from datetime import datetime
|
||
|
||
from app.domain.domain_errors import require
|
||
|
||
|
||
def require_complete_scada_evidence(task: dict, evidence: list[dict], refs: list[str]) -> None:
|
||
replay = task.get("source", {}).get("replay_context")
|
||
if task.get("workflow") != "scada_anomaly" and not replay:
|
||
return
|
||
target = datetime.fromisoformat(
|
||
replay["knowledge_cutoff"] if replay else task["trigger"]["occurred_at"]
|
||
)
|
||
for item in evidence:
|
||
if item["evidence_id"] not in refs:
|
||
continue
|
||
snapshot = item.get("data_snapshot") or {}
|
||
if snapshot.get("source") != "query_scada_readings" or snapshot.get("truncated"):
|
||
continue
|
||
result = snapshot.get("data") or {}
|
||
data = result.get("data") if isinstance(result, dict) else None
|
||
coverage = data.get("coverage") if isinstance(data, dict) else None
|
||
if not coverage or coverage.get("mode") != "all_scada" or coverage.get("query_complete") is not True:
|
||
continue
|
||
window = result.get("window") or {}
|
||
if not window.get("start") or not window.get("end"):
|
||
continue
|
||
start, end = (datetime.fromisoformat(window[key]) for key in ("start", "end"))
|
||
if not start <= target <= end:
|
||
continue
|
||
if replay and (coverage.get("replay_id") != replay["replay_id"]
|
||
or coverage.get("batch_sequence") != replay["batch_sequence"]
|
||
or end != target):
|
||
continue
|
||
return
|
||
require(False, "SCADA_COVERAGE_REQUIRED",
|
||
"最终SCADA报告必须引用本轮全量读取证据。调用query_scada_readings并省略device_ids,"
|
||
"在任务时间窗内读取全部授权设备、完整属性与观测,核对coverage和缺测清单后再发布。",
|
||
status=409)
|