feat(b09): 산근 텍스트 수식 % 덧줄 규격 = 149-4 pct_extras(「노무비의 %」)(155-5)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QND7Co43B24zK2P937rdxt
This commit is contained in:
2026-10-09 19:17:04 +09:00
co-authored by Claude Opus 5.5
parent 32f97c3913
commit fcaa1d5612
2 changed files with 23 additions and 23 deletions
+12 -23
View File
@@ -5,7 +5,8 @@
중간 「Q = 이름식 = 값」(작업량 Q = ROUND 2 — `Sangeun._env`) ·
줄 「보통인부 : 172,068 * 0.022 = 3,785.4」(단가 * 수량식 값 꼴[ / 배][ * 율 %] = 금액) ·
중기 = 노무비 · 재료비 · 경비 세 줄 · 하위 산근 = 「산근 n호표 단가 * 수량」 ·
% 덧줄 = 「이름 : 노무비의 n% = 밑수 * n % = 금액」 · 끝 = 「계 : 노무비 + 재료비 + 경비 = 합」.
% 덧줄 = 「이름 (노무비의 %) : 밑수 * n % = 금액」(규격 = 149-4 `pct_extras`) ·
끝 = 「계 : 노무비 + 재료비 + 경비 = 합」.
식 풀이 = 엔진 `master_text` 낮은 부품(render · _names · fmt) · 단가 = 인자 `price_of(key)`(B09 굳힌
단가 · 엔진 단가 글은 안 씀) · 단가 없음 = 「단가 없음」 글.
끝수 = 엑셀 자리 — 줄 ROUNDDOWN 1 · 계 비목마다 ROUNDDOWN 0(`Sangeun._money` · `block_totals`).
@@ -21,6 +22,7 @@ from typing import Any
from B09_Estimation import B09_Estimation_Sangeun as sg
from M01_MasterData import M01_MasterData_Store as m01
from M02_MasterTemplete import M02_Labor_Doc as labor_doc
mf = sg.mf
mtx = m01.mtx
@@ -141,37 +143,24 @@ def _logic_lines(whole, item, env, qty_text, q, price_of) -> list[tuple[str, dic
return [(text, amounts)]
def _pct(extra: dict) -> str:
"""% 덧줄 이름 — 식 「X * Y / 100」 의 X 가 이름이면 「X의 Y%」 · 그 밖 빈 글."""
try:
node = mf.parse(extra.get("식") or "")
except mf.FormulaError:
return ""
if not (node[0] == "bin" and node[1] == "/" and node[3] == ("num", Decimal(100))):
return ""
inner = node[2]
if inner[0] != "bin" or inner[1] != "*" or inner[3][0] != "num":
return ""
base = inner[2]
if base[0] == "name":
word = base[1]
elif base[0] == "field" and base[1] == ("name", "줄"):
word = base[2]
else:
return ""
return f"{word}의 {mtx.fmt(inner[3][1])}%"
def _pct_specs(whole, row, env) -> list[str]:
"""% 덧줄마다 규격(「노무비의 %」 · 149-4 `M02_Labor_Doc.pct_extras`) · % 아닌 덧줄 = 빈 글."""
head = [{} for _ in row.get("호표", [])]
tail = [{} for _ in row.get("덧줄", [])]
got = labor_doc.pct_extras(whole, row, head + tail, dict(env))[len(head) :]
return [str(one.get("규격") or "") for one in got]
def _extra_lines(whole, row, env, amounts: list[dict]) -> list[tuple[str, dict]]:
"""% 줄(덧줄) — 밑수 = 그때까지 B09 비목 합(`Sangeun._extras` 와 같음) · 금액 ROUNDDOWN 1."""
out: list[tuple[str, dict]] = []
for extra in row.get("덧줄", []):
specs = _pct_specs(whole, row, env)
for extra, spec in zip(row.get("덧줄", []), specs):
part = sg.COST_PART.get(extra.get("비목"), "material")
sums = {
name: sg.total([a.get(p, 0.0) for a in amounts]) for name, p in sg.COST_PART.items()
}
label = _pct(extra)
head = f"{extra['이름']} : {label + ' = ' if label else ''}"
head = f"{extra['이름']}{f' ({spec})' if spec else ''} : "
if any(v is None for v in sums.values()):
out.append((f"{head}밑수 빈칸", {part: None}))
amounts.append({part: None})
@@ -3,6 +3,7 @@
- 엑셀 6호표 뒷채움: 「보통인부 : 165,545 * 0.018 = 2,979.8」 · 중기 노 · 재 · 경 세 줄 ·
「계 : 노무비 6,878 + 재료비 538 + 경비 912 = 8,328」(줄 ROUNDDOWN 1 · 계 ROUNDDOWN 0)
- 엑셀 7호표 막자갈채집: 작업량 Q = ROUND 2 중간 줄 · 「55,700 * 1 / 21.59 * 2 = 5,159.7」
- % 덧줄 = 「공구손료 (노무비의 %) : 6,878.8 * 3 % = 206.3」(규격 = 149-4 `pct_extras`)
- 단가 없음 = 「단가 없음」 글 · 계 그 비목 빈칸 · 글 속 금액 = 산근 풀이(`Sangeun.Solver`) 줄 값
"""
@@ -68,6 +69,16 @@ def test_엑셀_7호표_작업량_Q_중간_줄():
assert "굴착기(무한궤도) 0.2㎥ 노무비 : 55,700 * 1 / 21.59 * 2 = 5,159.7" in got
def test_퍼센트_덧줄_노무비의_퍼센트():
tool = {"이름": "공구손료", "식": "노무비 * 3 / 100", "비목": "경비"}
whole = _whole(_excel("0.018", "0.07", extras=[tool]))
got = st.render_sangeun_text(whole, BASE, GIVEN, _prices({"LB000002": 165545}))
assert got[-2:] == [
"공구손료 (노무비의 %) : 6,878.8 * 3 % = 206.3", # 밑수 = 인력 + 중기 노무비
"계 : 노무비 6,878 + 재료비 538 + 경비 1,119 = 8,535",
]
def test_배_켜쌓기율_꼬리_단가_없음():
whole = _whole(_excel("0.018", "0.07"))
got = st.render_sangeun_text(whole, BASE, GIVEN, _prices({}), times=10, rate=90)