fix(scada): align location capability and investigation guidance
Generic Container CI/CD / test-build-publish (push) Successful in 5m25s
Server CI/CD v2 / build-test-publish-and-deploy (push) Successful in 5m26s

This commit is contained in:
2026-11-07 20:14:24 +08:00
parent bf8ea45d40
commit 01bad116c1
9 changed files with 59 additions and 21 deletions
+2
View File
@@ -131,6 +131,8 @@ def test_location_capability_and_tool_contract_do_not_inject_mock_instructions()
assert "mock" not in str(context) and "模拟" not in context["notice"]
tool = TOOLS["run_burst_location"].protocol_tool()
assert "相似度" in tool.description
assert "location_capability.available=true" in tool.description
assert "location_demo" not in tool.description
assert "demo模拟候选" not in tool.description
assert "明确标记的演示候选" not in str(tool.input_schema)
+13 -2
View File
@@ -49,6 +49,16 @@ def test_work_order_handling_and_legacy_scada_replay_receive_separate_skills():
assert "run_burst_location" in replay["instructions"]
def test_scada_instructions_advance_candidate_analysis_independently_of_source_labels():
skill = read_work_order_skill("scada_anomaly", include_content=True)
text = skill["instructions"]
assert "location_capability" in text
assert "证据来源字段用于记录数据取得方式,不构成停止分析的条件" in text
assert "隔离边界、节点覆盖与影响分析,完成报告后再收尾" in text
assert "现场工单与设备操作遵守服务端动作权限" in text
assert not any(word in text for word in ("演示", "mock", "location_demo", "只发布回放成果"))
def test_task_skill_uses_persisted_workflow_and_preserves_task_scope(monkeypatch):
task_id, project_id = uuid4(), uuid4()
reader = AsyncMock(return_value={"task": {"workflow": "burst_report"}})
@@ -103,7 +113,8 @@ def test_replay_context_advertises_demo_location_without_revealing_truth(monkeyp
for section in ("summary", "skill"):
result = asyncio.run(query_service.execute_query(scope, binding, TOOLS["get_task_context"],
TaskContextInput(context_handle=binding.context_handle, section=section)))
capability = result.data["location_demo"]
capability = result.data["location_capability"]
assert "location_demo" not in result.data
assert capability["available"] is True
assert capability["tool"] == "run_burst_location"
assert capability["burst_leakage_required"] is False
@@ -113,4 +124,4 @@ def test_replay_context_advertises_demo_location_without_revealing_truth(monkeyp
binding.replay_context["knowledge_cutoff"] = "2026-07-15T23:45:00Z"
result = asyncio.run(query_service.execute_query(scope, binding, TOOLS["get_task_context"],
TaskContextInput(context_handle=binding.context_handle)))
assert result.data["location_demo"]["available"] is False
assert result.data["location_capability"]["available"] is False
+25 -1
View File
@@ -10,7 +10,7 @@ from fastapi import HTTPException
from app.mcp.analysis_models import BurstLocationInput
from app.mcp.catalog import TOOLS
from app.mcp.models import BoundContext
from app.mcp.models import BoundContext, ScadaInput
from app.mcp import query_service
from app.services.scada_replay_queries import replay_burst_location_inputs, replay_snapshot_rows
@@ -18,6 +18,30 @@ from app.services.scada_replay_queries import replay_burst_location_inputs, repl
START = datetime(2026, 10, 8, tzinfo=timezone.utc)
def test_replay_origin_is_preserved_without_a_provenance_only_data_gap(monkeypatch):
from app.services import scada_replay_queries
monkeypatch.setattr(query_service, "authorize_query", AsyncMock())
reading = {"time": START.isoformat(), "device_id": "P1", "cleaned_value": 20.0,
"monitored_value": 20.0, "quality": "valid"}
read = AsyncMock(return_value=[reading])
monkeypatch.setattr(scada_replay_queries, "replay_snapshot_rows", read)
project_id = uuid4()
context = {"replay_id": str(uuid4()), "knowledge_cutoff": (START + timedelta(minutes=15)).isoformat(),
"batch_sequence": 9}
connection = object()
scope = SimpleNamespace(connection=connection, project=SimpleNamespace(project_id=project_id))
binding = SimpleNamespace(replay_context=context)
args = ScadaInput(context_handle="a" * 43, device_ids=["P1"], start=START,
end=START + timedelta(minutes=15))
result = asyncio.run(query_service.execute_query(scope, binding, TOOLS["query_scada_readings"], args))
assert result.origin == "synthetic"
assert result.data[0]["device_id"] == "P1"
assert result.data_gaps == []
read.assert_awaited_once_with(connection, project_id, context, ["P1"], args.start, args.end,
args.limit + 1, args.offset)
class Cursor:
def __init__(self, rows): self.rows = rows
async def fetchone(self): return self.rows[0] if self.rows else None