"""2장 소요재료·기계손료 표를 읽는 한 벌 — 체인톱부터(2026-09-15 브레인 ㉡ · 판정 ①②③). 산림품셈 2-1-1 체인톱 표(F0042): 보통휘발유(주연료) 5.6ℓ/대/일 · 잡품 주연료비 40% · 체인오일(일반·친환경) 2.1ℓ/대/일 「체인톱 대수는 산출된 벌목부 또는 특별인부의 100% 적용」 · 2-2-1 체인톱 손료(F0064): 45cc 0.0084(1일 1대) × 체인톱가격 체인톱을 쓰는 공종(4-2-2 단목베기 · 6-5 어린나무가꾸기)이 인력만으로 서서 연료·잡품·손료가 통째로 빠져 있었음. ③ 표가 이름으로 밝힌 인원 줄만 — 4-2-2 「벌목부」 · 6-5 「특별인부 (체인톱 사용)」 ① 체인오일 일반/친환경은 「자재 단가」 에 넣은 쪽 · 둘 다면 안 붙고 사유 ② 체인톱가격·체인오일 시중가격은 칸 + 사유 · 휘발유는 유가 판으로 바로 섬 """ from __future__ import annotations from decimal import ROUND_FLOOR, Decimal from B09_Estimation.B09_Estimation_UnitPrice import cached_build SAW = "X-AR-X-61d1681d" SAW_PRICE = "AR-M-5649cf3f" OIL = "AR-M-fa7fbf6d" ECO_OIL = "AR-M-de2fe662" def _rows(book, code: str) -> list: own = book.details.get(code) or [] return [ *own, *(r for d in own if d.ref_code.startswith("D-") for r in book.details[d.ref_code]), ] def test_체인톱_1대_1일_호표는_표의_휘발유와_잡품_40퍼센트() -> None: book = cached_build().book rows = book.details[SAW] fuel = next(r for r in rows if r.ref_code == "M-FUEL-휘발유") assert fuel.quantity == Decimal("5.6"), fuel misc = next(r for r in rows if r.percent_of_material is not None) assert misc.percent_of_material == Decimal(40) price = book.titles["M-FUEL-휘발유"].slots[-1] expected = (Decimal("5.6") * price * Decimal("1.4")).quantize(Decimal(1), rounding=ROUND_FLOOR) assert abs(book.resolve(SAW).material - expected) <= 1, (book.resolve(SAW), expected) def test_단목베기는_벌목부_인원만큼_체인톱_어린나무가꾸기는_체인톱_사용_특별인부만큼() -> None: book = cached_build().book rows = _rows(book, "B-FP-04-02-02#5m미만") faller = next(r for r in rows if book.titles[r.ref_code].name == "벌목부") saw = next(r for r in rows if r.ref_code == SAW) assert saw.quantity == faller.quantity, (saw, faller) rows = _rows(book, "B-FP-06-05") special = next( r for r in rows if book.titles.get(r.ref_code) and book.titles[r.ref_code].name == "특별인부" ) saw = next(r for r in rows if r.ref_code == SAW) assert saw.quantity == special.quantity, (saw, special) # 보통인부(작업 보조)는 체인톱을 안 씀 — 표가 이름으로 밝힌 줄만(③) assert sum(1 for r in rows if r.ref_code == SAW) == 1 def test_체인오일은_넣은_쪽만_둘_다면_사유() -> None: none = cached_build() assert any("체인오일" in label for label in none.unattached.get("FP-04-02-02", [])) one = cached_build(material_prices=((OIL, "2250", "시중"),)) rows = _rows(one.book, "B-FP-04-02-02#5m미만") faller = next(r for r in rows if one.book.titles[r.ref_code].name == "벌목부") oil = next(r for r in rows if r.ref_code == OIL) assert oil.quantity == faller.quantity * Decimal("2.1"), oil both = cached_build(material_prices=((OIL, "2250", "시중"), (ECO_OIL, "6000", "시중"))) assert not any(r.ref_code in (OIL, ECO_OIL) for r in _rows(both.book, "B-FP-04-02-02#5m미만")) assert any("둘 다" in label for label in both.unattached.get("FP-04-02-02", [])) def test_체인톱가격이_들면_손료_0_0084_가_경비로() -> None: none = cached_build() assert any("체인톱가격" in label for label in none.unattached.get("FP-06-05", [])) priced = cached_build(material_prices=((SAW_PRICE, "900000", "견적"),)) assert priced.book.resolve(SAW).expense == Decimal(7560) # 900,000 × 0.0084 for code in (SAW_PRICE, OIL, ECO_OIL): assert "FP-04-02-02" in none.material_uses.get(code, []), code