Files
Aislo/B09_Estimation/B09_Estimation_WoodChipping.py
eomsangdonandClaude Opus 5 2f9d42ac35 fix(b09): ㉠ 자원 축 줄 읽기가 말없이 버리던 기계·연료 줄을 「못 맞춤 + 일부만」 으로 — 인력만으로 싸게 서던 공종 막기
· 버리던 세 자리: 규격이 이름 앞인 장비 칸(「0.8㎥ 굴착기 | h」 가 머리글로 걸러짐) · 값·이름 둘 다 못 읽은 줄(「디젤엔진(15HP) | 11.19kW」 · 「연료비 | ps × 0.253ℓ × 6h」) · 수량 빈 연료 줄(「연료비 | 경유 | L」)
· 연료·휘발유·경유·엔진·기관·양수기·발전기 이름이거나 시간·대·kW·HP 단위인 줄만 — 머리 줄·설명 줄은 그대로
· 사유 「기계·연료 줄이 안 붙음」 · 파쇄 모듈은 제가 쓰는 파쇄기 줄의 사유를 걷음
· ⚠ 처음 승인받은 「_names_a_machine 을 모든 공종으로」 는 재어 보니 0 을 막아 정정 보고 뒤 이 자리로

새로 사유가 붙은 줄 8 · 사라진 줄 0 · 새로 막힌 공종 5(12-26 물푸기 · 14-1 · 14-2 근주이식 · 10-8-3 케이블크레인 운전 · 10-7-4 모노레일) · 제목 금액 변화 0 · 프로젝트 내역 그대로 · 끄기 둘 빨강 · 전체 시험 1712 + 374 통과

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

122 lines
5.4 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 원가계산 — **이동식 임목 파쇄** 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 and "".join(label.split()) != "이동식임목파쇄기"
]
if "파쇄기" in (build.component_gaps.get(CODE) or ""):
build.component_gaps.pop(CODE)
build.partial_ratio.pop(CODE, None)
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)