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:
@@ -204,3 +204,44 @@ def test_legacy_execution_timeout_does_not_block_owner_authorization(monkeypatch
|
||||
assert args.timeout_seconds <= 240
|
||||
assert row['parameters']['timeout_seconds'] == 3600
|
||||
authorize.assert_awaited_once()
|
||||
|
||||
|
||||
def test_large_burst_result_keeps_saved_map_command_and_full_evidence(monkeypatch):
|
||||
from copy import deepcopy
|
||||
from unittest.mock import AsyncMock
|
||||
from app.mcp import calculation_jobs as jobs
|
||||
from app.mcp.calculation_models import CalculationResultInput
|
||||
from app.mcp.models import QueryResult
|
||||
original = binding()
|
||||
original.task_id = uuid4()
|
||||
full = {"parameters": {"burst_pipe_ids": ["P1"]}, "result": {
|
||||
"impact": {"phase": "after_burst_before_intervention", "status": "ready",
|
||||
"compared_node_count": 1000, "comparisons": [{"node_id": f"J{i}", "pressure_drop_m": 4} for i in range(1000)],
|
||||
"grades": [{"group_id": "drop_3_5", "node_ids": [f"J{i}" for i in range(1000)]}],
|
||||
"unavailable_node_ids": []},
|
||||
"coverage": {"status": "ready", "groups": {"drop_3_5": {"node_ids": [f"J{i}" for i in range(1000)],
|
||||
"mapped_node_count": 1000, "missing_node_ids": []}}},
|
||||
"map_payload": {"type": "FeatureCollection", "features": [{"large": "recorded-geometry"}]},
|
||||
}}
|
||||
result = QueryResult(source='run_burst_simulation', source_ref='mcp:burst', queried_at=datetime.now(timezone.utc),
|
||||
semantics='analysis', data=full, evidence_id='saved-burst')
|
||||
row = {'job_id': uuid4(), 'binding': durable_binding(original), 'status': 'succeeded', 'result': result.model_dump(mode='json')}
|
||||
persisted = deepcopy(row)
|
||||
monkeypatch.setattr(jobs, 'CalculationJobRepository', lambda *a: SimpleNamespace(get=AsyncMock(return_value=row)))
|
||||
monkeypatch.setattr(jobs, 'authorize_original', AsyncMock())
|
||||
monkeypatch.setattr(jobs, 'attach_calculation_evidence', AsyncMock(side_effect=lambda scope, row, result: result))
|
||||
scope = SimpleNamespace(connection=None, project=SimpleNamespace(project_id=original.project_id, user_id=uuid4()))
|
||||
args = CalculationResultInput(context_handle='x' * 32, job_id=row['job_id'])
|
||||
response = asyncio.run(jobs.read_calculation_job(scope, original, SimpleNamespace(name='get_calculation_result'), args))
|
||||
assert response.truncated and len(response.model_dump_json().encode()) < 11000
|
||||
assert response.data['result']['impact']['compared_node_count'] == 1000
|
||||
assert response.data['result']['impact']['grades'][0]['node_count'] == 1000
|
||||
assert response.data['presentation']['command']['evidence_id'] == 'saved-burst'
|
||||
assert response.data['presentation']['command']['action'] == 'set_overlay'
|
||||
assert response.data['resource_uri'].endswith('/evidence/saved-burst')
|
||||
assert 'comparisons' not in response.data['result']['impact']
|
||||
assert row == persisted
|
||||
# Explicit path pagination still reads every original node without summary replacement.
|
||||
page = asyncio.run(jobs.read_calculation_job(scope, original, SimpleNamespace(name='get_calculation_result'),
|
||||
args.model_copy(update={'data_path': ['result', 'impact', 'comparisons'], 'offset': 990, 'limit': 10})))
|
||||
assert page.data[0]['node_id'] == 'J990' and page.page.total == 1000
|
||||
|
||||
Reference in New Issue
Block a user