Files
Aislo/B09_Estimation/B09_Estimation_WoodChipping.py
T
eomsangdonandClaude Opus 5 0fc64fa704 feat(b09): ㉰ 이동식 임목 파쇄 8-11 일위대가 — 운전경비표에 값이 없던 파쇄기 기계 층을 8-11 [주]⑤·비고로 세움
· 건설 8-4 운전경비표 7205-0125 93.25㎾ 줄은 연료·잡품 칸이 「-」 → 레코드가 없어 기계 층도 일위대가도 조용히 없었음
· 8-11 [주]⑤ 연료 13.5ℓ(경유) · 비고 잡재료 주연료비 16% · [주]② 기계운전자 1인이 유일한 값이라 그대로(브레인 판정)
· B-FP-08-11 ㎥당 = 파쇄기 1/Q(3.5) + 보통인부 2인 ÷ 8시간 ÷ Q · 기계운전자는 기계 층 조종원으로 한 번만
· 파쇄기날(자원 목록 새 칸) — 「자재 단가」 에 넣으면 0.00125개/hr ÷ Q, 아니면 사유 · [주]③ 운반비·④ 우드그랩·⑥ 규격 외 견적은 사유 한 줄
· 영월 호표는 옛 판(8-2-35 Q=6)이라 대조 대상 아님

새 제목 B 51,029원/㎥ · 바뀐 제목 0 · 프로젝트 내역 그대로(파쇄 기본 끔) · 잴 시험 넷 빨강→초록 · 끄기 넷 빨강 · 전체 시험 1711 + 371 통과

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BW6Jdsh18WPtYUR6THZJqn
2026-09-15 01:08:56 +09:00

