Files
Aislo/resources/tester/test_155_b09_sources.py
T
eomsangdonandClaude Opus 5.5 5dd72cfa38 feat(b09): 산근 텍스트 수식 칸 · 일위대가화 글 · % 덧줄 표기(155-5)
- 산근 블록마다 서버가 글 지음(sub4 render_sangeun_text · B09 굳힌 단가) — head · 줄마다 text · foot(계)
- 단가산출근거 탭 = 엑셀 꼴(공종 · 산출내역 · 합계 · 노무비 · 재료비 · 경비 · 비고) · 제목 · 총계 굵게
- 중기 = 노 · 재 · 경 세 줄(서버 글 줄 수와 같게)
- % 덧줄 = 규격 「노무비의 %」 · 수량 = 율 · 단위 %(sub3 pct_extras)
- 산근목록 안내 글 = 단가산출 줄 · 사전조합 안 줄 · 일위대가화 줄의 하위 로직(「단가승격」 글 지움)
- 시험: 145-1 화면 글 대조 새 갈래 · test_155_b09_sources · test_155_b09_tabs 덧붙임

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015KYZ2HWWdUwZWYnn3tQz7X
2026-10-09 19:26:40 +09:00

134 lines
6.9 KiB
Python

"""155-3 · 155-4 B09 호표 갈래 — 사용자 뜻(10-9 둘째 · 145 정함 뒤집음 · M02 구분 짝 · 엑셀 꼴):
일위대가화(승격) 줄 = 일위대가 호표(대가 블록) · 단가산출 줄 = 산근 ·
일위대가화 줄의 하위 로직 줄 = 산근(대가 블록 산근 줄 키 = 산근 목록 키) ·
산근 줄 수량 = 수량 그대로(배 10 로직 금액 1/배 흠) · 비고 「산근 n호표」(파이썬 · TS 짝) ·
산근 자재 자리표시 키(「MO:경유:…」) = 자재단가대비표 키.
"""
import json
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
for path in (ROOT, ROOT / "resources" / "tester"):
if str(path) not in sys.path:
sys.path.insert(0, str(path))
import test_145_2_b09_labor_calc as t145 # noqa: E402
from B09_Estimation import B09_Estimation_Prices as prices # noqa: E402
from B09_Estimation import B09_Estimation_Sangeun as sg # noqa: E402
from B09_Estimation.B09_Estimation_Calc_Base import compute_base, rounddown # noqa: E402
from B09_Estimation.B09_Estimation_Calc_Labor import compute_labor # noqa: E402
from B09_Estimation.B09_Estimation_Labor_Source import daega_inputs # noqa: E402
from B09_Estimation.B09_Estimation_Linked import linked_resource_keys, used_keys # noqa: E402
from B09_Estimation.B09_Estimation_Router import compute_sheets # noqa: E402
from M02_MasterTemplete import M02_Item_Prices as item_prices # noqa: E402
PILE = "GF000233" # 말뚝 다듬기 — 원/10개(배 10) · 인력 줄 둘
PIPE = "GF000174" # 흄관 부설 — 하위 로직 줄(GF000155 · GF000160) = 하위 산근
FUEL = "GF000011" # 재료 줄 「MO:경유:{지역}」 자리표시
def _sheets_of(root):
inputs = prices.build_inputs(None, linked_resource_keys(root))
inputs["daega"] = daega_inputs(root)
inputs["hopyo"] = {}
inputs["bill"] = {"items": []}
return inputs, compute_sheets(root, inputs, {})
def _blk(sheets, key):
return next(b for b in sheets["daega_blocks"] if b["key"] == key)
def test_155_3_배_10_로직_산근_줄_수량_그대로_대가_줄만_나눔(tmp_path):
root = t145._project(tmp_path / "p", [(100, "SS000901"), (200, "SS000902")])
t145._snap(root, "pipe@100.0", "SS000901", [{"로직": PILE, "수량": {"손": 20}}], {})
t145._snap(
root, "pipe@200.0", "SS000902", [{"로직": PILE, "승격": True, "수량": {"손": 20}}], {}
)
inputs, sheets = _sheets_of(root)
owners = sheets["daega_owners"]
(sg_line,) = _blk(sheets, owners["pipe@100.0"])["lines"]
(dg_line,) = _blk(sheets, owners["pipe@200.0"])["lines"]
assert (sg_line["kind"], sg_line["qty"]) == ("sangeun", 20) # 산근 단가 = 1 개
assert (dg_line["kind"], dg_line["qty"]) == ("daega", 2) # 대가 블록 = 10 개
row = next(r for r in sheets["sg_list"] if r["key"] == sg_line["key"])
assert sg_line["ref_no"] == f"산근 {row['no']}호표" and dg_line["ref_no"].startswith("대가")
# 같은 일 · 같은 수량 = 금액 거의 같음(끝수 자리만 다름 · 예전 흠 = 1/10)
assert sg_line["amount"] is not None and dg_line["amount"] is not None
assert abs(sg_line["amount"] - dg_line["amount"]) <= 0.02 * dg_line["amount"]
def test_155_4_일위대가화_하위_로직_줄_산근_목록에(tmp_path):
root = t145._project(tmp_path / "p", [(100, "SS000901")])
t145._snap(
root, "pipe@100.0", "SS000901", [{"로직": PIPE, "승격": True, "수량": {"손": 1}}], {}
)
inputs, sheets = _sheets_of(root)
(call,) = _blk(sheets, sheets["daega_owners"]["pipe@100.0"])["lines"]
pipe = _blk(sheets, call["key"])
assert pipe["kind"] == "품셈" and call["kind"] == "daega"
subs = [ln for ln in pipe["lines"] if ln["kind"] == "sangeun"]
listed = {r["key"]: r for r in sheets["sg_list"]}
assert {ln["key"][:11] for ln in subs} >= {"SG:GF000155", "SG:GF000160"}
for ln in subs: # 대가 블록 산근 줄 = 산근 목록 줄(같은 키) · 비고 · 단가 이음
assert ln["key"] in listed and ln["ref_no"] == f"산근 {listed[ln['key']]['no']}호표"
assert ln["prices"]["labor"] == listed[ln["key"]]["labor"]
assert all(r["logic"] != PIPE for r in sheets["sg_list"]) # 일위대가화 줄 자체 = 산근 아님
def test_155_4_산근_자재_자리표시_키_대비표로(tmp_path):
folder = tmp_path / "a"
folder.mkdir()
files = {
"단가.json": item_prices.to_json(item_prices.collect([FUEL])),
"일위대가.json": {"줄": {"종류": "일위대가", "담은로직": [{"로직": FUEL}]}},
"입력값.json": {},
}
for name, data in files.items():
(folder / name).write_text(json.dumps(data, ensure_ascii=False), encoding="utf-8")
sheets = compute_base(prices.build_inputs(None, used_keys(folder)), {})
(block,) = sg.collect([("s1", folder)], sheets)["sg"]
fuel = [ln for ln in block["lines"] if ln["kind"] == "material"]
assert fuel and all(ln["ref"]["key"].startswith("MO0") for ln in fuel)
applied = {r["key"]: r["applied"] for r in sheets["compare"]}
for ln in fuel:
assert ln["material"] == rounddown(applied[ln["ref"]["key"]] * ln["qty"], 1)
def test_155_3_비고_산근_n호표_거울():
sangeun = {k: {**v, "no": n} for n, (k, v) in enumerate(t145.SANGEUN.items(), 1)}
py = json.loads(json.dumps(compute_labor(t145.EXCEL, t145.SHEETS, sangeun), ensure_ascii=False))
ts = t145._ts(t145.EXCEL, t145.SHEETS, sangeun)
assert t145._same(py, ts["out"])
notes = [
ln.get("ref_no") for b in py["daega_blocks"] for ln in b["lines"] if ln["kind"] == "sangeun"
]
assert notes and notes[0] == "산근 1호표" and all(n and n.startswith("산근 ") for n in notes)
def test_155_5_산근_글_줄마다_덧줄_퍼센트_표기(tmp_path):
lines = [{"로직": "GF000143", "수량": {"손": 1}}, {"로직": PILE, "수량": {"손": 20}}]
root = t145._project(tmp_path / "p", [(100, "SS000901")])
lines = [{"로직": "GF000143", "수량": {"손": 1}}, {"로직": PILE, "수량": {"손": 20}}]
t145._snap(root, "pipe@100.0", "SS000901", lines, {})
inputs, sheets = _sheets_of(root)
crane, pile = sheets["sg"]
assert crane["head"][:2] == ["케이블 크레인 운반대 설치 / 1기", "산림품셈 10-8-2 적용"]
extra = crane["lines"][-1]
assert (extra["kind"], extra["spec"], extra["qty"], extra["unit"]) == (
"extra", "재료비의 %", 20.0, "%",
) # fmt: skip
assert extra["text"][0].startswith("기타비용 (재료비의 %) : ")
for b in (crane, pile):
for ln in b["lines"]:
money = sum(ln[p] or 0 for p in sg.PARTS)
assert ln["text"][-1].endswith(f"= {money:,.1f}") or ln["kind"] == "machine"
assert b["foot"] == (
f"계 : 노무비 {b['labor']:,.0f} + 재료비 {b['material']:,.0f}"
f" + 경비 {b['expense']:,.0f} = {b['sum']:,.0f}"
)
assert " / 10 = " in pile["lines"][0]["text"][0] # 배 10 로직 꼬리