B05·B06·B08·B09·Z01·common_util 의 data_* 폴더·파일 이름 상수를 master_data/old · ref 의 새 이름으로 돌림. 로직은 그대로. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WRFJmmhPi4exAzHgPZHMT
124 lines
5.4 KiB
Python
124 lines
5.4 KiB
Python
"""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, "1_기초_노임_건설업_대한건설협회_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)
|