feat(b09): 치즐 소모량 — 자원 목록 AR-M 치즐(본) · 대형브레이커 줄 옆 본/hr ÷ Q × 배분으로 같은 D 에 붙음(수동 단가가 있을 때만) · 칸을 채우면 치즐 13공종 온전 · 9-4-1 평균 갈래는 원문에 치즐 평균이 없어 사유
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BW6Jdsh18WPtYUR6THZJqn
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
"""B09 원가계산 — **치즐 소모량**(대형브레이커 깨기 · PLAN 1장 Ⓐ-3 · 2026-09-14 브레인 판정).
|
||||
|
||||
품셈 표가 브레이커 줄 옆에 「치즐소모량(본/hr)」을 줌 — 제8장 [주]⑤ 「브레이커 … 손료 및 치즐
|
||||
소모율을 **추가**」. 실무 원본(영월·봉화·울진) 단가산출근거 「(2) 치즐 손료 · M=0.006 × 223,000 / Q
|
||||
= 382.2 W/㎥」 — 재료비(재료비 집계표 M00118 치즐(대형 브레이카) 본).
|
||||
|
||||
수량 치즐(본/hr) ÷ Q × 장비 배분율 — 브레이커 줄과 같은 D(단가산출)에 나란히(Q 를 함께 실음)
|
||||
단가 자원 목록 `AR-M` 치즐 — **수동 단가가 들어와야** 제목이 섬(임의 단가 없음) ·
|
||||
안 들어오면 종전대로 「못 붙은 줄」
|
||||
⚠ 기계 층(`X-0230` 대형브레이커)은 부착 장비라 손료만 — 치즐은 그 안에 없어 이중계상 아님(시험).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from decimal import Decimal
|
||||
from typing import Any
|
||||
|
||||
#: 자원 목록 치즐(대형브레이커용 · 본) — `resource_catalog_ext` 에 올린 코드.
|
||||
CHISEL_CODE = "AR-M-a2926452"
|
||||
#: 치즐을 붙이는 기계 — 대형브레이커(분류 0230).
|
||||
BREAKER_PREFIX = "0230-"
|
||||
CHISEL_MISSING = "치즐소모량(본/hr) — 치즐 단가 없음(「자재 단가」 탭에서 넣으면 붙음)"
|
||||
CHISEL_AVERAGE_NOT_GIVEN = (
|
||||
"치즐(평균 갈래) — 원문 [주]① 은 Q 평균만 정하고 치즐 평균은 안 줌(지어내지 않음)"
|
||||
)
|
||||
|
||||
|
||||
def chisel_per_hour(node: dict[str, Any]) -> Decimal | None:
|
||||
"""표의 「치즐소모(량)(본/hr)」 줄 값 — 그 이름 칸 뒤 첫 수. 없으면 `None`."""
|
||||
from B09_Estimation.B09_Estimation_MachineProductivity import parse_measure
|
||||
|
||||
for table in node.get("tables", []):
|
||||
for row in table.get("raw_row") or []:
|
||||
cells = ["".join(str(cell).split()) for cell in row]
|
||||
for index, cell in enumerate(cells):
|
||||
if not cell.startswith("치즐소모"):
|
||||
continue
|
||||
value = next(
|
||||
(parse_measure(t) for t in cells[index + 1 :] if parse_measure(t)), None
|
||||
)
|
||||
if value:
|
||||
return value
|
||||
return None
|
||||
|
||||
|
||||
def attach_chisel(
|
||||
book: Any,
|
||||
title_code: str,
|
||||
per_hour: Decimal | None,
|
||||
capacity: Decimal,
|
||||
share: Decimal = Decimal(1),
|
||||
) -> bool:
|
||||
"""치즐 줄을 그 제목의 D 에 붙임 — 치즐 제목(수동 단가)이 없거나 소모량이 없으면 `False`."""
|
||||
if not per_hour or CHISEL_CODE not in book.titles:
|
||||
return False
|
||||
book.add_output_detail(
|
||||
title_code,
|
||||
CHISEL_CODE,
|
||||
per_hour / capacity * share,
|
||||
note=(
|
||||
f"치즐 {per_hour} 본/hr ÷ Q {capacity}"
|
||||
+ (f" × 배분 {share}" if share != 1 else "")
|
||||
+ " — 품셈 제8장 [주]⑤ 치즐 소모율 추가"
|
||||
),
|
||||
output=capacity,
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
def without_chisel_labels(labels: list[str]) -> list[str]:
|
||||
"""치즐이 붙은 뒤 「못 붙은 줄」 에서 치즐 글을 걷음."""
|
||||
return [label for label in labels if "치즐" not in label]
|
||||
@@ -106,12 +106,22 @@ def _add_rock_leaf(build: Any, node: dict[str, Any]) -> None:
|
||||
+ (f" (기계 단가 층 없음: {', '.join(missing)})" if missing else "")
|
||||
)
|
||||
return
|
||||
variants = [(rock, q, f"작업능력 Q = {q} ㎥/hr ({rock})") for rock, q, _ in rows]
|
||||
from B09_Estimation.B09_Estimation_Chisel import (
|
||||
CHISEL_AVERAGE_NOT_GIVEN,
|
||||
CHISEL_MISSING,
|
||||
attach_chisel,
|
||||
)
|
||||
|
||||
variants = [(rock, q, f"작업능력 Q = {q} ㎥/hr ({rock})", chisel) for rock, q, chisel in rows]
|
||||
if code in AVERAGE_BASIS:
|
||||
# 평균 Q 도 **소수 2자리로 확정**한 뒤 나눔(명세 7장) — (5.0+3.4+2.6)/3 = 3.67.
|
||||
# ⚠ 치즐 평균은 원문에 없어 안 붙임(None) — 지어내지 않고 사유로 남김.
|
||||
average = fix2(sum((q for _, q, _ in rows), Decimal(0)) / Decimal(len(rows)))
|
||||
variants.append((AVERAGE_VARIANT, average, f"Q = {average} ㎥/hr — {AVERAGE_BASIS[code]}"))
|
||||
for variant, capacity, note in variants:
|
||||
variants.append(
|
||||
(AVERAGE_VARIANT, average, f"Q = {average} ㎥/hr — {AVERAGE_BASIS[code]}", None)
|
||||
)
|
||||
attached = False
|
||||
for variant, capacity, note, chisel in variants:
|
||||
title_code = f"B-{code}#{variant}"
|
||||
build.book.add_title(
|
||||
PriceTitle(
|
||||
@@ -131,9 +141,23 @@ def _add_rock_leaf(build: Any, node: dict[str, Any]) -> None:
|
||||
f"{note} · {source}",
|
||||
output=capacity,
|
||||
)
|
||||
# 치즐 — 암질마다 표의 본/hr ÷ Q(수동 단가가 들어와 치즐 제목이 섰을 때만).
|
||||
attached = attach_chisel(build.book, title_code, chisel, capacity) or attached
|
||||
build.variants.setdefault(code, []).append(variant)
|
||||
# 표 줄은 다 읽었다 — 남는 것은 치즐뿐(자재 카탈로그가 없어 금액에 안 붙는 알려진 미결).
|
||||
build.unattached[code] = ["치즐소모량(본/hr) — 자재 단가 층 없음"] if rows[0][2] else []
|
||||
# 표 줄은 다 읽었다 — 남는 것은 치즐뿐. 단가가 없으면 못 붙은 줄, 붙었으면 평균 갈래 사유만.
|
||||
if rows[0][2]:
|
||||
# 암질 표는 치즐이 열이라 「치즐소모」 줄로 안 잡힘 — 「자재 단가」 탭 쓰이는 곳에 올림.
|
||||
from B09_Estimation.B09_Estimation_Chisel import CHISEL_CODE
|
||||
|
||||
uses = build.material_uses.setdefault(CHISEL_CODE, [])
|
||||
if code not in uses:
|
||||
uses.append(code)
|
||||
if not rows[0][2]:
|
||||
build.unattached[code] = []
|
||||
elif not attached:
|
||||
build.unattached[code] = [CHISEL_MISSING]
|
||||
else:
|
||||
build.unattached[code] = [CHISEL_AVERAGE_NOT_GIVEN] if code in AVERAGE_BASIS else []
|
||||
|
||||
|
||||
def _step_titles(build: Any, step: str) -> dict[str, str]:
|
||||
|
||||
@@ -797,6 +797,19 @@ def build_unit_prices(
|
||||
from B09_Estimation.B09_Estimation_MaterialPrices import add_material_titles
|
||||
|
||||
build.manual_materials = add_material_titles(build.book, material_prices)
|
||||
from B09_Estimation.B09_Estimation_Chisel import (
|
||||
BREAKER_PREFIX,
|
||||
CHISEL_CODE,
|
||||
attach_chisel,
|
||||
chisel_per_hour,
|
||||
without_chisel_labels,
|
||||
)
|
||||
|
||||
nodes_by_code = {str(node.get("work_item_code")): node for node in master.get("work_items", [])}
|
||||
# 치즐은 자원 축 줄이 아니라 표의 소모량 줄 — 「자재 단가」 탭에 칸이 서게 쓰는 공종을 실음.
|
||||
chisel_items = sorted(code for code, node in nodes_by_code.items() if chisel_per_hour(node))
|
||||
if chisel_items:
|
||||
build.material_uses[CHISEL_CODE] = chisel_items
|
||||
for row in axis.rows:
|
||||
if row.resource_kind == "material":
|
||||
uses = build.material_uses.setdefault(row.resource_code, [])
|
||||
@@ -1005,6 +1018,17 @@ def build_unit_prices(
|
||||
output=fix2(capacity["capacity_per_hour"]),
|
||||
)
|
||||
attached_capacity = True
|
||||
# 치즐 — 대형브레이커 줄 옆 「치즐소모량(본/hr)」 ÷ Q × 배분(Ⓐ-3 · 수동 단가가 있을 때만).
|
||||
if capacity["machine_code"].startswith(BREAKER_PREFIX) and attach_chisel(
|
||||
build.book,
|
||||
title_code,
|
||||
chisel_per_hour(nodes_by_code.get(work_item_code, {})),
|
||||
fix2(capacity["capacity_per_hour"]),
|
||||
group_share,
|
||||
):
|
||||
build.unattached[work_item_code] = without_chisel_labels(
|
||||
build.unattached.get(work_item_code, [])
|
||||
)
|
||||
|
||||
# ⚠ **배분율이 있는 표는 「몇 %가 실제로 붙었나」를 세어 둔다.**
|
||||
# 「인력(10%)·장비(90%)」 표에서 인력만 붙으면 단가가 10 % 몫만인데, 그 값이
|
||||
|
||||
Reference in New Issue
Block a user