refactor(B08,B09): 폴더째 old_code 로 옮기고 빈 화면 둘만 남김 (PLAN 7-3)

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
This commit is contained in:
2026-09-22 12:27:45 +09:00
co-authored by Claude Opus 5
parent 185f800d23
commit 8472fc9f40
231 changed files with 1617 additions and 1216 deletions
@@ -0,0 +1,123 @@
"""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)