feat(B09): 깨기(대형브레이커)가 붙어 구조물터파기가 섬 — 본체 3,089만원
품셈 제8장 [주]⑤ 「불도저 및 굴착기에 리퍼, 브레이커, 부착용집게를 조합하여 사용할 때는 …잡재료비율을 16%로 계상하고, 리퍼, 브레이커, 부착용 집게의 손료 및 치즐 소모율을 추가하는 것이다」 를 따라감. 세 자리를 이었음 1. **뭉개진 카탈로그 줄을 원문으로 되살림** — `mach_base_2026.json` 의 대형 브레이커 여섯 줄이 이름 칸에 표 전체가 뭉쳐 규격·손료계수가 없었음. 건설품셈 제8장 (0230) 표에서 읽어 채움. ⚠ **두 번 검증** — 계수 합 3,000+2,833+768=6,601 이 표의 「계」와 같고, 같은 방식으로 읽은 굴착기 「계 2,085」가 카탈로그 0.0002085 와 정확히 일치 2. **부착용 장비는 손료만으로 층을 세움** — 제 엔진이 없어 운전경비표에 줄이 없음. 없다고 버리면 깨기 몫이 영영 안 붙음. ⚠ 리퍼·브레이커·집게에만 여는 길 3. **작업량을 직접 준 기계 줄을 읽음** — 「대형브레이커(㎥/hr) 3.5」. 공식이 아니라 시간당 작업량을 표가 바로 주는 모양. 단위가 붙은 칸만 읽음(짐작 금지) 결과 - 구조물터파기(암절취·육상·2~3m) 155.625㎥ × 72,378.4 = **11,263,899원** — 처음 섬 - 돌쌓기 단가에 **부착용 집게 손료**가 더해짐(메 76,607.9 → 78,864.5) — [주]⑤ 그대로 - 본체 19,444,626 → **30,892,279원** - 남은 것은 **치즐(자재) 하나** — 기계가 빠지면 막고 자재 소모품이 빠지면 드러내기만 하는 규칙대로, 금액을 세우고 본표가 「치즐소모량 줄이 아직 안 붙었습니다」를 말함 ⚠ 아직 안 한 것 — 같은 [주]⑤ 의 **「잡재료비율 16%」**. 조합 사용 시 굴착기 잡재료가 22% 가 아니라 16% 임. 그 자리는 운전경비 층이라 따로 손봐야 함. 검증: pytest 290 통과(신규 3) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -193,3 +193,110 @@ def reference_factor_values(
|
||||
raw_texts[code] = own_ref[1]
|
||||
|
||||
return values, provenance, failures, raw_texts
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 작업량을 **직접 준** 기계 줄 (2026-09-09)
|
||||
# ---------------------------------------------------------------------------
|
||||
#
|
||||
# 품셈은 기계 몫을 늘 공식으로만 주지 않는다. **시간당 작업량을 바로 적는** 줄이 있다.
|
||||
#
|
||||
# ['장비 (90%)', '깨기', '대형브레이커(㎥/hr)', '3.5', 'Q=(3.2+3.8)/2 (연암평균치 적용)']
|
||||
#
|
||||
# 이 줄을 못 읽으면 암·발파암 갈래의 **깨기 몫이 통째로 빠진다** — 들어내기(백호우)만
|
||||
# 붙어 「일부만 선 단가」로 남는다.
|
||||
#
|
||||
# ⚠ **단위가 붙어 있을 때만 읽는다.** 「(㎥/hr)」·「(m/hr)」처럼 시간당 작업량임을
|
||||
# 표가 스스로 밝힌 줄만 본다. 숫자만 있는 칸을 작업량으로 넘겨짚지 않는다.
|
||||
|
||||
_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)."""
|
||||
from B09_Estimation.B09_Estimation_MachineProductivity import resolve_machine
|
||||
|
||||
from B09_Estimation.B09_Estimation_MachineCost import load_machine_catalog
|
||||
|
||||
catalog = load_machine_catalog()
|
||||
for table in node.get("tables", []):
|
||||
for row in table.get("raw_row") or []:
|
||||
for cell in row:
|
||||
found = resolve_machine(_clean(cell))
|
||||
if found is not None:
|
||||
machine = catalog.machines.get(found[0])
|
||||
if machine is not None and machine.specification:
|
||||
return str(machine.specification)
|
||||
return ""
|
||||
|
||||
|
||||
def _machine_by_name(text: str, preferred_spec: str) -> tuple[str, str] | None:
|
||||
"""이름만으로 기종을 고른다 — 규격이 여럿이면 **짝의 규격**을 따른다.
|
||||
|
||||
⚠ 「대형브레이커(㎥/hr)」는 괄호가 **규격이 아니라 단위**라 보통 길로는 안 풀린다.
|
||||
"""
|
||||
from B09_Estimation.B09_Estimation_MachineCost import load_machine_catalog
|
||||
|
||||
wanted = re.sub(r"\s", "", text)
|
||||
if not wanted:
|
||||
return None
|
||||
catalog = load_machine_catalog()
|
||||
hits = [
|
||||
(code, machine)
|
||||
for code, machine in catalog.machines.items()
|
||||
if wanted and wanted in re.sub(r"\s", "", machine.name)
|
||||
]
|
||||
if not hits:
|
||||
return None
|
||||
if preferred_spec:
|
||||
narrowed = [item for item in hits if str(item[1].specification) == str(preferred_spec)]
|
||||
if len(narrowed) == 1:
|
||||
return narrowed[0][0], narrowed[0][1].name
|
||||
return (hits[0][0], hits[0][1].name) if len(hits) == 1 else None
|
||||
|
||||
|
||||
def direct_capacity_rows(node: dict[str, Any]) -> list[dict[str, Any]]:
|
||||
"""그 공종에서 **시간당 작업량을 직접 준 기계 줄**들.
|
||||
|
||||
돌려주는 것 — 기계 이름 칸 · 기종 코드/이름 · 시간당 작업량 · 묶음 배분율(%) · 원문 문구.
|
||||
"""
|
||||
from B09_Estimation.B09_Estimation_MachineProductivity import parse_measure
|
||||
|
||||
found: list[dict[str, Any]] = []
|
||||
ratio: Decimal | None = None
|
||||
# 같은 표에 짝이 되는 기종이 있으면 **그 규격**을 따른다 — 「대형브레이커」는 규격을
|
||||
# 안 적고, 실무도 「대형브레이커 + B/H 0.7」처럼 붙는 굴착기 규격으로 잡는다.
|
||||
paired_spec = _paired_machine_spec(node)
|
||||
for table in node.get("tables", []):
|
||||
for row in table.get("raw_row") or []:
|
||||
cells = [_clean(cell) for cell in row]
|
||||
if not cells:
|
||||
continue
|
||||
seen_ratio = re.search(r"[((]\s*(\d+(?:\.\d+)?)\s*%\s*[))]", cells[0])
|
||||
if seen_ratio:
|
||||
ratio = Decimal(seen_ratio.group(1))
|
||||
for index, cell in enumerate(cells):
|
||||
if not _CAPACITY_UNIT.search(cell):
|
||||
continue
|
||||
machine = _machine_by_name(_CAPACITY_UNIT.sub("", cell).strip(), paired_spec)
|
||||
if machine is None:
|
||||
continue
|
||||
capacity = next(
|
||||
(parse_measure(token) for token in cells[index + 1 :] if parse_measure(token)),
|
||||
None,
|
||||
)
|
||||
if capacity is None or capacity <= 0:
|
||||
continue
|
||||
found.append(
|
||||
{
|
||||
"cell": cell,
|
||||
"machine_code": machine[0],
|
||||
"machine_name": machine[1],
|
||||
"capacity_per_hour": capacity,
|
||||
"ratio_pct": ratio,
|
||||
"table_id": str(table.get("pum_table_id", "")),
|
||||
# 그 줄의 칸들 — 「못 붙은 줄」 목록에서 이 줄을 걷어내는 데 쓴다.
|
||||
"row_cells": [c for c in cells if c],
|
||||
}
|
||||
)
|
||||
return found
|
||||
|
||||
Reference in New Issue
Block a user