fix(b09): 깨기 대형브레이커에 굴착기 몸체 시간도 붙임 — 부착 장비라 몸체 없이 못 돎

- 9-12·9-13 「대형브레이커(㎥/hr)」 줄에 같은 표의 굴착기를 같은 1/Q 로 붙이고 #조합(잡재료 16%)으로 바꿔 닮
- 구조물터파기·측구터파기 암·발파암 갈래 단가가 몸체 장비비만큼 오름

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BW6Jdsh18WPtYUR6THZJqn
This commit is contained in:
2026-09-13 19:20:57 +09:00
co-authored by Claude Opus 5
parent a10432d201
commit 488074151c
2 changed files with 32 additions and 5 deletions
@@ -212,8 +212,8 @@ def reference_factor_values(
_CAPACITY_UNIT = re.compile(r"[(]\s*(㎥|m3|㎡|m2|m|ton|t)\s*/\s*(?:hr|시간)\s*[)]")
def _paired_machine_spec(node: dict[str, Any]) -> str:
"""그 표에 함께 나오는 기종의 규격(「유압식백호우 (무한궤도,0.7㎥)」 → 0.7)."""
def _paired_machine(node: dict[str, Any]) -> tuple[str, str, str] | None:
"""그 표에 함께 나오는 기종 — (코드, 이름, 규격). 「유압식백호우 (0.7㎥)」 → 0201-0070."""
from B09_Estimation.B09_Estimation_MachineProductivity import resolve_machine
from B09_Estimation.B09_Estimation_MachineCost import load_machine_catalog
@@ -226,8 +226,13 @@ def _paired_machine_spec(node: dict[str, Any]) -> str:
if found is not None:
machine = catalog.machines.get(found[0])
if machine is not None and machine.specification:
return str(machine.specification)
return ""
return found[0], machine.name, str(machine.specification)
return None
#: 제 엔진이 없는 **부착 장비** 분류번호 — 리퍼·대형 브레이커·진동콤팩터·부착용 집게.
#: `B09_Estimation_UnitPrice._ATTACHMENT_PREFIXES` 와 같은 번호(그쪽은 `X-` 층 코드로 잡음).
_ATTACHMENT_CODE_PREFIXES = ("0103-", "0230-", "0240-", "7206-")
def _machine_by_name(text: str, preferred_spec: str) -> tuple[str, str] | None:
@@ -300,7 +305,8 @@ def direct_capacity_rows(node: dict[str, Any]) -> list[dict[str, Any]]:
ratio: Decimal | None = None
# 같은 표에 짝이 되는 기종이 있으면 **그 규격**을 따른다 — 「대형브레이커」는 규격을
# 안 적고, 실무도 「대형브레이커 + B/H 0.7」처럼 붙는 굴착기 규격으로 잡는다.
paired_spec = _paired_machine_spec(node)
paired = _paired_machine(node)
paired_spec = paired[2] if paired else ""
for table in node.get("tables", []):
for row in table.get("raw_row") or []:
cells = [_clean(cell) for cell in row]
@@ -333,6 +339,18 @@ def direct_capacity_rows(node: dict[str, Any]) -> list[dict[str, Any]]:
"row_cells": [c for c in cells if c],
}
)
# ⚠ 부착 장비(깨기 대형브레이커)는 **굴착기에 달려 돈다** — 몸체 시간도
# 같은 작업량으로 붙인다(2026-09-13 브레인 판정 · 영월 「브레이커조합」 ÷ Q).
# 안 붙이면 장비비가 몸체만큼 모자란다. 본체는 뒤에서 `#조합`(잡재료 16%)으로.
if machine[0].startswith(_ATTACHMENT_CODE_PREFIXES) and paired:
found.append(
{
**found[-1],
"cell": f"{cell} — 몸체 {paired[1]} {paired[2]}",
"machine_code": paired[0],
"machine_name": paired[1],
}
)
# 절 제목·[주]에만 있는 자리 — 표에서 이미 잡혔으면 **덧붙이지 않는다**(두 번 세지 않게).
already = {row["machine_code"] for row in found}
@@ -91,3 +91,12 @@ def test_발파암은_단계_사유로_막히고_고르기형_부모는_오류()
}
blocked = build_bill(handoff, build=_build())
assert any("갈래 고르기형 부모" in m["reason"] for m in blocked.missing)
def test_깨기_브레이커는_굴착기_몸체_시간과_함께_붙는다():
"""9-12·9-13 「깨기 대형브레이커」 — 부착 장비라 몸체도 같은 1/Q 로(2026-09-13 판정)."""
details = _build().book.details["B-FP-09-13-07"]
breaker = next(d for d in details if d.ref_code == "X-0230-0007")
assert breaker.quantity == Decimal(1) / Decimal("3.5") * Decimal("0.9") # 장비 90 % 몫
breaking = [d for d in details if d.quantity == breaker.quantity]
assert {d.ref_code for d in breaking} == {"X-0230-0007", "X-0201-0070#조합"}