일감 3 의 API·화면 덩어리. 토적표 오른쪽 절반(실무 V~AI)이 화면에 섬. 한 응답으로 냄 실무 토적표가 한 장이라 화면도 한 장임. 나눠 부르면 두 번 왕복하고 같은 측점 목록을 두 벌로 들게 됨. earthwork-table 응답에 slope 를 함께 실음. 열 구성 — 계열 4 x 성토면/절토면 = (거리, 면적) 7쌍 층따기[성토면] · 면고르기 · 법면보호공(종자파종) · 지장목제거. 거리 = 그 측점 사면길이, 면적은 토적표와 같은 평균단면적법. 합계행은 면적만 — 거리(사면길이)는 합이 뜻이 없음(실무 시트도 비움). ⚠ 잘린 측점을 감추지 않음 (PLAN 8-4b) 사면이 원지반을 못 만난 측점은 사면길이·면적이 그 지점에서 잘려 있음. 경고 문구에 **무엇이 잘렸는지**를 적고(「실제 값은 이보다 큼」), 측점 단추를 누르면 그 줄로 가서 잠깐 강조됨 — 17곳을 눈으로 찾게 하지 않음. 잘린 줄은 측점 칸 왼쪽에 표시가 남음. 검증 — 공용 브라우저 실화면. 타입검사 오류 0. 머리글 3단 13/12/28셀, 대분류에 층따기·면고르기·법면보호공·지장목제거, 3단에 거리/면적 쌍. 본문 65행 x 34열(토공 20 + 사면 14). NO.1 사면값 6.4/64.4 · 3.5/93.0 등이 엔진값과 일치. 잘린 줄 17개 표시 · 경고 문구 · 측점 단추 17개 확인. 합계 면고르기 성토 13,518.6㎡ · 절토 5,433.7㎡ 로 엔진 합계와 같음. 회귀 404 passed (실패 1건은 B05 코리도 기존 깨짐). 조작한 화면은 사용자가 보던 주소로 되돌려 놓음. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
73 lines
3.1 KiB
Python
73 lines
3.1 KiB
Python
"""B08 토적표 조회 라우터 (일감 2 · PLAN 8-4b).
|
|
|
|
값은 어디서 오나
|
|
측점별 단면적은 **B06 이 이미 낸 정본**이다(`cross_sections.data.design` 의
|
|
`cut_soil_area_m2`·`cut_rock_area_m2`·`fill_area_m2`·`ditch_area_m2`).
|
|
B08 은 그것을 다시 재지 않고 **평균단면적법으로 체적화만** 한다.
|
|
|
|
계산 자리 (CLAUDE.md 5장)
|
|
초기값은 서버가 한 번 계산해 영구저장한다. 여기서는 저장된 단면적을 읽어 표를 만든다 —
|
|
새 수량을 낳지 않으므로 캐시·조작 경로가 따로 필요 없다.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from typing import Any
|
|
from uuid import UUID
|
|
|
|
from fastapi import APIRouter
|
|
from fastapi.responses import JSONResponse
|
|
|
|
from B06_Section.B06_Section_Repository import (
|
|
get_cross_section_designs,
|
|
get_workflow_route_context,
|
|
)
|
|
from B08_Quantity.B08_Quantity_Engine_EarthworkTable import StationArea, build_table
|
|
from B08_Quantity.B08_Quantity_Engine_SlopeArea import build_table as build_slope_table
|
|
from B08_Quantity.B08_Quantity_Engine_SlopeLength import station_slopes
|
|
from config.config_db import run_with_connection
|
|
|
|
logger = logging.getLogger(__name__)
|
|
router = APIRouter(prefix="/api/projects", tags=["B08 Quantity"])
|
|
|
|
|
|
def _stations(designs: list[dict[str, Any]]) -> list[StationArea]:
|
|
return [
|
|
StationArea.from_design(item["chainage_m"], item.get("design") or {}) for item in designs
|
|
]
|
|
|
|
|
|
@router.get("/{project_id}/quantity/{route_id}/earthwork-table")
|
|
async def get_earthwork_table(project_id: UUID, route_id: int) -> JSONResponse:
|
|
"""토적표 — 토공(체적)과 사면 4계열(면적)을 **한 응답**으로 낸다.
|
|
|
|
실무 토적표가 한 장이라 화면도 한 장이다. 나눠 부르면 두 번 왕복하고, 같은 측점 목록을
|
|
두 벌로 들게 된다.
|
|
"""
|
|
try:
|
|
designs = await run_with_connection(get_cross_section_designs, route_id)
|
|
except Exception:
|
|
logger.exception("B08 토적표 조회 실패: project_id=%s route_id=%s", project_id, route_id)
|
|
return JSONResponse(
|
|
status_code=500,
|
|
content={"status": "error", "message": "토적표를 만들지 못했습니다."},
|
|
)
|
|
table = build_table(_stations(designs))
|
|
# 사면 계열은 저장된 설계선에서 유도한다 — 반영률은 기본 100 %(설계자 입력은 후속).
|
|
table["slope"] = build_slope_table(station_slopes(designs))
|
|
table["route_id"] = route_id
|
|
return JSONResponse(content=table)
|
|
|
|
|
|
@router.get("/{project_id}/quantity/earthwork-table")
|
|
async def get_earthwork_table_for_current_route(project_id: UUID) -> JSONResponse:
|
|
"""경로를 안 주면 워크플로가 보고 있는 경로로 낸다 — 화면이 route_id 를 모를 때 쓴다."""
|
|
context = await run_with_connection(get_workflow_route_context, project_id)
|
|
if not context or not context.get("route_id"):
|
|
return JSONResponse(
|
|
status_code=404,
|
|
content={"status": "error", "message": "이 프로젝트에 확정된 노선이 없습니다."},
|
|
)
|
|
return await get_earthwork_table(project_id, int(context["route_id"]))
|