"""B08 토공 탭 길 (PLAN 61-3 · 69-2) — 읽기만, 아무 파일 · DB 에도 쓰지 않음. GET /api/projects/{id}/quantity/earthwork 토적표 + 토공집계표 + 평균운반거리 · 종방향 무대운반거리 운반 Q · L = 종단 레코드에 저장된 유토곡선 배분(`mass_haul.haul_plan`) — B07 토적도와 같은 정본. 단면적 = B06 횡단 설계 저장값(`cross_sections.data.design`) · 노선 = 확정 노선(없으면 최신 노선) · 토량환산계수 = 유토곡선과 같은 `mixed_conversion_factors(quantity_settings)`. """ from __future__ import annotations import asyncio from typing import Any from uuid import UUID from fastapi import APIRouter, HTTPException from B06_Section.B06_Section_Repository import ( get_confirmed_route_context, get_cross_section_designs, get_longitudinal_section, get_workflow_route_context, ) from B08_Quantity.B08_Quantity_Engine_Earthwork import StationArea, build_table from B08_Quantity.B08_Quantity_Engine_Earthwork_Summary import haul_lists, summary_rows from B08_Quantity.B08_Quantity_Router_Hydro import _project_root from common_util.common_util_project_settings import mixed_conversion_factors, quantity_settings from config.config_db import get_db_pool router = APIRouter(prefix="/api/projects", tags=["B08 Earthwork"]) @router.get("/{project_id}/quantity/earthwork") async def earthwork_table(project_id: UUID) -> dict[str, Any]: root = await _project_root(project_id) async with get_db_pool().acquire() as connection: context = await get_confirmed_route_context(connection, project_id) confirmed = context is not None context = context or await get_workflow_route_context(connection, project_id) if not context: raise HTTPException(status_code=404, detail="노선이 없습니다 — 노선 · 횡단 설계부터.") route_id = int(context["route_id"]) rows = await get_cross_section_designs(connection, route_id) section = await get_longitudinal_section(connection, project_id, route_id) factors = await asyncio.to_thread(lambda: mixed_conversion_factors(quantity_settings(root))) stations = [StationArea.from_design(row["chainage_m"], row["design"]) for row in rows] table = build_table(stations, factors) mass_haul = ((section or {}).get("data") or {}).get("mass_haul") or {} haul = haul_lists(mass_haul.get("haul_plan")) return { **table, "summary": summary_rows(table["totals"], haul), "haul": haul, "haul_saved": bool(mass_haul.get("haul_plan")), "route_id": route_id, "confirmed": confirmed, }