Files
Aislo/B09_Estimation/B09_Estimation_Copy_Prices.py
T
eomsangdonandClaude Opus 5.5 193ce92806 refactor(841-1): B09 단가 책 · 바탕 셈 걷음 — B 걸음
- 단가 책 = 자재만(자재단가대비표 · 4 자재대 · 관급자재대) · 노무 · 기계 · 운전사 계수(CC000336 세 줄) · 연료 · 환율 줄 걷음
- 바탕 셈(py · ts 짝) 운전사 시간 노임 · 중기사용료(손료 · 운전사 · 연료 · 잡품 재현) · 경비목록표 · 노임 · 기계 덮개 걷음
- 노무비목록표 · 중기목록표 = 대가 · 산근 줄 엔진 단가(프로젝트 사본 M02 값) 나열 · 다시 안 셈(price_lists)
- 경비목록표 · 중기사용료 · 환율및기초자료 탭 = 「—」 한 줄 + 까닭 한 줄(탭은 둠)
- [단가 갱신] = 프로젝트 사본 자원 단가 줄(단가.json LB · MT · EQ · MO · FX)을 지금 M01 로 + 자재 단가 책(새 파일 B09_Estimation_Copy_Prices.py · 사용자가 누를 때만) · 미리 보기 = 덮개로 같은 셈(파일 안 씀)
- 산근 · 대가 단가 고친 칸 걷음(품만) · 단가표 불러오기(707-3) 노임 · 기계 덮개 걷음(자재만)
- 낡은 시험은 C 걸음에서 정리

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-15 16:02:11 +09:00

93 lines
4.1 KiB
Python

"""B09 [단가 갱신] = 프로젝트 사본 자원 단가 줄 갱신(PLAN 841-1 · 요구 854 · 866) — 서버 단독.
B09 금액 = 프로젝트 사본(구조물 사본 · 서고 사본)을 M02 엔진으로 셈한 값(B09 단가 책으로 다시 안 셈).
그래서 사본 값을 지금 M01 로 올리는 일 = 사본 `단가.json` 자원 줄(LB · MT · EQ · MO · FX)만 지금 M01 줄로
(로직 줄 · 일위대가 · 입력값 · 고른 자재 · 덮개 그대로 · M01 에서 지워진 키 = 마지막 값 그대로) —
M02 [단가 갱신](`M02_Sangeun_Copy.refresh`)과 같은 뜻 · 사용자가 누를 때만(프로젝트 안은 자동 아님).
미리 보기 = `OVER` 덮개(파일 안 씀 · 사본 읽는 곳이 `bundle` 로 읽음)로 같은 셈.
"""
from __future__ import annotations
from contextvars import ContextVar
from pathlib import Path
from typing import Any
from B09_Estimation import B09_Estimation_Bill_Library as library
from common_util.common_util_json import atomic_write_json
from M01_MasterData import M01_MasterData_Store as m01
from M02_MasterTemplete import M02_Item_Prices as item_prices
from M02_MasterTemplete import M02_Sangeun_Copy as skc
from M02_MasterTemplete.M02_Item_Snapshot import linked_snapshots, structures_dir
from M02_MasterTemplete.M02_Item_Source import PRICE_FILE, read_json
#: 미리 보기 덮개 {사본 폴더 글: 새 `단가.json`} — 없으면 파일
OVER: ContextVar[dict[str, dict] | None] = ContextVar("b09_copy_prices", default=None)
def bundle(folder: Path) -> dict[str, Any]:
"""사본 `단가.json` — 미리 보기 덮개가 있으면 그것."""
over = OVER.get()
if over and str(folder) in over:
return over[str(folder)]
return read_json(folder / PRICE_FILE) or {}
def folders(root: str | Path) -> list[Path]:
"""연결된 구조물 사본 + 서고 사본 폴더."""
base = structures_dir(root)
out = [base / one["폴더"] for one in linked_snapshots(root).values()]
out += [folder for folder, _manifest in library.copies(root)]
return list(dict.fromkeys(out))
def _shown(row: dict[str, Any]) -> Any:
"""미리 보기 값 — 노임 · 유가 = 값 · 기계 = 가격(천원) · 자재 = 출처 값 묶음."""
value = row.get("값")
if isinstance(value, dict):
return value.get("가격")
if value is not None:
return value
return {k: row.get(k) for k in ("조달청", "물가자료", "유통물가", "물가정보", "거래가격")}
def refreshed(root: str | Path) -> tuple[dict[str, dict], list[dict[str, Any]]]:
"""({바뀐 사본 폴더 글: 새 `단가.json`}, 바뀐 자원 [{키, 이름, 전, 후}]) — 파일 안 씀."""
whole = skc.whole_()
news: dict[str, dict] = {}
changed: dict[str, dict[str, Any]] = {}
for folder in folders(root):
data = read_json(folder / PRICE_FILE)
if not isinstance(data, dict):
continue
out, hit = {}, False
for name, part in data.items():
slot = m01.items_key(part) if isinstance(part, dict) else ""
rows = part.get(slot) if slot else None
if not isinstance(rows, list):
out[name] = part
continue
fresh = []
for row in rows:
key = str((row or {}).get("키") or "")
live = whole.index.get(key[:2], {}).get(key) if key[:2] in skc.RES else None
new = item_prices.to_json(live) if live is not None else row
if new != row:
hit = True
changed.setdefault(
key,
{"키": key, "이름": new.get("이름"), "전": _shown(row), "후": _shown(new)},
)
fresh.append(new)
out[name] = {**part, slot: fresh}
if hit:
news[str(folder)] = out
return news, list(changed.values())
def write(news: dict[str, dict]) -> int:
"""새 `단가.json` 을 사본 폴더에 씀 — 쓴 사본 수."""
for folder, data in news.items():
atomic_write_json(Path(folder) / PRICE_FILE, data)
return len(news)