- master_cut: 엔진 답 덧칸 `셈법`(새 칸 뗀 작은 로직) · `plain(got, gone)` 이 셈법 있으면 줄 자리 · 합밖 · 단계 · 블록 끝전 · 조정 그대로(켜쌓기율 · 별도계상 뒤 다시 버림이 큰나머지 · 조정을 덮던 흠) · `kept` - M02_Labor_Doc: run_line 답이 단계 · 셈법을 넘김 · drop_apart 가 plain 에 뺄 줄 차례를 줌(686 줄 · 늘지 않음) - 새 B09_Estimation_Sangeun_Sum: 산근 풀이 · 산근 글이 새 의미 로직만 엔진 규칙(표시 · 줄 자리 · 합밖 · 번지 · 단계 · 끝전 · 조정) · 옛 로직 = Q ROUND 2 · ROUNDDOWN 1 · 0 그대로 - B09 산근 글: 로직 줄(중기 세 줄) 금액을 줄 하나로 묶어 차례를 호표와 맞춤(합 같음) - 지킴 시험 test_792_3_engine_paths: 견본 넷(봉화 · 소광 · 영월 · 임도) + 단위 로직 = 엔진 · M02 SK · SC 한 줄 · B09 산근(글 계 포함) 같은 금액 · 다시 버림 셈법 지킴 - test_791_1: 견본 넷을 만들기 함수로 떼어 냄(같은 기대) - test_222_1 낡은 기대 셋 고침: 751-3 끝수 뒤 별도계상 뺀 기준 = 남은 줄 비목 합 원 버림(소수 없음) · 옛 기대는 「원 버린 기준 − 소수 재료 금액」이라 0.6 원 어긋남 - test_145_1: 작업량 Q 이름 규칙이 B09_Estimation_Sangeun_Sum 으로 옮김 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
90 lines
3.9 KiB
Python
90 lines
3.9 KiB
Python
"""B09 산근 풀이 셈 의미(PLAN 792-3) — 791-1 엔진 새 의미(줄 자리 · 합밖 · 번지 · 단계 · 블록 끝전 ·
|
|
결과 조정 · 변수 표시)를 쓰는 로직만 엔진 규칙(`master_cut` · `master_formula_sum`) 그대로 ·
|
|
새 칸 없는 로직 = 옛 B09 규칙(작업량 Q = ROUND 2 · 줄 ROUNDDOWN 1 · 총계 비목마다 ROUNDDOWN 0).
|
|
쓰는 곳 = `B09_Estimation_Sangeun.Solver` · 지킴 시험 = `test_792_3_engine_paths.py`(엔진 · M02 SK ·
|
|
SC · B09 산근 네 길 같은 금액).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import importlib
|
|
import re
|
|
from decimal import Decimal
|
|
from typing import Any
|
|
|
|
from B09_Estimation.B09_Estimation_Calc_Base import round_half, rounddown, total
|
|
from M01_MasterData import M01_MasterData_Store as _m01 # noqa: F401 — scripts 길을 둠
|
|
|
|
mcut = importlib.import_module("master_cut")
|
|
ms = importlib.import_module("master_formula_sum")
|
|
|
|
PARTS = ("labor", "material", "expense")
|
|
COST_PART = {"노무비": "labor", "재료비": "material", "경비": "expense"}
|
|
_Q = re.compile(r"Q\d*") # 작업량 중간값 이름(산림품셈 09장 토공 · 07장 집재 …)
|
|
|
|
|
|
def shown(row: dict, step: dict, value: Any) -> Any:
|
|
"""중간값 — `표시` 칸 · 새 의미 로직 = 엔진 그대로(`표시` 자리 반올림) · 옛 = Q ROUND 2."""
|
|
if step.get("표시") is not None or ms.uses(row):
|
|
return ms.shown(step, value)
|
|
if _Q.fullmatch(step["이름"]) and isinstance(value, (Decimal, int, float)):
|
|
return Decimal(str(round_half(float(value), 2)))
|
|
return value
|
|
|
|
|
|
def _cut(value: float, rule: dict | None) -> float:
|
|
return float(mcut._line(value, rule)) if rule else rounddown(value, 1)
|
|
|
|
|
|
def places(row: dict, lines: list[dict]) -> None:
|
|
"""호표 줄 `자리`(줄 자리) — 그 줄 금액만 단가 × 수량을 그 규칙으로 다시 버림(제자리)."""
|
|
for item, ln in zip(row.get("호표", []), lines):
|
|
rule = item.get("자리")
|
|
if not rule or ln.get("qty") is None:
|
|
continue
|
|
for part, price in (ln.get("price") or {}).items():
|
|
if part in PARTS and price is not None:
|
|
ln[part] = _cut(price * ln["qty"], rule)
|
|
|
|
|
|
def _engine(lines: list[dict]) -> list[dict]:
|
|
"""B09 줄 → 엔진 줄 꼴 `{비목}`(번지 · 단계 셈)."""
|
|
return [
|
|
{"비목": {k: Decimal(str(ln.get(p) or 0)) for k, p in COST_PART.items()}} for ln in lines
|
|
]
|
|
|
|
|
|
def sums(row: dict, lines: list[dict]) -> dict[str, float | None]:
|
|
"""덧줄 밑 비목 합 — 새 의미 로직은 합밖 · 제외 단계 줄을 뺌."""
|
|
skip = ms.skipped(row) if ms.uses(row) else set()
|
|
kept = [ln for n, ln in enumerate(lines) if n not in skip]
|
|
return {name: total([ln[p] for ln in kept]) for name, p in COST_PART.items()}
|
|
|
|
|
|
def scope(row: dict, lines: list[dict], env: dict) -> None:
|
|
"""덧줄 식 이름 `번지` · `단계`(지금까지 줄) — 새 의미 로직만."""
|
|
if ms.uses(row):
|
|
env.update(ms.scope(row, _engine(lines)))
|
|
|
|
|
|
def extra(row: dict, item: dict, value: float, env: dict) -> float:
|
|
"""덧줄 금액 — 줄 `자리` 면 그 규칙 · 아니면 ROUNDDOWN 1 ·
|
|
새 의미 로직은 뒤 덧줄이 그 값을 부름."""
|
|
money = _cut(value, item.get("자리"))
|
|
if ms.uses(row):
|
|
env[item["이름"]] = Decimal(str(money))
|
|
return money
|
|
|
|
|
|
def totals(row: dict, lines: list[dict]) -> dict[str, float | None] | None:
|
|
"""총계 — 새 의미 로직 = 합밖 · 제외 빼고 블록 끝전 · `끝수` · 조정(`master_cut.settle`) ·
|
|
옛 로직 · 빈칸 비목 있음 = None(옛 `block_totals`)."""
|
|
if not ms.uses(row):
|
|
return None
|
|
got = sums(row, lines)
|
|
if any(v is None for v in got.values()):
|
|
return None
|
|
costs = mcut.settle(row, {k: Decimal(str(v)) for k, v in got.items()})
|
|
out = {COST_PART[k]: float(v) for k, v in costs.items()}
|
|
return {**out, "sum": sum(out.values())}
|