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:
2026-10-19 18:46:58 +08:00
parent 2b6fc5aa52
commit be6317eb48
117 changed files with 12703 additions and 638 deletions
+35 -2
View File
@@ -38,10 +38,10 @@ def test_valve_close_analysis_uses_normalized_scheme_type(monkeypatch):
monkeypatch.setattr(
simulation_scenarios.simulation,
"run_simulation",
lambda **kwargs: captured.update(kwargs),
lambda **kwargs: (captured.update(kwargs), {"node_results": []})[1],
)
simulation_scenarios.valve_close_analysis.__wrapped__(
saved_result = simulation_scenarios.valve_close_analysis.__wrapped__(
name="demo",
modify_pattern_start_time="2026-01-01T00:00:00+08:00",
modify_valve_opening={"V1": 0.0},
@@ -51,6 +51,13 @@ def test_valve_close_analysis_uses_normalized_scheme_type(monkeypatch):
assert captured["scheme_type"] == "valve_close_analysis"
assert captured["result_db_name"] == "demo"
assert saved_result is None # Preserve the existing REST "success" response.
preview = simulation_scenarios.valve_close_analysis.__wrapped__(
name="demo", modify_pattern_start_time="2026-01-01T00:00:00+08:00",
_temporary_project="tjw_tmp_demo", persist_results=False,
)
assert preview == {"node_results": []}
assert captured["persist_results"] is False
def test_apply_valve_control_matches_runner_semantics(monkeypatch):
@@ -208,6 +215,32 @@ def test_extended_simulation_stores_results_by_run_id(monkeypatch):
assert transaction_calls == [("begin", "demo"), ("end", "demo")]
refresh_mock.assert_called_once_with("demo")
preview = simulation.run_simulation(
name="tjw_tmp_preview_test", simulation_type="extended",
modify_pattern_start_time="2026-07-16T00:00:00+08:00",
modify_total_duration=900, persist_results=False,
scada_mappings=_empty_scada_mappings(simulation),
)
assert preview["node_results"][0]["node"] == "J1"
assert len(storage_calls) == len(create_calls) == 1
assert len(lifecycle_calls) == 1
with pytest.raises(ValueError, match="isolated temporary model"):
simulation.run_simulation(name="demo", simulation_type="extended",
modify_pattern_start_time="2026-07-16T00:00:00+08:00", persist_results=False)
realtime_storage = Mock()
monkeypatch.setattr(simulation.TimescaleInternalStorage, "store_realtime_simulation", realtime_storage)
realtime = simulation.run_simulation(
name="demo", simulation_type="realtime",
modify_pattern_start_time="2026-07-16T00:00:00+08:00",
scada_mappings=_empty_scada_mappings(simulation),
)
realtime_storage.assert_called_once_with(
realtime["node_results"], realtime["link_results"],
"2026-07-16T00:00:00+08:00", db_name="demo",
)
assert len(storage_calls) == len(create_calls) == len(lifecycle_calls) == 1
def test_extended_simulation_marks_run_failed_when_result_storage_fails(monkeypatch):
from app.services import simulation