Files
Aislo/B07_DesignDetail/B07_DesignDetail_Router_PlanKinds.py
T
eomsangdonandClaude Opus 5.5 138e80c79f feat(B07): 계획평면도 종류 · 정보 조각 서버 (PLAN 60-1 · 60-2)
- 정보 조각(등고선 · 계류 · 중심선 · 성토사면 외곽선 · 비탈 눈금 · IP · R · 측점 NO · 시종점 · 대피소/사토장 · 라이다 배경)마다 장별 파일(plan_pieces/{장}/{조각}.json)
- 외곽선 = 노견 끝 + 설계선이 지반에 닿는 자리(B06 횡단 설계 결과) · 측점 자리는 횡단 파일 중심 · 좌축
- 종류 = 조각 고르기 목록(plan_kinds/{id}.json) · 기본 넷(지형 · 노선배치도 · 대피소 · 라이다) · 더하기 · 지우기 API
- 대피소 · 사토장 = 구조물 정본 refuge · spoil_bank + 사용자 지정 자리(plan_marks.json)
- 도면 목록 · 단건 조회를 종류 × 장으로 · 옛 plan_* id 그대로 이어짐

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NPJGtfXo6GMbcpiboQC4uK
2026-10-06 17:51:40 +09:00

99 lines
3.2 KiB
Python

"""B07 계획평면도 종류 · 대피소 자리 라우터 (PLAN 60장).
종류 = 정보 조각 고르기 목록. 더하기 · 지우기는 파일 하나씩(`plan_kinds/{id}.json`)이다.
대피소 · 사토장 자리는 구조물 정본 밖에서 사용자가 더 정한 자리(`plan_marks.json`)다.
"""
import asyncio
import logging
from typing import Any
from uuid import UUID
from fastapi import APIRouter
from fastapi.responses import JSONResponse
from pydantic import BaseModel, Field
from B07_DesignDetail.B07_DesignDetail_Engine_Cad_PlanKinds import PIECES
from B07_DesignDetail.B07_DesignDetail_Router_Frame import _project_root
from B07_DesignDetail.B07_DesignDetail_Router_Support_Plan import (
add_kind,
delete_kind,
list_kinds,
read_marks,
write_marks,
)
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/projects", tags=["B07 Design Detail"])
class PlanKindRequest(BaseModel):
name: str
pieces: list[str] = Field(default_factory=list)
class PlanMark(BaseModel):
chainage_m: float = Field(ge=0)
refuge: bool = False
spoil: bool = False
class PlanMarksRequest(BaseModel):
marks: list[PlanMark] = Field(default_factory=list)
def _state(root: Any) -> dict[str, Any]:
return {
"status": "success",
"pieces": [{"id": piece, "name": name} for piece, name, _locked in PIECES],
"kinds": list_kinds(root),
"marks": read_marks(root),
}
def _error(status: int, exc: Exception) -> JSONResponse:
return JSONResponse(status_code=status, content={"status": "error", "message": str(exc)})
@router.get("/{project_id}/plan-kinds")
async def get_plan_kinds(project_id: UUID) -> Any:
"""고를 수 있는 조각 · 지금 종류 · 대피소 자리."""
root = await _project_root(project_id)
return await asyncio.to_thread(_state, root)
@router.post("/{project_id}/plan-kinds")
async def post_plan_kind(project_id: UUID, request: PlanKindRequest) -> Any:
"""종류 더하기 — 조각을 골라 이름을 붙인다."""
try:
root = await _project_root(project_id)
await asyncio.to_thread(add_kind, root, request.name, request.pieces)
return await asyncio.to_thread(_state, root)
except ValueError as exc:
return _error(400, exc)
@router.delete("/{project_id}/plan-kinds/{kind_id}")
async def delete_plan_kind(project_id: UUID, kind_id: str) -> Any:
"""종류 지우기 — 기본 넷도 지울 수 있다."""
try:
root = await _project_root(project_id)
await asyncio.to_thread(delete_kind, root, kind_id)
return await asyncio.to_thread(_state, root)
except ValueError as exc:
return _error(400, exc)
except FileNotFoundError as exc:
return _error(404, exc)
@router.put("/{project_id}/plan-marks")
async def put_plan_marks(project_id: UUID, request: PlanMarksRequest) -> Any:
"""사용자가 정한 대피소 · 사토장 자리를 통째로 갈아 쓴다."""
try:
root = await _project_root(project_id)
marks = [mark.model_dump() for mark in request.marks]
await asyncio.to_thread(write_marks, root, marks)
return await asyncio.to_thread(_state, root)
except ValueError as exc:
return _error(400, exc)