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.
29 lines
898 B
Python
29 lines
898 B
Python
from typing import Literal
|
|
|
|
from pydantic import BaseModel, ConfigDict, Field
|
|
|
|
MAX_PLACE_SEARCH_RESULTS = 10
|
|
|
|
|
|
class PlaceSearchRequest(BaseModel):
|
|
model_config = ConfigDict(str_strip_whitespace=True, extra="forbid")
|
|
|
|
keyword: str = Field(..., min_length=1, max_length=120)
|
|
|
|
|
|
class PlaceSearchItem(BaseModel):
|
|
id: str = Field(..., min_length=1)
|
|
name: str = Field(..., min_length=1)
|
|
address: str
|
|
longitude: float = Field(..., ge=-180, le=180, allow_inf_nan=False)
|
|
latitude: float = Field(..., ge=-90, le=90, allow_inf_nan=False)
|
|
category: str | None = None
|
|
district: Literal["奉贤区"] = "奉贤区"
|
|
|
|
|
|
class PlaceSearchResponse(BaseModel):
|
|
items: list[PlaceSearchItem] = Field(..., max_length=MAX_PLACE_SEARCH_RESULTS)
|
|
total: int = Field(..., ge=0, le=MAX_PLACE_SEARCH_RESULTS)
|
|
truncated: bool = False
|
|
region: Literal["奉贤区"] = "奉贤区"
|