B08_Quantity 86 · B09_Estimation 111 파일을 old_code/ 로 옮김(지우지 않음). 화면은 메뉴·주소·단계 막대만 남은 빈 틀 둘 — main.py 라우터 13 개는 끊음. B07 이 빌려 쓰던 비탈 길이·면적은 필요한 함수만 B07_DesignDetail_Engine_SlopeGeometry 로 옮겨 적음(면적 적분·노면 면적·측점 묶음은 안 옮김) · 시험 하나를 새로 둠. B07 구조물도 조립(Cad_StandardSheet)은 2026-09-13 에 이미 도면 목록에서 빠져 부르는 곳이 없어 old_code 로 같이 보냄 — 구조물 그림은 되살리지 않음. B06 구조물 몫 조회는 빈 값으로 두어 화면이 그대로 서게 함. B08·B09 를 부르던 시험 25 개도 old_code/resources/tester 로 옮김. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PAdA5ThqmtVSsbzjusk1cJ
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)
|