"""B09 원가계산 길(PLAN 144 · 145) — 결과는 저장하지 않고 GET 때 서버가 셈(B08 꼴). GET /api/projects/{id}/estimation 입력(굳힌 단가 · 수요 · B08 수량) + 사용자 값 → 표 전부 PUT /api/projects/{id}/estimation {user} [저장] — 사용자 값 · 책에 없는 새 단가 줄만 굳힘 → 서버가 정본으로 다시 셈 POST /api/projects/{id}/estimation/prices [단가 갱신] — 수요 단가 전부를 지금 M01 값으로 새로 굳힘 저장 자리 = 프로젝트 안 `B09_Estimation/v1/`(`B09_Estimation_Store`) · 단가 = `B09_Estimation_Prices` · 수요 = `B09_Estimation_Linked`(연결된 사본) · 셈 = `B09_Estimation_Calc_*`(화면 짝과 같은 식). """ from __future__ import annotations import asyncio from pathlib import Path from typing import Any from uuid import UUID from fastapi import APIRouter, Body from B08_Quantity.B08_Quantity_Router_Earthwork import earthwork_table from B08_Quantity.B08_Quantity_Router_Hydro import _project_root from B08_Quantity.B08_Quantity_Router_Material import material_sheets from B09_Estimation import B09_Estimation_Prices as prices from B09_Estimation import B09_Estimation_Store as store from B09_Estimation.B09_Estimation_Bill_Items import bill_items, hopyo_prices from B09_Estimation.B09_Estimation_Calc_Bill import compute_estimation from B09_Estimation.B09_Estimation_Linked import linked_resource_keys from B09_Estimation.B09_Estimation_Summary import compute_summary, hopyo_lines router = APIRouter(prefix="/api/projects", tags=["B09 Estimation"]) async def _b08(project_id: UUID, notes: list[str]) -> dict[str, Any]: """B08 수량 원천 — 못 읽으면 빈칸 + 알림(B09 는 B08 를 고치지 않음).""" out: dict[str, Any] = {} for key, name, read in ( ("materials", "자재 집계표", material_sheets), ("earthwork", "토공집계표", earthwork_table), ): try: out[key] = await read(project_id) except Exception as error: # noqa: BLE001 — 원천 하나가 막혀도 나머지 표는 그림 notes.append(f"B08 {name} 못 읽음 — {getattr(error, 'detail', error)}") out[key] = None # 구조물 집계표 = 자재 집계표가 이미 부른 값(열 · 계 · 조합) — 두 번 안 부름 out["summary"] = (out["materials"] or {}).get("source") return out async def payload(project_id: UUID, root: Path) -> dict[str, Any]: notes: list[str] = [] b08 = await _b08(project_id, notes) keys = await asyncio.to_thread(linked_resource_keys, root) if not keys: notes.append( "연결된 구조물 조합 사본 없음 — 노무 · 경비 · 중기 단가 줄은 사본이 연결되면 생김" ) book = await asyncio.to_thread(store.read_book, root) inputs = await asyncio.to_thread(prices.build_inputs, book, keys, b08["materials"]) inputs["bill"] = bill_items(b08) inputs["hopyo"] = hopyo_prices(root) user = await asyncio.to_thread(store.read_user, root) return { "inputs": inputs, "user": user, "book": {"made": book.get("made")} if book else None, "sheets": compute_estimation(inputs, user), "notes": notes, # 집계표 넷 = 서버 단독(145-5) — 짝 셈 결과를 역전개 · 조작 중엔 안 바뀜 "summary": compute_summary(compute_estimation(inputs, user), hopyo_lines(root)), } @router.get("/{project_id}/estimation") async def estimation(project_id: UUID) -> dict[str, Any]: root = await _project_root(project_id) return await payload(project_id, root) @router.put("/{project_id}/estimation") async def save_estimation(project_id: UUID, body: dict[str, Any] = Body(...)) -> dict[str, Any]: root = await _project_root(project_id) await asyncio.to_thread(store.write_user, root, body.get("user")) keys = await asyncio.to_thread(linked_resource_keys, root) book = await asyncio.to_thread(store.read_book, root) book = await asyncio.to_thread(prices.extend_book, book, keys) await asyncio.to_thread(store.write_book, root, book) return await payload(project_id, root) @router.post("/{project_id}/estimation/prices") async def refresh_prices(project_id: UUID) -> dict[str, Any]: root = await _project_root(project_id) keys = await asyncio.to_thread(linked_resource_keys, root) book = await asyncio.to_thread(prices.refresh_book, keys) await asyncio.to_thread(store.write_book, root, book) return await payload(project_id, root)