- 탭 = 기번3 공통 엑셀 보이는 탭 차례(목차 · 안내 = 연결 점검) · 탭 하나 = 공용 표 하나 · 호표 칸 누름 = 그 탭 그 줄로 이동 - 저장 = 프로젝트 B09_Estimation/v1(사용자 값 · 굳힌 단가 한 벌) · 결과는 GET 때 서버 셈 · [저장] · [단가 갱신] 길 - 줄 = 수요 따라(연결된 사본 원천 자리 · 기계 → 운전사 · 연료 · B08 총괄 자재) · 연결 없으면 빈칸 + 연결 점검 목록 - 셈 = 파이썬 · TS 짝 + 거울(운전사 시간 노임 · 자재 최저 자동 + 사용자 고침 · 관급 · 별도 · 중기 손료 + 운전사 + 연료 + 잡품 · 엑셀 끝수) - 공용 표 = 칸 누름 · 줄로 이동(강조) 더함 - 시험 = test_144_b09_skeleton · test_144_b09_base_calc(엑셀 중기 126,000 · 76,436 · 96,829 · 40,659 · 운전사 55,700 · 47,231 · 33,571) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015KYZ2HWWdUwZWYnn3tQz7X
110 lines
3.9 KiB
Python
110 lines
3.9 KiB
Python
"""B09 원가 저장 자리(PLAN 144-1) — 프로젝트 안 `B09_Estimation/v1/`.
|
|
|
|
price_book.json 굳힌 단가 한 벌 {"made": 시각, "rows": {키: 입력 줄}, "driver_factor": …}
|
|
— [단가 갱신] 때만 M01 로 새로 · [저장] 때 책에 없는 새 줄만 더함(있는 줄 안 바꿈)
|
|
user_values.json 사용자 값 {"materials": {자재 키: {applied?, supply?}}, "estimate": {…}}
|
|
— 고친 칸만(키가 있으면 그 값 · null = 빈칸 · 없으면 기본)
|
|
|
|
결과(표)는 저장하지 않음 — GET 때 서버가 셈(B08 꼴).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import math
|
|
import os
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from B09_Estimation.B09_Estimation_Calc_Base import SUPPLY
|
|
|
|
FOLDER = ("B09_Estimation", "v1")
|
|
BOOK = "price_book.json"
|
|
USER = "user_values.json"
|
|
|
|
#: 원가계산 사용자 입력 칸 — 요율 고르는 값 · 손입력(엑셀 총괄 · 원가계산서 · 내역 손입력 칸)
|
|
ESTIMATE_FIELDS = (
|
|
"estimated_price", # 추정가격(원) — 요율 구간 고름
|
|
"duration_days", # 공사기간(일)
|
|
"work_type", # 공종(글)
|
|
"safety_base", # 산업안전보건관리비 기초액(원)
|
|
"profit_adjust", # 이윤 보정(원 · 빼는 값)
|
|
"vat_adjust", # 부가가치세 조정(원)
|
|
"indirect_material", # 간접재료비(원)
|
|
"scrap", # 작업설 부산물(원 · 빼는 값)
|
|
"procurement_fee_pct", # 조달수수료율(%)
|
|
)
|
|
|
|
|
|
def folder(project_root: str | Path) -> Path:
|
|
return Path(project_root).joinpath(*FOLDER)
|
|
|
|
|
|
def _read(path: Path) -> dict[str, Any] | None:
|
|
try:
|
|
data = json.loads(path.read_text(encoding="utf-8"))
|
|
except (OSError, ValueError):
|
|
return None
|
|
return data if isinstance(data, dict) else None
|
|
|
|
|
|
def _write(path: Path, data: dict[str, Any]) -> None:
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
temp = path.with_suffix(".tmp")
|
|
temp.write_text(json.dumps(data, ensure_ascii=False, indent=1), encoding="utf-8")
|
|
os.replace(temp, path)
|
|
|
|
|
|
def read_book(project_root: str | Path) -> dict[str, Any] | None:
|
|
book = _read(folder(project_root) / BOOK)
|
|
if book is None or not isinstance(book.get("rows"), dict):
|
|
return None
|
|
return book
|
|
|
|
|
|
def write_book(project_root: str | Path, book: dict[str, Any]) -> None:
|
|
_write(folder(project_root) / BOOK, book)
|
|
|
|
|
|
def _num(value: Any) -> float | None:
|
|
if isinstance(value, bool) or not isinstance(value, (int, float)):
|
|
return None
|
|
return float(value) if math.isfinite(value) else None
|
|
|
|
|
|
def clean_user(raw: Any) -> dict[str, Any]:
|
|
"""사용자 값 다듬기 — 모르는 칸 버림 · 수 칸은 수 · null(빈칸)만."""
|
|
raw = raw if isinstance(raw, dict) else {}
|
|
materials: dict[str, dict[str, Any]] = {}
|
|
for key, cell in (raw.get("materials") or {}).items():
|
|
if not isinstance(cell, dict):
|
|
continue
|
|
out: dict[str, Any] = {}
|
|
if "applied" in cell:
|
|
out["applied"] = _num(cell["applied"])
|
|
if cell.get("supply") in SUPPLY:
|
|
out["supply"] = cell["supply"]
|
|
if out:
|
|
materials[str(key)] = out
|
|
estimate: dict[str, Any] = {}
|
|
given = raw.get("estimate") if isinstance(raw.get("estimate"), dict) else {}
|
|
for field in ESTIMATE_FIELDS:
|
|
if field not in given:
|
|
continue
|
|
value = given[field]
|
|
estimate[field] = (str(value) if value else None) if field == "work_type" else _num(value)
|
|
rates = given.get("rates")
|
|
if isinstance(rates, dict):
|
|
estimate["rates"] = {str(k): _num(v) for k, v in rates.items()}
|
|
return {"materials": materials, "estimate": estimate}
|
|
|
|
|
|
def read_user(project_root: str | Path) -> dict[str, Any]:
|
|
return clean_user(_read(folder(project_root) / USER))
|
|
|
|
|
|
def write_user(project_root: str | Path, user: dict[str, Any]) -> dict[str, Any]:
|
|
clean = clean_user(user)
|
|
_write(folder(project_root) / USER, clean)
|
|
return clean
|