# -*- coding: utf-8 -*- """인력 · 재료 · 경비 묶음 — **셈 때 파생**(정본에 칸 없음 · PLAN 712-1 · 요구서 원칙 5). 규칙 한 줄: - 호표 · 덧줄 = `비목` 따라(노무비 → 인력 · 재료비 → 재료 · 경비 → 경비). - 비목 없는 로직 줄 = 부르는 로직이 기계경비(`is_machine`)면 경비 + `장비` · 아니면 하위. 두 줄 모두 엔진 줄 `비목` 에 노무비 · 재료비 · 경비 세 몫이 그대로 남음(한 줄 · 흩어 넣기 둘 다). 기계경비 판별 = `is_machine` 한 곳 — M01 엔진 · M02 엑셀 맞춤(`M02_Hopyo_Ours`) · B09 산근(`B09_Estimation_Sangeun` · `_Text`)이 같이 씀. 엔진 줄 덧칸 = `묶음`(출력 꼬리표만 · 입력 값 덧칸 `값묶음` 은 715-1b 몫) · 기계경비 줄 `장비` true. """ from __future__ import annotations import re import master_formula as mf BUNDLES = ("인력", "재료", "경비", "하위") BY_ITEM = {"노무비": "인력", "재료비": "재료", "경비": "경비"} BY_KIND = {"인력": "인력", "재료": "재료", "기계": "경비"} # 비목 빈 옛 줄 받침 def is_machine(row: dict | None) -> bool: """기계경비 로직 — 돈 로직이고 결과단위 「원/시간」(작업량 「㎥/시간」 · 이름 「시간당」 아님).""" if not row or "결과" in row or isinstance(row.get("흐름"), dict): return False return re.sub(r"\s", "", str(row.get("결과단위") or "")) == "원/시간" def called(master, item: dict, env: dict | None = None) -> dict | None: """로직 줄이 부르는 로직 줄(정본) — 못 찾으면 None.""" try: node = mf.parse(item.get("요소") or "") if not node or node[0] != "logic": return None key = mf.fill(node[1], env) if env is not None and "{" in node[1] else node[1] return master.logic(key) except (mf.FormulaError, mf.EmptyInput, KeyError, TypeError): return None def bundle(master, item: dict, env: dict | None = None) -> tuple[str, bool]: """줄 하나(호표 · 덧줄 정본 줄) → (묶음, 장비인가).""" if item.get("비목") in BY_ITEM: return BY_ITEM[item["비목"]], False if item.get("종류") == "로직": return ("경비", True) if is_machine(called(master, item, env)) else ("하위", False) return BY_KIND.get(str(item.get("종류")), "하위"), False def tag(master, row: dict, lines: list[dict], env: dict | None = None) -> list[dict]: """엔진 `run` 줄에 `묶음`(+ 기계경비 줄 `장비`) — 줄 차례 = 호표 줄 하나씩 뒤 덧줄 하나씩.""" for item, line in zip([*row.get("호표", []), *row.get("덧줄", [])], lines): name, machine = bundle(master, item, env) line["묶음"] = name if machine: line["장비"] = True return lines