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,99 @@
|
||||
"""Resolve browser overlays only from immutable Server-ingested snapshots."""
|
||||
from __future__ import annotations
|
||||
|
||||
from math import isfinite
|
||||
|
||||
from app.domain.domain_errors import require
|
||||
from app.domain.schemas.operational_data import TaskDataSnapshot
|
||||
|
||||
|
||||
def _number(value) -> bool:
|
||||
return isinstance(value, (int, float)) and not isinstance(value, bool) and isfinite(value)
|
||||
|
||||
|
||||
def _position(value):
|
||||
require(isinstance(value, list) and len(value) in {2, 3}
|
||||
and all(_number(item) for item in value)
|
||||
and -180 <= value[0] <= 180 and -90 <= value[1] <= 90,
|
||||
"OVERLAY_GEOMETRY_INVALID", "Overlay coordinates must be finite WGS84 positions", status=422)
|
||||
return value[:2]
|
||||
|
||||
|
||||
def _geometry(value):
|
||||
require(isinstance(value, dict), "OVERLAY_GEOMETRY_INVALID", "Overlay geometry is missing", status=422)
|
||||
kind = value.get("type")
|
||||
depths = {"Point": 0, "MultiPoint": 1, "LineString": 1,
|
||||
"MultiLineString": 2, "Polygon": 2, "MultiPolygon": 3}
|
||||
require(kind in depths, "OVERLAY_GEOMETRY_INVALID", "Unsupported overlay geometry", status=422)
|
||||
positions = 0
|
||||
|
||||
def coordinates(items, depth):
|
||||
nonlocal positions
|
||||
if depth == 0:
|
||||
positions += 1
|
||||
require(positions <= 20000, "OVERLAY_TOO_LARGE", "Overlay geometry is too large", status=422)
|
||||
return _position(items)
|
||||
require(isinstance(items, list) and 0 < len(items) <= 20000,
|
||||
"OVERLAY_GEOMETRY_INVALID", "Overlay coordinates are empty or too large", status=422)
|
||||
return [coordinates(item, depth - 1) for item in items]
|
||||
|
||||
normalized = coordinates(value.get("coordinates"), depths[kind])
|
||||
lines = ([normalized] if kind == "LineString" else normalized if kind == "MultiLineString" else [])
|
||||
require(all(len(line) >= 2 for line in lines), "OVERLAY_GEOMETRY_INVALID",
|
||||
"Overlay lines need at least two positions", status=422)
|
||||
rings = (normalized if kind == "Polygon" else [ring for polygon in normalized for ring in polygon]
|
||||
if kind == "MultiPolygon" else [])
|
||||
require(all(len(ring) >= 4 and ring[0] == ring[-1] for ring in rings),
|
||||
"OVERLAY_GEOMETRY_INVALID", "Overlay polygon rings must be closed", status=422)
|
||||
return {"type": kind, "coordinates": normalized}
|
||||
|
||||
|
||||
def resolve_overlay(snapshot: TaskDataSnapshot, mode: str) -> dict:
|
||||
"""Use recorded GeoJSON or recorded coordinates; never geocode or infer a boundary."""
|
||||
data = snapshot.data
|
||||
candidates = [data]
|
||||
if isinstance(data, dict):
|
||||
candidates.extend([data.get("map_payload"), data.get("drawing_payload")])
|
||||
latest = data.get("latest_analysis")
|
||||
if isinstance(latest, dict):
|
||||
candidates.append(latest.get("map_payload"))
|
||||
collection = next((item for item in candidates
|
||||
if isinstance(item, dict) and item.get("type") == "FeatureCollection"), None)
|
||||
if collection is not None:
|
||||
raw_features = collection.get("features")
|
||||
require(isinstance(raw_features, list), "OVERLAY_GEOMETRY_INVALID",
|
||||
"Snapshot FeatureCollection is invalid", status=422)
|
||||
else:
|
||||
rows = data.get("items") if isinstance(data, dict) else data
|
||||
require(isinstance(rows, list), "OVERLAY_DATA_MISSING",
|
||||
"Evidence snapshot has no recorded coordinates or GeoJSON", status=422)
|
||||
raw_features = []
|
||||
for row in rows:
|
||||
if not isinstance(row, dict) or row.get("longitude") is None or row.get("latitude") is None:
|
||||
continue
|
||||
raw_features.append({
|
||||
"type": "Feature", "geometry": {"type": "Point", "coordinates": [row["longitude"], row["latitude"]]},
|
||||
"id": row.get("device_id") or row.get("point_id") or row.get("work_order_id") or row.get("id"),
|
||||
"properties": {"weight": row.get("weight", 1)},
|
||||
})
|
||||
require(0 < len(raw_features) <= 1000, "OVERLAY_DATA_MISSING",
|
||||
"Overlay needs between one and 1000 recorded features", status=422)
|
||||
features = []
|
||||
for feature in raw_features:
|
||||
require(isinstance(feature, dict) and feature.get("type") == "Feature",
|
||||
"OVERLAY_GEOMETRY_INVALID", "Snapshot contains an invalid feature", status=422)
|
||||
geometry = _geometry(feature.get("geometry"))
|
||||
require(mode != "heatmap" or geometry["type"] == "Point", "OVERLAY_HEATMAP_POINTS_REQUIRED",
|
||||
"Heatmaps require recorded points, not inferred polygon centroids", status=422)
|
||||
properties = feature.get("properties") or {}
|
||||
require(isinstance(properties, dict), "OVERLAY_GEOMETRY_INVALID", "Feature properties are invalid", status=422)
|
||||
normalized = {"type": "Feature", "geometry": geometry, "properties": dict(properties)}
|
||||
if isinstance(feature.get("id"), (str, int)) and not isinstance(feature.get("id"), bool):
|
||||
normalized["id"] = feature["id"]
|
||||
if mode == "heatmap":
|
||||
weight = properties.get("weight", 1)
|
||||
require(_number(weight) and weight >= 0, "OVERLAY_WEIGHT_INVALID",
|
||||
"Heatmap weight must be a finite nonnegative number", status=422)
|
||||
normalized["properties"]["weight"] = weight
|
||||
features.append(normalized)
|
||||
return {"type": "FeatureCollection", "features": features}
|
||||
Reference in New Issue
Block a user