Files
Aislo/B06_Section/B06_Section_Router_CrossTable.py
T

98 lines
4.1 KiB
Python

"""B06 횡단 [상세] 모달의 **B07 횡단 수량표 값** 창구(138-4).
값은 B07 횡단도가 쓰는 `_quantity_table` 한 벌을 **브라우저 캐시 설계**로 돌린 것이다 —
화면이 TS 로 다시 짜지 않는다(CLAUDE.md 5장 ③ 서버 단독). 칸 배치도 B07 표 정의를 그대로
실어 보내 화면은 그리기만 한다. **표시 전용** — 정본을 쓰지 않는다. 모달을 열 때 부른다.
표 손질(179-4 · 고른 칸 · 더한 행 · 열)은 설계 `detail_table` 을 `_Engine_Cross_Table_Edit` 가 읽음.
"""
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_Engine_Cross_Table_Edit import edited_cells, table_layout
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 표와 같은 꼴) · 칸 배치(+ 더한 행 · 열 179-4)."""
table = _quantity_table(source, design)
text = {
key: _format(value) if isinstance(value, (int, float)) else "-"
for key, value in table.items()
}
_values, texts, repick = edited_cells(design)
text.update(texts)
return {
"quantity_table": table,
"text": text,
"layout": table_layout(
design, _CROSS_HEADER_KEYS, _CROSS_LEFT_ROWS, _CROSS_MIDDLE_ROWS, _CROSS_RIGHT_ROWS
),
# 길이 출처가 설계선을 벗어난 칸 — 값 비움 · 화면 「다시 고름」
"repick": repick,
}
@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})