Files
Aislo/B09_Estimation/B09_Estimation_Router.py
T
eomsangdonandClaude Opus 5.5 489a682794 feat(B09): 설계내역서 — 공종 나무 엑셀 꼴 · B08 수량 · 호표 단가 · 관급 · 원가계산서 이음(145-3)
- 공종 나무 = 엑셀 꼴 고정(토공사 1.1~1.6 · 구조물공 2.1~2.3 · 운반공 · 자재대 · 관급자재대 제외금액) · 공구 안 나눔
- 항목 짜기(서버) = B08 토공집계표 짝 · 구조물 집계표 열(구조물군 → 공종 짝 · 커플링 뺌) · 운반공 수량 = B08 총괄자재
- 조합 밖 토공 · 운반 산근 · 준비공 = 빈칸 「연결 없음」 · 구조물 단가 = 대가 호표 자리(145-2 hopyo)
- 4 자재대 = 대비표 「별도」 · 관급자재대 = 「관급」 표시 + 조달수수료(율 사용자 입력 · ROUNDDOWN 0)
- 셈 = 파이썬 · TS 짝 + 거울 · 금액 ROUNDDOWN(단가 × 수량, 0) 비목마다 · 제목 = 아는 금액 합 + 빈 줄 수
- 원가계산서 이음 = sheets.bill.sums {material · labor · expense · supply_net} · 빈 줄 있으면 null
- 시험 = test_145_3_b09_bill(엑셀 G9 361,494 · G14 11,003,825 · 관급 69,474,220 + 375,160 = 69,849,380)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015KYZ2HWWdUwZWYnn3tQz7X
2026-10-09 15:13:05 +09:00

99 lines
4.2 KiB
Python

"""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
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,
}
@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)