feat(server): integrate MCP queries and investigation messaging
Add project-bound OAuth MCP queries and controlled analysis with scoped evidence, limits, timeouts and a nearby-model spatial query using EPSG:4326 inputs. Default nearby searches to 200 meters and five results. Integrate investigation messaging/presentation, health/readiness and typed history; remove duplicate web search and fix online simulation and compressed baseline writes. Include project migrations 023-025; keep local credentials and demo seeding excluded. Validation: 319 focused pytest checks; real PostGIS temporary-table regression; read-only Jinqian bridge radius comparison; cross-language MCP discovery/calls.
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
from datetime import datetime, timedelta
|
||||
from unittest.mock import Mock
|
||||
|
||||
import pytest
|
||||
|
||||
from app.services import simulation
|
||||
|
||||
|
||||
START = datetime.fromisoformat("2026-10-08T12:00:00+08:00")
|
||||
|
||||
|
||||
def test_online_simulation_uses_realtime_steps_and_returns_requested_samples(monkeypatch):
|
||||
calls = []
|
||||
mappings = object()
|
||||
monkeypatch.setattr(simulation, "get_time", lambda _: {"HYDRAULIC TIMESTEP": "0:15:00"})
|
||||
monkeypatch.setattr(simulation, "query_corresponding_element_id_and_query_id", lambda _: mappings)
|
||||
|
||||
def run(**kwargs):
|
||||
calls.append(kwargs)
|
||||
return {
|
||||
"units": {"pressure": "MTR", "flow": "LPS"},
|
||||
"node_results": [{"node": "123422", "result": [{"pressure": len(calls)}]}, {"node": "other", "result": [{}]}],
|
||||
"link_results": [{"link": "540672", "result": [{"flow": len(calls)}]}],
|
||||
}
|
||||
|
||||
monkeypatch.setattr(simulation, "run_simulation", run)
|
||||
result = simulation.run_online_simulation("online", START, 3600, output_node_ids=["123422"], output_link_ids=["540672"])
|
||||
assert calls == [{
|
||||
"name": "online", "simulation_type": "realtime",
|
||||
"modify_pattern_start_time": (START + timedelta(minutes=15 * index)).isoformat(timespec="seconds"),
|
||||
"scada_mappings": mappings,
|
||||
} for index in range(4)]
|
||||
assert result["node_results"] == [{"node": "123422", "result": [{"pressure": index} for index in range(1, 5)]}]
|
||||
assert result["link_results"] == [{"link": "540672", "result": [{"flow": index} for index in range(1, 5)]}]
|
||||
assert result["times"]["num_periods"] == 4
|
||||
assert result["times"]["report_step"] == 900
|
||||
assert result["times"]["sample_times"][-1] == "2026-10-08T12:45:00+08:00"
|
||||
|
||||
|
||||
def test_online_simulation_rejects_invalid_step_before_calculating(monkeypatch):
|
||||
monkeypatch.setattr(simulation, "get_time", lambda _: {"HYDRAULIC TIMESTEP": "0:00:00"})
|
||||
run = Mock()
|
||||
monkeypatch.setattr(simulation, "run_simulation", run)
|
||||
with pytest.raises(ValueError, match="HYDRAULIC TIMESTEP"):
|
||||
simulation.run_online_simulation("online", START, 3600)
|
||||
run.assert_not_called()
|
||||
|
||||
|
||||
def test_mcp_online_simulation_does_not_copy_a_database(monkeypatch):
|
||||
from app.mcp.scenario_service import calculate_scenario
|
||||
|
||||
def reject_copy(*_):
|
||||
pytest.fail("Online simulation must not clone the project database")
|
||||
|
||||
run = Mock(return_value={"node_results": [], "link_results": [], "times": {}, "units": {}})
|
||||
monkeypatch.setattr("app.native.wndb.core.projects.temporary_project_database", reject_copy)
|
||||
monkeypatch.setattr(simulation, "run_online_simulation", run, raising=False)
|
||||
result = calculate_scenario("run_simulation", {
|
||||
"start_time": START, "duration": 3600,
|
||||
"output_node_ids": ["123422", "123425"], "output_link_ids": ["540672"],
|
||||
}, network="online", username="actor")
|
||||
run.assert_called_once_with("online", START, 3600, output_node_ids=["123422", "123425"], output_link_ids=["540672"])
|
||||
assert result["input_source"] == "online_model_simulation"
|
||||
assert result["persisted_baseline"] is True
|
||||
assert result["persisted_scheme"] is False
|
||||
Reference in New Issue
Block a user