· 계수표(T·L·E·q0·e·f·V1·V2·t) 칸을 기호로 읽어 불도저 식(8-2-1 · dozer_hourly_output) 그대로 Q1 47.92 ㎥/hr · Q = Q1 ÷ T 0.2 = 239.60 ㎡/hr · 기종은 q0 3.2 · V1 40 · V2 46(1단)로 8-2-1 표에서 되짚음 → 무한궤도 19ton([주]① 과 맞음) · D 줄 1/Q hr/㎡ · 원문 대 실무 어긋남 기록 — 영월 호표 Q=576.95 는 E 자리에 e(0.96)를 넣은 값으로 정확히 역산됨 · 다른 실무엔 이 호표 없음 → 원문이 또렷해 원문 E=0.4 로 셈(공종 사유 한 줄) 새 제목 B·D 618원/㎡ · 바뀐 제목 0 · 프로젝트 내역 표토제거 9,954.32㎡ 가 금액으로 섬(+6,151,768) · 잴 시험 둘 빨강→초록 · 끄기 셋 빨강 · 전체 시험 1703 + 364 통과 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BW6Jdsh18WPtYUR6THZJqn
113 lines
4.6 KiB
Python
113 lines
4.6 KiB
Python
"""B09 원가계산 — **표토제거 답외구간** 9-15-2 (2026-09-14 브레인 ㉰ 첫째).
|
||
|
||
B08 준비공 「표토제거」 줄이 부르는 코드인데 표가 계수표(T·L·E·q0·e·f·V1·V2·t)라 일위대가가 안 섰음.
|
||
|
||
q = q0 × e · ㎝ = L/V1 + L/V2 + t · Q1 = 60 × q × f × E / ㎝ (㎥/hr) · Q = Q1 / T (㎡/hr)
|
||
[주]① 무한궤도 불도저(19ton) · ③ 건설품셈 8-2-1 불도저 참조 → 불도저 식(`dozer_hourly_output`) 그대로 + T 로 나눔
|
||
기종은 표의 q0·V1·V2(1단)로 8-2-1 표에서 되짚음(`resolve_dozer`) — [주]① 19ton 과 맞는지 시험이 봄
|
||
|
||
⚠ 실무 영월 호표 Q=576.95 는 E 자리에 e(0.96)를 넣은 값 — 원문이 또렷해 원문 E 로 셈(까닭은 `KnownGaps`).
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import re
|
||
from decimal import Decimal
|
||
from typing import Any
|
||
|
||
CODE = "FP-09-15-02"
|
||
TABLE = "F0282"
|
||
_RE_SYMBOL = re.compile(r"^([A-Za-z]\d?)\s*\(")
|
||
_RE_GEAR = re.compile(r"(\d+)\s*단")
|
||
|
||
|
||
def _factors(node: dict[str, Any]):
|
||
"""(불도저 계수, T) — 칸이 모자라거나 기종이 안 좁혀지면 까닭 글."""
|
||
from B09_Estimation.B09_Estimation_MachineProductivity import parse_measure
|
||
from B09_Estimation.B09_Estimation_MachineProductivity_Dozer import DozerFactors, resolve_dozer
|
||
|
||
table = next((t for t in node.get("tables") or [] if t.get("pum_table_id") == TABLE), {})
|
||
values: dict[str, Decimal] = {}
|
||
gear = 1
|
||
for row in table.get("raw_row") or []:
|
||
found = _RE_SYMBOL.match(str(row[0]).strip()) if row else None
|
||
value = parse_measure(str(row[1])) if found and len(row) > 1 else None
|
||
if value is not None:
|
||
values[found.group(1)] = value
|
||
shift = _RE_GEAR.search(str(row[1]))
|
||
gear = int(shift.group(1)) if shift else gear
|
||
missing = [k for k in ("T", "L", "E", "q0", "e", "f", "V1", "V2") if k not in values]
|
||
if missing:
|
||
return f"9-15-2 표 칸 없음: {', '.join(missing)}"
|
||
machine = resolve_dozer(values["q0"], values["V1"], values["V2"], gear)
|
||
if machine is None:
|
||
return f"삽날 {values['q0']}㎥ · {values['V1']}/{values['V2']}m/분({gear}단) 으로 불도저가 안 좁혀짐"
|
||
factors = DozerFactors(
|
||
work_item_code=CODE,
|
||
blade_capacity_m3=values["q0"],
|
||
distance_factor=values["e"],
|
||
volume_factor=values["f"],
|
||
efficiency=values["E"],
|
||
haul_distance_m=values["L"],
|
||
forward_speed_m_min=values["V1"],
|
||
reverse_speed_m_min=values["V2"],
|
||
machine_code=machine[0],
|
||
machine_name=machine[1],
|
||
)
|
||
return factors, values["T"]
|
||
|
||
|
||
def topsoil_output(node: dict[str, Any] | None = None) -> tuple[Decimal, Decimal]:
|
||
"""(Q1 ㎥/hr, Q ㎡/hr) — 둘 다 소수 2자리로 확정한 뒤 씀(명세 7장)."""
|
||
from B09_Estimation.B09_Estimation_MachineProductivity import fix2
|
||
from B09_Estimation.B09_Estimation_MachineProductivity_Dozer import dozer_hourly_output
|
||
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)
|
||
found = _factors(node)
|
||
if isinstance(found, str):
|
||
raise ValueError(found)
|
||
factors, thickness = found
|
||
q1 = dozer_hourly_output(factors)
|
||
return q1, fix2(q1 / thickness)
|
||
|
||
|
||
def attach_topsoil_removal(build: Any, nodes_by_code: dict[str, dict[str, Any]]) -> None:
|
||
"""`B-FP-09-15-02` — 불도저 1/Q hr/㎡ 한 줄(D). 기계 층이 없거나 표가 달라지면 까닭만."""
|
||
from B09_Estimation.B09_Estimation_PriceBook import PriceKind, PriceTitle
|
||
|
||
node = nodes_by_code.get(CODE)
|
||
title_code = f"B-{CODE}"
|
||
if node is None or title_code in build.book.titles:
|
||
return
|
||
found = _factors(node)
|
||
if isinstance(found, str):
|
||
build.component_gaps[CODE] = found
|
||
return
|
||
factors, thickness = found
|
||
hourly = f"X-{factors.machine_code}"
|
||
if hourly not in build.book.titles:
|
||
build.component_gaps[CODE] = f"{factors.machine_name} 시간당 사용료가 안 섬"
|
||
return
|
||
q1, q = topsoil_output(node)
|
||
build.book.add_title(
|
||
PriceTitle(
|
||
code=title_code,
|
||
kind=PriceKind.UNIT_PRICE,
|
||
name=str(node.get("name") or CODE),
|
||
spec="표토제거",
|
||
unit="㎡",
|
||
)
|
||
)
|
||
build.book.add_output_detail(
|
||
title_code,
|
||
hourly,
|
||
Decimal(1) / q,
|
||
f"{factors.formula_text} → Q = Q1 {q1} ÷ T {thickness}m = {q} ㎡/hr"
|
||
" (산림품셈 9-15-2 [주]②③ · 건설 8-2-1)",
|
||
output=q,
|
||
)
|
||
if CODE in build.skipped:
|
||
build.skipped.remove(CODE)
|