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
+109
View File
@@ -371,3 +371,112 @@ def _timestamp(value: datetime | str) -> float:
else datetime.fromisoformat(value.replace("Z", "+00:00"))
)
return parsed.timestamp()
# Persisted runtime queries shared by REST and MCP.
from contextlib import asynccontextmanager
import logging
from pydantic import ValidationError
from app.auth.project_dependencies import ProjectContext
from app.infra.db.postgresql.analysis import AnalysisRepository
from app.infra.db.postgresql.workbench_operations import WorkbenchOperationsRepository
from app.domain.schemas.workbench_runtime import WorkbenchRuntimeSnapshot
logger = logging.getLogger(__name__)
@asynccontextmanager
async def _projection_transaction(conn):
transaction = getattr(conn, "transaction", None)
if transaction is None:
yield
return
async with transaction():
yield
async def _sensor_location_map_payload(conn, results):
node_ids = sensor_location_ids_for_map_projection(results)
if not node_ids:
return None
points = await AnalysisRepository.list_junction_map_points(conn,node_ids)
return project_sensor_location_map_payload(node_ids,points)
def _validate_documents(documents, model, label):
valid=[];rejected=0
for document in documents:
try:valid.append(model.model_validate(document).model_dump())
except ValidationError:
rejected+=1
logger.warning("Workbench %s document was rejected",label)
return valid,rejected
async def read_workbench_runtime(ctx: ProjectContext, conn):
analysis_runs: list[dict] | None = None
latest_results: list[dict] | None = None
latest_analysis_map_payload: dict | None = None
analysis_availability = "not_configured"
alert_storage_configured = False
alerts: list[dict] | None = None
schedule_availability = "not_configured"
alert_availability = "not_configured"
try:
async with _projection_transaction(conn):
analysis_runs = await AnalysisRepository.list_recent_runs(conn, limit=100)
if analysis_runs:
latest_results = await AnalysisRepository.list_recent_results(
conn, analysis_runs[0]["run_id"], limit=100
)
analysis_availability = "ready" if analysis_runs else "not_configured"
except Exception:
logger.warning("Workbench analysis projection is unavailable", exc_info=True)
analysis_runs = None
latest_results = None
analysis_availability = "degraded"
if analysis_runs and latest_results:
try:
async with _projection_transaction(conn):
latest_analysis_map_payload = await _sensor_location_map_payload(
conn, latest_results
)
except Exception:
logger.warning(
"Workbench analysis spatial references are unavailable",
exc_info=True,
)
try:
async with _projection_transaction(conn):
operations_availability = await WorkbenchOperationsRepository.availability(conn)
alert_storage_configured = operations_availability.alerts
except Exception:
logger.warning("Workbench operations storage probe is unavailable", exc_info=True)
schedule_availability = "degraded"
alert_availability = "degraded"
if alert_storage_configured:
try:
async with _projection_transaction(conn):
alert_documents = await WorkbenchOperationsRepository.list_alerts(
conn, str(ctx.project_id)
)
alerts, invalid_alerts = _validate_documents(
alert_documents, WorkbenchPersistedAlert, "alert"
)
alert_availability = "degraded" if invalid_alerts else "ready"
except Exception:
alerts = None
alert_availability = "degraded"
logger.warning("Workbench alert projection is unavailable", exc_info=True)
return await build_workbench_runtime_snapshot(
analysis_runs=analysis_runs,
latest_analysis_results=latest_results,
latest_analysis_map_payload=latest_analysis_map_payload,
alert_storage_configured=alert_storage_configured,
persisted_alerts=alerts,
scenario_availability=analysis_availability,
artifact_availability=analysis_availability,
schedule_availability=schedule_availability,
alert_availability=alert_availability,
projection_degraded=False,
)