"""예취기·분무기 인력 표 한 모양 넷 — 2026-09-15 브레인 차례 ② (사유 없던 49 다음). 산림 6-2-2 줄베기 · 6-2-3 모두베기 · 6-4-1 덩굴걷기 · 6-4-2 덩굴 약제 표는 「공법 | (갈래) | 사용도구 | 단위 | 소요인력 | 인력구분」 줄 — 이름이 끝 칸이라 줄 읽기가 한 줄도 못 읽었음(6-4-1 은 사유조차 없었음). 6-2-2 묘목찾기 낫 인/100본 0.07 보통인부 · 줄베기 1,500본 미만 1.50 · 1,500~3,000 2.00 · 3,000 이상 2.50 특별인부(인/ha) 6-2-3 묘목찾기 0.07 · 모두베기 조림 당해 연도 3.50 · 2년차 4.00 · 3년차 이상 5.00 6-4-1 기계작업 예취기 인/ha 3.30 특별인부 · 6-4-2 약제살포 배부식분무기 1.00 특별인부 + 작업보조 1.50 보통인부 ⭐ 6-2-2·6-2-3 [주]④⑤ 「묘목찾기와 줄베기(모두베기) 품을 **합하여** 적용」 — 조림목 본수(본/ha)는 설계 입력 칸 · 비면 「일부만」 으로 막음(반만 선 틀린 값보다 안 선 값) · 제안값 없음(부록 예시 2,700본/ha 는 품도 표와 다른 자료) 2장: 예취기 휘발유 5.0ℓ/대/일 · 잡품 10%(2-1-1 2 [주]① 줄베기·모두베기·지상부 덩굴걷기에만) · 손료 0.0084(2-2-2) · 배부식분무기 손료 0.0084(2-2-4) — 체인톱 한 벌 길 · 1대당 1인 """ from __future__ import annotations from decimal import Decimal import pytest from B09_Estimation.B09_Estimation_KnownGaps import known_gap_note from B09_Estimation.B09_Estimation_UnitPrice import cached_build CUTTER = "X-AR-X-da716e54" SPRAYER = "X-AR-X-5d0a0416" def _rows(book, title: str) -> dict[str, Decimal]: return {r.ref_code: r.quantity for r in book.details[title]} def test_덩굴걷기는_특별인부와_예취기_1대_1일() -> None: build = cached_build() rows = _rows(build.book, "B-FP-06-04-01") assert rows["1003"] == Decimal("3.30") and rows[CUTTER] == Decimal("3.30"), rows assert "FP-06-04-01" not in build.partial_ratio and "FP-06-04-01" not in build.basis_missing assert build.book.titles["B-FP-06-04-01"].unit == "ha" cutter = _rows(build.book, CUTTER) assert cutter["M-FUEL-휘발유"] == Decimal("5.0"), cutter misc = next(r for r in build.book.details[CUTTER] if r.percent_of_material is not None) assert misc.percent_of_material == Decimal(10) assert "예취기가격" in " ".join(build.unattached["FP-06-04-01"]) def test_덩굴_약제는_두_인력을_더하고_분무기_손료_칸() -> None: build = cached_build() rows = _rows(build.book, "B-FP-06-04-02") assert rows["1003"] == Decimal("1.00") and rows["1002"] == Decimal("1.50"), rows assert SPRAYER not in rows # 연료 없는 장비 — 구입가가 없으면 빈 호표를 안 세움 assert "배부식분무기가격" in " ".join(build.unattached["FP-06-04-02"]) assert "FP-06-04-02" not in build.basis_missing # 단위 칸 인/ha 가 밑수 priced = cached_build(material_prices=(("AR-M-d6394831", "150000", "견적"),)) assert _rows(priced.book, "B-FP-06-04-02")[SPRAYER] == Decimal("1.00") assert priced.book.resolve(SPRAYER).expense == Decimal("1260.0000") # 150,000 × 0.0084 @pytest.mark.parametrize( ("code", "variants"), [ ( "FP-06-02-02", {"1,500본미만": "1.50", "1,500본이상3,000본미만": "2.00", "3,000본이상": "2.50"}, ), ( "FP-06-02-03", { "조림당해연도(전년도추기조림포함)": "3.50", "조림2년차": "4.00", "조림3년차이상": "5.00", }, ), ], ) def test_풀베기는_갈래마다_특별인부_예취기_묘목찾기는_본수_칸(code, variants) -> None: empty = cached_build() for key, people in variants.items(): rows = _rows(empty.book, f"B-{code}#{key}") assert rows["1003"] == Decimal(people) and rows[CUTTER] == Decimal(people), rows assert "1002" not in rows # 본수가 없으면 묘목찾기를 안 셈 assert code in empty.partial_ratio # 반만 선 값으로 안 붙게 막음 assert "조림목 본수" in empty.component_gaps[code], empty.component_gaps.get(code) filled = cached_build(seedlings_per_ha="2700") for key in variants: rows = _rows(filled.book, f"B-{code}#{key}") assert rows["1002"] == Decimal("0.07") * Decimal(27), rows # 0.07인/100본 × 2,700본/ha assert code not in filled.partial_ratio assert "조림목 본수" not in (filled.component_gaps.get(code) or "") def test_본수_칸은_양수만_받음() -> None: from B09_Estimation.B09_Estimation_Brushcutting import parse_seedlings_per_ha assert parse_seedlings_per_ha("") is None assert parse_seedlings_per_ha("2,700") == Decimal(2700) for bad in ("0", "-5", "많이"): with pytest.raises(ValueError): parse_seedlings_per_ha(bad) def test_부록_예시와_표의_어긋남은_기록만() -> None: note = known_gap_note("FP-06-02-02") assert "부록" in note and "0.10" in note and "0.07" in note, note assert "2,700" not in (cached_build().component_gaps.get("FP-06-02-02") or "") # 제안값 없음