"""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 로직 꼬리