- 측구 줄 맨 오른쪽 [상세] · 투명 막 · 카드 안 가리는 넓은 쪽 · 카드 다시 그리면 갱신 - 구조물 글 structure_note — USER_TOUCHED_KEYS 두 벌 · CrossSectionPatch · 세션 초안 → [저장] - 표 값 = 서버 cross-quantity-table 한 길(B07 _quantity_table 을 캐시 설계로) · 칸 배치 B07 그대로 - test_138_cross_detail Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JywHxQuWPk6UYXTebyrBoU
95 lines
3.8 KiB
Python
95 lines
3.8 KiB
Python
"""B06 횡단 [상세] 모달의 **B07 횡단 수량표 값** 창구(138-4).
|
|
|
|
값은 B07 횡단도가 쓰는 `_quantity_table` 한 벌을 **브라우저 캐시 설계**로 돌린 것이다 —
|
|
화면이 TS 로 다시 짜지 않는다(CLAUDE.md 5장 ③ 서버 단독). 칸 배치도 B07 표 정의를 그대로
|
|
실어 보내 화면은 그리기만 한다. **표시 전용** — 정본을 쓰지 않는다. 모달을 열 때 부른다.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import logging
|
|
from pathlib import Path
|
|
from typing import Any
|
|
from uuid import UUID
|
|
|
|
from fastapi import APIRouter
|
|
from fastapi.responses import JSONResponse
|
|
from pydantic import BaseModel, Field
|
|
|
|
from B03_FileInput.B03_FileInput_Repository import get_project_storage_relative_path
|
|
from B06_Section.B06_Section_Repository import get_longitudinal_section
|
|
from B06_Section.B06_Section_Router_Design import read_cross_design_inputs
|
|
from B07_DesignDetail.B07_DesignDetail_Engine_Cad import _format
|
|
from B07_DesignDetail.B07_DesignDetail_Engine_Cad_Table import (
|
|
_CROSS_HEADER_KEYS,
|
|
_CROSS_LEFT_ROWS,
|
|
_CROSS_MIDDLE_ROWS,
|
|
_CROSS_RIGHT_ROWS,
|
|
)
|
|
from B07_DesignDetail.B07_DesignDetail_Router_Support import _quantity_table
|
|
from common_util.common_util_storage import resolve_stored_project_path
|
|
from config.config_db import get_db_pool
|
|
|
|
logger = logging.getLogger(__name__)
|
|
router = APIRouter(prefix="/api/projects", tags=["B06 Profile Cross"])
|
|
|
|
|
|
class CrossQuantityTableRequest(BaseModel):
|
|
chainage_m: float = Field(..., ge=0)
|
|
# 브라우저 캐시의 그 측점 설계(조작 반영본) — 없으면 원본 칸만.
|
|
design: dict[str, Any] | None = None
|
|
|
|
|
|
def cross_quantity_sheet(source: dict[str, Any], design: dict[str, Any] | None) -> dict[str, Any]:
|
|
"""B07 횡단 수량표 한 장 — 값(숫자) · 글(B07 표와 같은 꼴) · 칸 배치."""
|
|
table = _quantity_table(source, design)
|
|
text = {
|
|
key: _format(value) if isinstance(value, (int, float)) else "-"
|
|
for key, value in table.items()
|
|
}
|
|
return {
|
|
"quantity_table": table,
|
|
"text": text,
|
|
"layout": {
|
|
"header": [list(row) for row in _CROSS_HEADER_KEYS],
|
|
"left": [list(row) for row in _CROSS_LEFT_ROWS],
|
|
"middle": [list(row) for row in _CROSS_MIDDLE_ROWS],
|
|
"right": [list(row) for row in _CROSS_RIGHT_ROWS],
|
|
},
|
|
}
|
|
|
|
|
|
@router.post("/{project_id}/sections/{route_id}/cross-quantity-table", response_model=None)
|
|
async def cross_quantity_table(
|
|
project_id: UUID, route_id: int, request: CrossQuantityTableRequest
|
|
) -> JSONResponse:
|
|
pool = get_db_pool()
|
|
async with pool.acquire() as connection:
|
|
longitudinal = await get_longitudinal_section(connection, project_id, route_id)
|
|
stored_path = await get_project_storage_relative_path(connection, project_id)
|
|
if not longitudinal or not stored_path:
|
|
return JSONResponse(
|
|
status_code=404,
|
|
content={"status": "error", "message": "종횡단 상세 결과가 없습니다."},
|
|
)
|
|
try:
|
|
*_, source = await asyncio.to_thread(
|
|
read_cross_design_inputs,
|
|
Path(resolve_stored_project_path(stored_path)),
|
|
str(longitudinal["longitudinal_file_path"]),
|
|
request.chainage_m,
|
|
)
|
|
sheet = cross_quantity_sheet(source, request.design)
|
|
except (FileNotFoundError, ValueError) as error:
|
|
return JSONResponse(status_code=404, content={"status": "error", "message": str(error)})
|
|
except Exception:
|
|
logger.exception(
|
|
"횡단 수량표 계산 실패: project_id=%s chainage=%s", project_id, request.chainage_m
|
|
)
|
|
return JSONResponse(
|
|
status_code=500,
|
|
content={"status": "error", "message": "횡단 수량표를 만들지 못했습니다."},
|
|
)
|
|
return JSONResponse(content={"status": "success", **sheet})
|