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:
@@ -11,7 +11,7 @@ from app.native.wndb.model.tanks import get_tank, set_tank
|
||||
from app.native.wndb.model.times import get_time, set_time
|
||||
|
||||
# from get_real_status import *
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timedelta
|
||||
from math import modf
|
||||
import json
|
||||
import pytz
|
||||
@@ -150,6 +150,69 @@ def _apply_valve_control(
|
||||
set_status(project_name, cs)
|
||||
|
||||
|
||||
def run_online_simulation(
|
||||
name: str,
|
||||
start_time: datetime,
|
||||
duration_seconds: int,
|
||||
*,
|
||||
output_node_ids: list[str] | None = None,
|
||||
output_link_ids: list[str] | None = None,
|
||||
) -> dict:
|
||||
"""Recalculate online baseline snapshots without cloning a model database."""
|
||||
if start_time.tzinfo is None or duration_seconds <= 0:
|
||||
raise ValueError("Online simulation requires an aware start time and positive duration")
|
||||
step = parse_clock_duration_seconds(
|
||||
get_time(name)["HYDRAULIC TIMESTEP"], field_name="HYDRAULIC TIMESTEP"
|
||||
)
|
||||
if step <= 0:
|
||||
raise ValueError("HYDRAULIC TIMESTEP must be greater than 0.")
|
||||
mappings = query_corresponding_element_id_and_query_id(name)
|
||||
selected = {
|
||||
"node": {node: [] for node in output_node_ids or []},
|
||||
"link": {link: [] for link in output_link_ids or []},
|
||||
}
|
||||
sample_times = []
|
||||
units = {}
|
||||
current = start_time
|
||||
end = start_time + timedelta(seconds=duration_seconds)
|
||||
while current < end:
|
||||
output = run_simulation(
|
||||
name=name,
|
||||
simulation_type="realtime",
|
||||
modify_pattern_start_time=current.isoformat(timespec="seconds"),
|
||||
scada_mappings=mappings,
|
||||
)
|
||||
if not isinstance(output, dict):
|
||||
raise RuntimeError("Online simulation did not return calculation results")
|
||||
units = output.get("units", {})
|
||||
sample_times.append(current.isoformat(timespec="seconds"))
|
||||
for kind, elements in selected.items():
|
||||
if not elements:
|
||||
continue
|
||||
rows = {
|
||||
str(row[kind]): row.get("result", [])
|
||||
for row in output.get(f"{kind}_results", []) if str(row[kind]) in elements
|
||||
}
|
||||
for element, values in elements.items():
|
||||
samples = rows.get(element, [])
|
||||
# Each realtime run is a single snapshot. Missing samples remain
|
||||
# explicit so later values cannot shift to an earlier timestamp.
|
||||
values.append(samples[0] if samples else None)
|
||||
current += timedelta(seconds=step)
|
||||
return {
|
||||
"units": units,
|
||||
"times": {
|
||||
"report_start": 0, "report_step": step,
|
||||
"sim_duration": duration_seconds, "num_periods": len(sample_times),
|
||||
"sample_times": sample_times,
|
||||
},
|
||||
**{f"{kind}_results": [
|
||||
{kind: element, "result": values}
|
||||
for element, values in elements.items() if any(value is not None for value in values)
|
||||
] for kind, elements in selected.items()},
|
||||
}
|
||||
|
||||
|
||||
# 2025/01/11
|
||||
def run_simulation(
|
||||
name: str,
|
||||
@@ -170,7 +233,8 @@ def run_simulation(
|
||||
scheme_username: str = "system",
|
||||
scheme_detail: dict | None = None,
|
||||
scada_mappings: ScadaElementMappings | None = None,
|
||||
) -> UUID | None:
|
||||
persist_results: bool = True,
|
||||
) -> UUID | dict | None:
|
||||
"""
|
||||
传入需要修改的参数,改变数据库中对应位置的值,然后计算,返回结果
|
||||
:param name: 模型名称,数据库中对应的名字
|
||||
@@ -189,6 +253,10 @@ def run_simulation(
|
||||
:param scheme_name:模拟方案名称
|
||||
:return:
|
||||
"""
|
||||
if not persist_results and (
|
||||
not name.startswith("tjw_tmp_") or simulation_type.upper() != "EXTENDED"
|
||||
):
|
||||
raise ValueError("Non-persistent simulation requires an isolated temporary model")
|
||||
# 记录开始时间
|
||||
time_cost_start = time.perf_counter()
|
||||
|
||||
@@ -514,6 +582,8 @@ def run_simulation(
|
||||
num_periods_result = times_info.get("num_periods")
|
||||
if num_periods_result is None:
|
||||
raise RuntimeError("run_project output missing times.num_periods")
|
||||
if not persist_results:
|
||||
return output_data
|
||||
print("simulation_type", simulation_type)
|
||||
print("before store result")
|
||||
# print(num_periods_result)
|
||||
@@ -583,4 +653,6 @@ def run_simulation(
|
||||
# TimescaleInternalQueries.fill_scheme_simulation_result_to_SCADA(scheme_type=scheme_type, scheme_name=scheme_name)
|
||||
|
||||
print("after store result")
|
||||
if simulation_type.upper() == "REALTIME":
|
||||
return output_data
|
||||
return run_id if simulation_type.upper() == "EXTENDED" else None
|
||||
|
||||
Reference in New Issue
Block a user