feat(M02): 프로젝트 복사본 일위대가 문서 — 스냅샷 · 입력값 첫 값 · 자재 단가 · 수량 반영 계산 (39-1c)

- Item_Source: 층 labor/<열>.json 이 있으면 일위대가.json = {줄: 문서, 로직, 빠진로직, 알약} · 없으면 옛 UA 조합 줄 · 단가 묶음에 자재 MT
- Item_Snapshot: 입력값.json 첫 값 = 문서 입력값 {로직: {입력: {값, 확인: false}}}
- Item_Prices: collect 가 자재 MT 도 · calc 가 줄마다 수량 · 반영 + 자재 · 기준합 · 반영합(M02_Labor_Doc.settle 한 벌 · 면적 알약 칸 값)
- Item_Copy · Project_Items_Router: 일위대가 종류 읽기만(쓰면 400) · 재적용은 새 첫 값 위에 프로젝트 값 · calc 에 면적
- 시험 test_m02_item_labor.py

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UoSr2sqvYtBf1dtQdus88s
This commit is contained in:
2026-10-04 17:55:58 +09:00
co-authored by Claude Opus 5.5
parent 9242cd15e5
commit e1ad3d3625
6 changed files with 321 additions and 27 deletions
+35 -14
View File
@@ -2,9 +2,11 @@
모으는 것 = `{파일 이름: 내용}`:
`집계.json` SS 줄 + 열 정의 · `도면.json` SD · `면적.json` SA · `재료.json` SM ·
`자재조합.json` UM 줄 · `일위대가.json` UA 줄 + 담은 로직 줄(M01 시스템) ·
`자재조합.json` UM 줄 ·
`일위대가.json` = 층 `labor/<열id>.json` 문서(39-1c · 없으면 옛 UA 조합 줄)
+ 담은 로직 줄(M01 시스템) + 알약(가져온 때 그대로) ·
`입력연결.json` 층의 `inputs/<열id>.json`(없으면 빈 연결) ·
`단가.json` 계산에 닿는 마스터 줄 전부
`단가.json` 계산에 닿는 마스터 줄 전부(담은 로직 + 일위대가 자재 MT)
`원본판` = 모은 내용의 해시 — 지금 층 · 마스터에서 다시 모은 판과 대어 「마스터가 바뀜」 을 앎.
프로젝트가 고치는 값(`입력값.json` · `고른자재.json`)은 원본이 아니라 여기서 안 모음.
원본 층 자리 = `M02_Template_Layers` 경로 · 일위대가 · 단가는 M01 시스템 것.
@@ -23,6 +25,7 @@ TABLE_NAME = "구조물집계표"
UM_FILE = "자재조합.json"
#: 열 id → 스냅샷 파일(구조물 도면 · 면적산출 · 재료산출)
PART_FILES = (("structure", "도면.json"), ("area", "면적.json"), ("material", "재료.json"))
LABOR_KIND = "labor" # 열 id → 일위대가 문서(39-1c) — 스냅샷은 `LABOR_FILE` 에 감싸서
LINK_FILE = "입력연결.json"
PRICE_FILE = "단가.json"
LABOR_FILE = "일위대가.json"
@@ -80,15 +83,27 @@ def version_of(content: Any) -> str:
def labor_rows(ua_key: str) -> dict[str, Any] | None:
"""조합 줄(마스터 UA · 회사 UC · 개인 UP) + 담은 로직 줄(M01 시스템 · 없는 로직은 빼고 키만
`빠진로직`)."""
from M01_MasterData import M01_MasterData_Store as m01
"""옛 조합 줄(마스터 UA · 회사 UC · 개인 UP) + 담은 로직 줄 — labor 문서가 없는 열
(옮기기 전)."""
from M02_MasterTemplete import M02_MasterTemplete_Combo_Store as combos
ua = combos.find(ua_key)
if ua is None:
return None
wanted = [str(entry.get("로직")) for entry in ua.get("담은로직") or [] if entry.get("로직")]
return None if ua is None else logic_rows(ua)
def labor_doc_rows(doc: dict[str, Any]) -> dict[str, Any]:
"""일위대가 문서(39-1a) + 담은 로직 줄 + 알약(`M02_Labor_Doc.pills` · 가져온 때 그대로 —
복사본 계산이 마스터를 안 읽게)."""
from M02_MasterTemplete import M02_Labor_Doc as labor_doc
return {**logic_rows(doc), "알약": labor_doc.pills(doc)}
def logic_rows(row: dict[str, Any]) -> dict[str, Any]:
"""`{줄: row, 로직: 담은 로직 줄(M01 시스템), 빠진로직: 없는 로직 키}`."""
from M01_MasterData import M01_MasterData_Store as m01
wanted = [str(entry.get("로직")) for entry in row.get("담은로직") or [] if entry.get("로직")]
found: dict[str, dict[str, Any]] = {}
raw: dict[str, Any] = {} # M01 적재본은 수가 Decimal — 줄은 원본 파일 JSON 그대로 담음
for key in wanted:
@@ -98,11 +113,11 @@ def labor_rows(ua_key: str) -> dict[str, Any] | None:
continue
if file not in raw:
raw[file] = read_json(Path(m01.FOLDER) / file)
row = row_of(raw[file], key)
if row is not None:
found[key] = row
got = row_of(raw[file], key)
if got is not None:
found[key] = got
return {
"줄": ua,
"줄": row,
"로직": [found[key] for key in wanted if key in found],
"빠진로직": [key for key in wanted if key not in found],
}
@@ -142,10 +157,16 @@ def build_source(item: dict[str, Any]) -> dict[str, Any]:
um = row_of(read_json(root / UM_FILE), str(row["자재조합"]))
if um is not None:
files[UM_FILE] = um
labor = labor_rows(str(row["일위대가"])) if row.get("일위대가") else None
doc = read_json(root / LABOR_KIND / f"{column_id}.json") if column_id else None
if isinstance(doc, dict):
labor = labor_doc_rows(doc)
else:
labor = labor_rows(str(row["일위대가"])) if row.get("일위대가") else None
if labor is not None:
files[LABOR_FILE] = labor
files[LINK_FILE] = input_links(root, column_id)
logic_keys = [str(one.get("키")) for one in (labor or {}).get("로직", [])]
files[PRICE_FILE] = prices.to_json(prices.collect(logic_keys)) if logic_keys else {}
materials = [str(m.get("코드")) for m in ((labor or {}).get("줄") or {}).get("자재") or []]
wanted = logic_keys + [code for code in materials if code]
files[PRICE_FILE] = prices.to_json(prices.collect(logic_keys, materials)) if wanted else {}
return files