"""B08 좌측 패널 옵션 길(PLAN 122 · 124) — 배관 1본 길이 · 연암 / 경암 비율 · 할증 모래 · 자갈 · 시멘트. GET /api/projects/{id}/quantity/options 지금 값 PUT /api/projects/{id}/quantity/options [저장] — 받은 칸만 정본 자리에 씀 1본 길이 = 프로젝트 층 M02 구조물집계표 변수 `파형강관_1본_m` 한 벌(98 `one_pipe_length` 이 읽는 자리 · M02 표 식 `[$파형강관_1본_m]` 도 같은 값) · 프로젝트 사본이 없으면 시스템 층 문서를 바탕으로 깔고 씀 · M02 [초기화]면 같이 되돌아감. 연암 % = `project_settings.json › quantity › rock_ratios_pct`(연암 · 경암 = 100 − 연암) · 기본 90 은 파일에 안 쓰고 읽을 때 채움(`soft_rock_pct`). 할증 % = `quantity › material_surcharge_pct`(모래 · 자갈 · 시멘트 · 받은 칸만 · 기본 12 · 5 · 3 은 읽을 때 채움 `panel_surcharge`) — 총괄자재수량집계표 그 세 줄이 씀. """ from __future__ import annotations import asyncio from pathlib import Path from typing import Annotated, Any, Literal from uuid import UUID from fastapi import APIRouter, HTTPException from pydantic import BaseModel, Field from B08_Quantity.B08_Quantity_Material_Calc import PANEL_KEY, panel_surcharge from B08_Quantity.B08_Quantity_Router_Hydro import _project_root from B08_Quantity.B08_Quantity_Router_Summary import ONE_LENGTH_VAR, TABLE_NAME, one_pipe_length from common_util.common_util_project_settings import quantity_settings, save_section, soft_rock_pct from M02_MasterTemplete import M02_Template_Layers as layers router = APIRouter(prefix="/api/projects", tags=["B08 Options"]) class OptionsSave(BaseModel): """빈칸(None) = 그 칸은 그대로.""" one_pipe_length_m: float | None = Field(default=None, gt=0, le=100) soft_rock_pct: float | None = Field(default=None, ge=0, le=100) material_surcharge_pct: ( dict[Literal["sand", "gravel", "cement"], Annotated[float, Field(ge=0, le=100)]] | None ) = None def read_options(root: str | Path) -> dict[str, Any]: settings = quantity_settings(root) soft = soft_rock_pct(settings) return { "one_pipe_length_m": one_pipe_length(root), "rock_ratios_pct": {"연암": soft, "경암": 100.0 - soft}, PANEL_KEY: panel_surcharge(settings.get(PANEL_KEY)), } def _write_one_pipe(root: str | Path, value: float) -> None: """프로젝트 층 구조물집계표 변수 한 칸 — 판이 그새 바뀌었으면 `StaleTemplate`.""" folder = layers.project_dir(root) found = layers.read_template(folder, "table", TABLE_NAME) version = found["판"] if found else None found = found or layers.read_template(layers.system_dir(), "table", TABLE_NAME) if found is None: raise HTTPException(status_code=404, detail=f"{TABLE_NAME} 양식이 없습니다 — M02 표부터.") document = found["문서"] document["변수"] = {**(document.get("변수") or {}), ONE_LENGTH_VAR: value} layers.write_template( folder, "table", TABLE_NAME, document, version=version, check_version=True ) def save_options(root: str | Path, body: OptionsSave) -> dict[str, Any]: if body.one_pipe_length_m is not None: _write_one_pipe(root, body.one_pipe_length_m) if body.soft_rock_pct is not None: soft = float(body.soft_rock_pct) ratios = { **(quantity_settings(root).get("rock_ratios_pct") or {}), "연암": soft, "경암": 100.0 - soft, } save_section( root, "quantity", {"rock_ratios_pct": ratios}, replace_keys=("rock_ratios_pct",) ) if body.material_surcharge_pct: stored = quantity_settings(root).get(PANEL_KEY) merged = {**(stored if isinstance(stored, dict) else {}), **body.material_surcharge_pct} save_section(root, "quantity", {PANEL_KEY: merged}, replace_keys=(PANEL_KEY,)) return read_options(root) @router.get("/{project_id}/quantity/options") async def options_read(project_id: UUID) -> dict[str, Any]: root = await _project_root(project_id) return await asyncio.to_thread(read_options, root) @router.put("/{project_id}/quantity/options") async def options_save(project_id: UUID, body: OptionsSave) -> dict[str, Any]: root = await _project_root(project_id) try: return await asyncio.to_thread(save_options, root, body) except layers.StaleTemplate as error: raise HTTPException( status_code=409, detail="구조물집계표가 그새 바뀌었습니다 — 다시 열고 저장하세요." ) from error