- 산근 블록마다 서버가 글 지음(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
134 lines
6.9 KiB
Python
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 로직 꼬리
|