init
This commit is contained in:
151
schemas.py
Normal file
151
schemas.py
Normal file
@@ -0,0 +1,151 @@
|
||||
from pydantic import BaseModel, Field, field_validator, model_validator
|
||||
from typing import Literal
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Input
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class RawStop(BaseModel):
|
||||
name: str | None = None
|
||||
address: str
|
||||
city: str | None = None
|
||||
contact: str | None = None
|
||||
|
||||
@field_validator("name", "address", "city", "contact", mode="before")
|
||||
@classmethod
|
||||
def strip_optional_strings(cls, value: str | None) -> str | None:
|
||||
if value is None:
|
||||
return None
|
||||
text = value.strip()
|
||||
return text or None
|
||||
|
||||
@field_validator("address")
|
||||
@classmethod
|
||||
def validate_address(cls, value: str) -> str:
|
||||
if not value:
|
||||
raise ValueError("stop address cannot be empty")
|
||||
return value
|
||||
|
||||
|
||||
class RoutePlanRequest(BaseModel):
|
||||
task_name: str = Field(default="multi-destination-route-planning")
|
||||
origin_mode: Literal["fixed", "current_location"]
|
||||
origin_name: str | None = None
|
||||
origin_address: str | None = None
|
||||
origin_city: str | None = None
|
||||
destination_name: str | None = None
|
||||
destination_address: str
|
||||
destination_city: str | None = None
|
||||
stops: list[RawStop]
|
||||
route_strategy: Literal["shortest_distance", "fastest_time", "balanced"] = "shortest_distance"
|
||||
transport_mode: Literal["driving"] = "driving"
|
||||
need_deep_link: bool = True
|
||||
deep_link_mode: Literal["personal_map", "route_plan", "auto"] = "auto"
|
||||
need_html: bool = False
|
||||
max_permutations: int | None = None
|
||||
|
||||
@field_validator(
|
||||
"task_name",
|
||||
"origin_name",
|
||||
"origin_address",
|
||||
"origin_city",
|
||||
"destination_name",
|
||||
"destination_address",
|
||||
"destination_city",
|
||||
mode="before",
|
||||
)
|
||||
@classmethod
|
||||
def strip_request_strings(cls, value: str | None) -> str | None:
|
||||
if value is None:
|
||||
return None
|
||||
text = value.strip()
|
||||
return text or None
|
||||
|
||||
@field_validator("destination_address")
|
||||
@classmethod
|
||||
def validate_destination_address(cls, value: str) -> str:
|
||||
if not value:
|
||||
raise ValueError("destination_address cannot be empty")
|
||||
return value
|
||||
|
||||
@field_validator("max_permutations")
|
||||
@classmethod
|
||||
def validate_max_permutations(cls, value: int | None) -> int | None:
|
||||
if value is not None and value <= 0:
|
||||
raise ValueError("max_permutations must be greater than 0")
|
||||
return value
|
||||
|
||||
@model_validator(mode="after")
|
||||
def validate_route_request(self) -> "RoutePlanRequest":
|
||||
if not self.stops:
|
||||
raise ValueError("stops must contain at least one stop")
|
||||
|
||||
if self.origin_mode == "fixed" and not self.origin_address:
|
||||
raise ValueError("origin_address is required when origin_mode='fixed'")
|
||||
|
||||
destination_key = self.destination_address.casefold()
|
||||
duplicate_stop = next(
|
||||
(stop.address for stop in self.stops if stop.address.casefold() == destination_key),
|
||||
None,
|
||||
)
|
||||
if duplicate_stop is not None:
|
||||
raise ValueError("destination_address cannot also appear in stops")
|
||||
|
||||
return self
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Output
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class ResolvedPoint(BaseModel):
|
||||
role: Literal["origin", "stop", "destination"]
|
||||
input_name: str | None = None
|
||||
input_address: str
|
||||
resolved_name: str
|
||||
city: str | None = None
|
||||
district: str | None = None
|
||||
location: str # "lon,lat" format as returned by Amap
|
||||
lon: float
|
||||
lat: float
|
||||
poi_id: str | None = None
|
||||
source: Literal["geo", "text_search", "search_detail", "manual_fallback"]
|
||||
confidence_note: str | None = None
|
||||
|
||||
|
||||
class RouteLeg(BaseModel):
|
||||
from_label: str
|
||||
to_label: str
|
||||
origin_location: str
|
||||
destination_location: str
|
||||
distance_m: int
|
||||
duration_s: int
|
||||
|
||||
|
||||
class CandidateRoute(BaseModel):
|
||||
stop_order_labels: list[str]
|
||||
full_order_labels: list[str]
|
||||
legs: list[RouteLeg]
|
||||
total_distance_m: int
|
||||
total_duration_s: int
|
||||
ranking_reason: str | None = None
|
||||
|
||||
|
||||
class DeepLinks(BaseModel):
|
||||
personal_map: str | None = None
|
||||
android_route_plan: str | None = None
|
||||
ios_route_plan: str | None = None
|
||||
|
||||
|
||||
class RoutePlanResult(BaseModel):
|
||||
success: bool
|
||||
origin_mode: Literal["fixed", "current_location"]
|
||||
resolved_origin: ResolvedPoint | None = None
|
||||
resolved_destination: ResolvedPoint
|
||||
resolved_stops: list[ResolvedPoint]
|
||||
candidates: list[CandidateRoute]
|
||||
best_route: CandidateRoute
|
||||
deep_links: DeepLinks | None = None
|
||||
summary: str
|
||||
warnings: list[str]
|
||||
Reference in New Issue
Block a user