feat(b09): 운반 단가 모달 · 로직 미리보기 길 (253-3)

- POST /api/projects/{id}/estimation/logic-preview — 로직 한 산근을 프로젝트 단가로 풀어 계산 절차 글 · 단가(저장 안 함)
- 「운반 단가」 모달 — 단가산출 이름 · [변경] · [다른 종류] · 옵션 값(로직 입력 + 사이클 입력 · 표 고르기 CC000054~058) · 계산 절차(사이클 식 + 미리보기)
- 여는 곳 = B08 거리이정표 표 우클릭 [단가연결] · B09 설계내역서 운반공 줄 단가연결 · 운반 항목에 거리(dist)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JywHxQuWPk6UYXTebyrBoU
This commit is contained in:
2026-10-11 14:10:38 +09:00
co-authored by Claude Opus 5.5
parent 01bca00e3c
commit cb2db4ff49
7 changed files with 537 additions and 20 deletions
+41 -1
View File
@@ -6,6 +6,9 @@
[저장] — 사용자 값 · 책에 없는 새 단가 줄만 굳힘 → 서버가 정본으로 다시 셈
POST /api/projects/{id}/estimation/prices
[단가 갱신] — 수요 단가 전부를 지금 M01 값으로 새로 굳힘
POST /api/projects/{id}/estimation/logic-preview {code, given, rate, picks}
「운반 단가」 모달 계산 절차(253-3) — 그 로직 한 산근을 프로젝트 단가로 풀어 글 줄 · 단가 ·
저장 안 함(`render_sangeun_text` 를 `Sangeun.Solver` 로)
POST /api/projects/{id}/estimation/library {kind, layer, owner, key}
설계내역서 연결 고르기(204-2) — 서고 구조물조합 · 사전조합을 프로젝트 사본으로
(있으면 그대로) → {폴더, 만듦, key = 대가 블록 id, payload} ·
@@ -25,7 +28,7 @@ from typing import Any
from uuid import UUID
from fastapi import APIRouter, Body, Depends, HTTPException
from pydantic import BaseModel
from pydantic import BaseModel, Field
from B08_Quantity.B08_Quantity_Haul import haul_rows
from B08_Quantity.B08_Quantity_Milestone_Calc import compute_milestone
@@ -206,6 +209,43 @@ async def refresh_prices(project_id: UUID) -> dict[str, Any]:
return await payload(project_id, root)
class PreviewBody(BaseModel):
code: str = Field(min_length=1, max_length=40)
given: dict[str, Any] = Field(default_factory=dict)
rate: float | None = None
picks: dict[str, Any] | None = None
def logic_preview(root: Path, body: PreviewBody) -> dict[str, Any]:
"""로직 한 산근 미리보기 — 단가 = 굳힌 책(없는 줄 = 지금 M01) · 사용자 대비표 고름."""
src = {"code": body.code, "given": sangeun._clean_given(body.given), "rate": body.rate,
"picks": body.picks} # fmt: skip
hands = [("preview", "미리보기", src)]
keys = sangeun.hand_resource_keys({"haul_refs": {"preview": {"tab": "logic", **src}}})
user = store.read_user(root)
base = compute_base(prices.build_inputs(store.read_book(root), keys), user)
block = sangeun.Solver(base).add(sangeun.hand_whole(hands), src, None)
text = [
*block["head"],
*(t for ln in block["lines"] for t in ln.get("text") or []),
block["foot"],
]
return {
"name": block["name"],
"lines": [t for t in text if t],
"unit": block["unit"],
"price": block["sum"],
"parts": {p: block[p] for p in sangeun.PARTS},
"why": block["why"],
}
@router.post("/{project_id}/estimation/logic-preview")
async def preview_logic(project_id: UUID, body: PreviewBody) -> dict[str, Any]:
root = await _project_root(project_id)
return await asyncio.to_thread(logic_preview, root, body)
class LibraryBody(BaseModel):
kind: str
layer: str