Files
Aislo/B09_Estimation/B09_Estimation_Brushcutting.py

91 lines
4.2 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""B09 원가계산 — **풀베기 묘목찾기 = 조림목 본수 칸** (2026-09-15 브레인 차례 ② 판정 ㉠ · 지금 칸을 만듦).
산림 6-2-2 줄베기 · 6-2-3 모두베기 [주]④⑤ 「묘목찾기와 줄베기(모두베기) 품을 **합하여** 적용」 — 묘목찾기는
「낫 | 인/100본 | 0.07 | 보통인부」 라 조림목 본수(본/ha · [주]② 둘레베기 참고 · 설계 입력)가 있어야 ha당이 섬.
⇒ 본수가 들면 갈래마다 보통인부 0.07 × 본수 ÷ 100 · 비면 「일부만」 으로 막고 사유(반만 선 틀린 값보다 안 선 값).
⚠ 제안값 없음 — 부록 예시 2,700본/ha 는 같은 부록이 품도 표와 다르게 적은 자료(묘목찾기 0.10 ↔ 표 0.07).
"""
from __future__ import annotations
import re
from decimal import Decimal, InvalidOperation
from typing import Any
#: 공종 → 표 번호(`_JudgedTable` 도구 줄 표 · 인/100본 줄이 있는 둘).
SEEDLING_TABLES = {"FP-06-02-02": "F0155", "FP-06-02-03": "F0156"}
NAME = "묘목찾기"
MISSING = (
"조림목 본수(본/ha) 미입력 — 원문 [주]④⑤ 가 묘목찾기({amount}인/100본)와 합하여 적용하라 함"
" · 「산출 조건」 에서 넣으면 섬"
)
def parse_seedlings_per_ha(text: str | None) -> Decimal | None:
"""조림목 본수 칸 — 빈 칸은 `None` · 0 이하·수가 아닌 값은 `ValueError`(조용히 접지 않음)."""
cleaned = re.sub(r"[\s,]", "", str(text or ""))
if not cleaned:
return None
try:
value = Decimal(cleaned)
except InvalidOperation as exc:
raise ValueError(f"조림목 본수는 수로 넣어야 합니다: {text}") from exc
if value <= 0:
raise ValueError(f"조림목 본수는 0 보다 커야 합니다: {text}")
return value
def _per_hundred(node: dict[str, Any], table_id: str) -> tuple[Decimal, str] | None:
"""(인/100본 값, 인력 이름) — 표에서 읽음. 칸이 달라지면 `None`."""
from B09_Estimation.B09_Estimation_ResourceAxis import parse_amount
table = next((t for t in node.get("tables") or [] if t.get("pum_table_id") == table_id), {})
for row in table.get("raw_row") or []:
cells = ["".join(str(c).split()) for c in row]
if "인/100본" in cells:
unit = cells.index("인/100본")
amount = parse_amount(cells[unit + 1]) if len(cells) > unit + 2 else None
if amount is not None and cells[unit + 2]:
return amount, cells[unit + 2]
return None
def attach_seedling_finding(
build: Any, nodes: dict[str, dict[str, Any]], seedlings_per_ha: Decimal | None
) -> None:
"""본수가 들면 갈래 제목마다 묘목찾기 인력을 붙이고, 없으면 일부만 + 사유."""
from B09_Estimation.B09_Estimation_PriceBook import PriceDetail, PriceKind
book = build.book
for code, table_id in SEEDLING_TABLES.items():
found = _per_hundred(nodes.get(code) or {}, table_id)
titles = [t for t in book.titles if t.startswith(f"B-{code}#")]
labels = [
label for label in build.unattached.get(code) or [] if "".join(label.split()) != NAME
]
labor = next(
(
c
for c, t in book.titles.items()
if found and t.kind is PriceKind.LABOR and "#" not in c and t.name == found[1]
),
None,
)
if found is None or labor is None or not titles:
build.component_gaps[code] = "묘목찾기 줄 칸이 달라져 못 읽음"
build.partial_ratio.setdefault(code, Decimal(0))
continue
amount, name = found
if seedlings_per_ha is None:
reason = MISSING.format(amount=amount)
build.component_gaps[code] = reason
build.partial_ratio.setdefault(code, Decimal(0))
labels.append(reason)
else:
note = f"묘목찾기 {amount}인/100본 × 조림목 {seedlings_per_ha}본/ha ÷ 100 — [주]④⑤ 합하여 적용"
for title in titles:
book.add_detail(
PriceDetail(title, labor, amount * seedlings_per_ha / 100, note=note)
)
build.unattached[code] = labels