114 lines
5.0 KiB
Python
Raw 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 원가계산 — **이동식 임목 파쇄** 8-11 (2026-09-15 브레인 판정 · ㉰ 둘째).
원문 L4651 표: 이동식 임목 파쇄기 93.25KW · Q = 3.5 ㎥/hr · 비고 「잡재료 : 주연료비의 16% · 소모품비(파쇄기날)
0.00125개/hr」 · [주]① 1일 8시간 ② 1일 기계운전자 1인·보통인부 2인 ③ 장비 운반비 별도 ④ 우드그랩 별도
⑤ 연료 「10.8 + 16.3 / 2 = 13.5 (디젤)」.
⚠ 건설품셈 운전경비표(8-4 L3458) 7205-0125 93.25㎾ 줄은 연료·잡품 칸이 「-」 → 운전경비 레코드가 없어
기계 층이 안 서고 일위대가도 조용히 없었음. 8-11 [주]⑤·비고가 **유일한 값**(부딪히는 원문 없음)이라 그대로 씀.
기계운전자는 기계 층 조종원(건설기계운전사 · 8-4 잠정 규칙과 같은 직종)으로 한 번만 셈.
"""
from __future__ import annotations
import re
from decimal import Decimal
from typing import Any
CODE = "FP-08-11"
MACHINE = "7205-0125"
BLADE = "AR-M-249d0a01"
TABLE = "F0237"
#: [주]⑤ — 표 칸이 아니라 [주] 에만 있어 마스터 원문 줄에 안 실림. 원문 그대로 한 곳.
FUEL_LITERS_PER_HOUR = Decimal("13.5")
#: [주]① 1일 8시간 · [주]② 1일 보통인부 2인(기계운전자 1인은 기계 층 조종원).
HOURS_PER_DAY = Decimal(8)
LABORERS_PER_DAY = Decimal(2)
LABORER_CODE = "1002" # 보통인부
BLADE_MISSING = "파쇄기날 — 규격·단가 없음 · 「자재 단가」 탭에서 넣으면 0.00125개/hr ÷ Q 로 붙음"
_RE_Q = re.compile(r"Q\s*=\s*(\d+(?:\.\d+)?)")
_RE_MISC = re.compile(r"주연료비의\s*(\d+(?:\.\d+)?)\s*%")
_RE_BLADE = re.compile(r"(\d+(?:\.\d+)?)\s*개\s*/\s*hr")
def _table_values(node: dict[str, Any]) -> tuple[Decimal, Decimal, Decimal] | None:
"""(Q ㎥/hr, 잡재료 %, 파쇄기날 개/hr) — 표 한 줄에서. 칸이 달라지면 `None`."""
table = next((t for t in node.get("tables") or [] if t.get("pum_table_id") == TABLE), {})
text = " ".join(str(c) for row in table.get("raw_row") or [] for c in row)
found = [pattern.search(text) for pattern in (_RE_Q, _RE_MISC, _RE_BLADE)]
if not all(found):
return None
return tuple(Decimal(m.group(1)) for m in found) # type: ignore[return-value]
def operating_record(node: dict[str, Any] | None = None):
"""7205-0125 운전경비 — 8-11 [주]⑤ 연료 · 비고 잡재료 · [주]② 기계운전자 1인."""
from B09_Estimation.B09_Estimation_MachineOperating import (
_CATALOG_SUBPATH,
OperatingRecord,
_operator_code,
_read_json,
)
from B09_Estimation.B09_Estimation_ResourceAxis import load_work_item_master
if node is None:
node = next(n for n in load_work_item_master()["work_items"] if n["work_item_code"] == CODE)
values = _table_values(node)
if values is None:
return None
aliases = _read_json(*_CATALOG_SUBPATH, "labor_const_2026-01-01.json")["variables"]["aliases"]
return OperatingRecord(
machine_code=MACHINE,
machine_name="이동식 임목파쇄기",
specification="93.25",
fuel_liters_per_hour=FUEL_LITERS_PER_HOUR,
fuel_kind="경유",
misc_material_percent=values[1],
operator_person_days=Decimal(1),
operator_occupation_code=_operator_code("이동식 임목파쇄기", aliases),
)
def attach_wood_chipping(build: Any, nodes_by_code: dict[str, dict[str, Any]]) -> None:
"""`B-FP-08-11` ㎥당 — 파쇄기 1/Q(D) · 보통인부 2인 ÷ 8시간 ÷ Q · 파쇄기날(단가가 들면)."""
from B09_Estimation.B09_Estimation_PriceBook import PriceDetail, PriceKind, PriceTitle
node = nodes_by_code.get(CODE)
values = _table_values(node or {})
book = build.book
uses = build.material_uses.setdefault(BLADE, [])
if CODE not in uses:
uses.append(CODE)
if node is None or values is None or f"X-{MACHINE}" not in book.titles:
build.component_gaps[CODE] = "8-11 표 칸이 달라졌거나 파쇄기 기계 층이 안 섬"
return
q, _misc, blade_per_hour = values
title = f"B-{CODE}"
book.add_title(
PriceTitle(code=title, kind=PriceKind.UNIT_PRICE, name=str(node.get("name")), unit="㎥")
)
book.add_output_detail(
title, f"X-{MACHINE}", Decimal(1) / q, f"Q = {q} ㎥/hr (산림품셈 8-11)", output=q
)
book.add_detail(
PriceDetail(
title,
LABORER_CODE,
LABORERS_PER_DAY / HOURS_PER_DAY / q,
note=f"[주]② 1일 보통인부 {LABORERS_PER_DAY}인 ÷ [주]① {HOURS_PER_DAY}시간 ÷ Q {q}",
)
)
labels = [label for label in build.unattached.get(CODE, []) if "파쇄기날" not in label]
if BLADE in book.titles:
book.add_detail(
PriceDetail(
title, BLADE, blade_per_hour / q, note=f"파쇄기날 {blade_per_hour}개/hr ÷ Q"
)
)
else:
labels.append(BLADE_MISSING)
build.unattached[CODE] = labels
if CODE in build.skipped:
build.skipped.remove(CODE)