"""B05 경로 설계 요청·응답 검증 모델.""" from typing import Any from pydantic import BaseModel, ConfigDict, Field, model_validator from config.config_system import ROUTE_GRADE_CLASSES class RoutePoint(BaseModel): """경로 제어점 (BP/CP/EP).""" model_config = ConfigDict(extra="forbid") x: float y: float order: int | None = None class CirclePoint(BaseModel): """회피/금지 원 (AP/FP).""" model_config = ConfigDict(extra="forbid") x: float y: float radius_m: float = Field(gt=0) class RouteSolveRequest(BaseModel): """경로 탐색 실행 요청.""" model_config = ConfigDict(extra="forbid") filter_key: str = Field(description="지면 필터 키 (grid_min_z/csf/pmf)") method: str = Field(default="dtm", description="지표면 표현 (dtm/tin/nurbs/implicit/meshfree)") smooth: bool = Field(default=False) surface_model_id: int | None = Field(default=None, description="기반 지표면 모델 id") algorithm: str = Field(default="dijkstra", description="경로 알고리즘 (dijkstra/ridge_valley)") bp: RoutePoint ep: RoutePoint cp: list[RoutePoint] = Field(default_factory=list) ap: list[CirclePoint] = Field(default_factory=list) fp: list[CirclePoint] = Field(default_factory=list) grade_class: str = Field(default="trunk") paved: bool = Field(default=False) min_curve_radius_m: float | None = None max_uphill_grade: float | None = None max_downhill_grade: float | None = None weights: dict[str, float] | None = None allow_avoid_pass_through: bool = Field(default=False) @model_validator(mode="after") def validate_choices(self) -> "RouteSolveRequest": if self.grade_class not in ROUTE_GRADE_CLASSES: raise ValueError(f"임도 등급은 {ROUTE_GRADE_CLASSES} 중 하나여야 합니다.") if self.algorithm not in ("dijkstra", "ridge_valley"): raise ValueError("경로 알고리즘은 dijkstra 또는 ridge_valley여야 합니다.") return self def points_data(self) -> dict[str, Any]: return { "bp": self.bp.model_dump(), "ep": self.ep.model_dump(), "cp": [p.model_dump() for p in self.cp], "ap": [p.model_dump() for p in self.ap], "fp": [p.model_dump() for p in self.fp], } def options(self) -> dict[str, Any]: return { "grade_class": self.grade_class, "paved": self.paved, "min_curve_radius_m": self.min_curve_radius_m, "max_uphill_grade": self.max_uphill_grade, "max_downhill_grade": self.max_downhill_grade, "weights": self.weights, "allow_avoid_pass_through": self.allow_avoid_pass_through, } class RouteSolveResponse(BaseModel): """경로 탐색 실행 결과.""" status: str = "success" project_id: str route_id: int total_length_m: float metrics: dict[str, Any] required_points_ok: bool route_data_path: str class RouteConfirmResponse(BaseModel): """경로 확정 결과.""" status: str = "success" project_id: str route_id: int confirmed: bool = True