feat(master_data): 노임 준용 칸 · ESTX 다른 조사 다섯 갈래 노임 파일 · 인력_자체 → 새 노임 열쇠와 인력_미확보

- 역산: ESTX 미공표 추정 = 마지막 공표값 × 전체직종 평균 증가율 공표일마다 이어 곱·원 미만 버림 (5 직종 원 단위 일치 · 9 직종 앞 값 없음)
- 건설노임 미공표 14 줄에 준용 칸(null) · 엔진은 값이 비면 준용 직종 값 · 호표 출처 「준용: <직종>」 · M01 계산표에 표시
- 새 파일: 엔지니어링노임 59 · 측량노임 20 · 건설사업관리노임 7 · SW노임 17 · 산림노임 2 (줄 수·값 합 ESTX 와 같음)
- 인력_자체 49 → 새 노임 열쇠 18 · 인력_미확보 31 · 로직 참조 526 곳
- 로직 일괄 시험 계산: 통과 1011 → 1088 · 인력 값 없음 멈춤 176 → 84

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PAdA5ThqmtVSsbzjusk1cJ
This commit is contained in:
2026-09-19 23:31:02 +09:00
co-authored by Claude Opus 5
parent dd120ce44b
commit a8a8e49b52
29 changed files with 859 additions and 607 deletions
@@ -34,6 +34,12 @@ BOOKS = (
"건설품셈",
"건설노임",
"제조노임",
"엔지니어링노임",
"측량노임",
"건설사업관리노임",
"SW노임",
"산림노임",
"미확보",
"나라장터자재",
"오피넷유가",
"시중물가",
@@ -239,6 +239,16 @@ class Master:
raise FormulaError(f"없는 요소 「{ref}")
return row
def value(self, ref: str):
"""요소 값 · 준용 이름 — 값이 비고 「준용」(같은 원문 열쇠 또는 `그룹:원문:열쇠`)이 있으면 그 요소 값."""
row = self.get(ref)
alias = row.get("준용")
if row.get("") is not None or alias in (None, ""):
return row.get(""), None
alias = str(alias)
target = self.get(alias if ":" in alias else ":".join(ref.split(":", 2)[:2] + [alias]))
return target.get(""), target.get("이름") or alias
def logic(self, ref: str) -> dict:
return self.get(f"로직:{ref}")
@@ -311,7 +321,7 @@ def evaluate(node, env: dict, master: Master, depth: int = 0):
raise FormulaError(f"모르는 이름 「{node[1]}")
return env[node[1]]
if kind == "ref":
value = master.get(fill(node[1], env)).get("")
value = master.value(fill(node[1], env))[0]
if value is None:
raise FormulaError(f"{node[1]}」 값 없음 — 관리자가 채울 값")
if isinstance(value, list):
@@ -416,6 +426,7 @@ def run(master: Master, ref: str, given: dict, depth: int = 0) -> dict:
sums = dict.fromkeys(COST_ITEMS, Decimal(0))
lines = []
for item in row.get("호표", []):
alias = None
qty = evaluate(parse(item["수량"]), env, master, depth)
if item["종류"] == "로직":
bundle = evaluate(parse(item["요소"]), env, master, depth)
@@ -430,7 +441,7 @@ def run(master: Master, ref: str, given: dict, depth: int = 0) -> dict:
if node[0] == "field": # 값 묶음 요소의 칸 — 「재료:시중물가:M….채택」
price = master.get(fill(node[1][1], env))[""][node[2]]
else:
price = master.get(fill(item["요소"], env))[""]
price, alias = master.value(fill(item["요소"], env))
if price is None and qty == 0:
price = Decimal(0) # 안 쓰는 재료 줄 — 값이 비어도 금액 0
if price is None:
@@ -446,6 +457,7 @@ def run(master: Master, ref: str, given: dict, depth: int = 0) -> dict:
"단가": price,
"금액": qty * price,
"비목": split,
**({"출처": f"준용: {alias}"} if alias else {}),
}
)
for extra in row.get("덧줄", []):