노체다짐과 식이 다름 — 롤러는 V·W·E·D·f/N, 콤펙터는 A·N·H·f·E/P. 원문이 답까지 적어 두어(「∙ = 4.26 ㎥/시간」) 표 계수로 셈한 값과 맞대는 시험을 둠. - 표 칸에 단위가 붙어 오는 것(「0.09㎡」·「36,000회/hr」)을 수만 떼어 읽음. - 기종은 절 제목이 정함(플레이트 콤팩터 1.5) — 표에 기종 칸이 없음. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
172 lines
7.4 KiB
Python
172 lines
7.4 KiB
Python
"""B09 — **다짐 공식**으로 서는 공종 (산림품셈 9-16-2 노체다짐 · 9-14-2 플레이트 콤펙터).
|
||
|
||
굴착·운반과 **식이 다르다.** 굴착기는 사이클(㎝)로 세는데 다짐은 롤러가 지나간 넓이로 센다.
|
||
|
||
Q = 1000 × V × W × E × D × f / N (㎥/시간)
|
||
V 다짐속도(㎞/hr) · W 롤러 유효폭(m) · E 작업효율 · D 펴는 흙의 두께(m)
|
||
f 토량환산계수 · N 소요다짐횟수
|
||
|
||
계수는 **표에 다 있고**(9-16-2 여섯 줄), 기종만 [주]에 있어 마스터가 못 싣는다.
|
||
|
||
**⚠⚠ [주]③ 을 어떻게 읽었나 — 이중계상이 갈리는 자리**
|
||
원문 [주]③ 「임도에서는 굴착기(0.7㎥, 무한궤도)와 진동롤러(자주식 10ton)을 **조합**하는
|
||
것을 적용한다」.
|
||
⇒ **포설(9-16-1)의 굴착기와 다짐(9-16-2)의 롤러를 함께 쓰라는 뜻**으로 읽는다.
|
||
다짐 줄 안에 굴착기를 **또** 넣으면 **굴착기가 두 번 선다**(포설에서 이미 셈).
|
||
그래서 이 줄은 **롤러만** 든다.
|
||
|
||
**⚠ 이 줄이 언제 서나 — [주]⑤ 「대규모 성토지로서 층다짐이 필요한 경우 적용한다」**
|
||
조건부 공종이라 늘 서는 것이 아니다. 지금 B08 인계에 이 줄이 **안 온다** —
|
||
실무 내역 셋(영월·울진·봉화)에 「노체포설·노체다짐」으로 가른 줄이 없어
|
||
**성토 본체 한 줄 + 성토면다짐(㎡) 따로**로 맞춰 둔 결정이다(2026-09-08 매핑 기록).
|
||
⚠ **다만 실무의 그 한 줄은 단가 안에 적사→성토→다짐 3단계를 품고 있고, 우리 성토 단가는
|
||
포설(굴착기)뿐이다** — 층다짐을 넣기로 하면 이 단가가 그날 바로 선다.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import re
|
||
from decimal import Decimal
|
||
from typing import Any
|
||
|
||
_ZERO = Decimal(0)
|
||
|
||
#: 계수 이름 — 표 첫 칸이 「V(다짐속도,km/hr)」처럼 기호+설명이거나 「A」처럼 기호뿐이다.
|
||
#: ⚠ **식이 둘이다** — 롤러는 `V·W·E·D·f/N`, 플레이트 콤펙터는 `A·N·H·f·E/P` 다.
|
||
FORMULA_ROLLER = "roller"
|
||
FORMULA_PLATE = "plate"
|
||
_KEYS = {
|
||
FORMULA_ROLLER: ("V", "W", "E", "D", "f", "N"),
|
||
FORMULA_PLATE: ("A", "N", "H", "f", "E", "P"),
|
||
}
|
||
_RE_HEAD = re.compile(r"^([VWEDfNAHP])(?:[((]|$)")
|
||
#: 「0.09㎡」·「36,000회/hr」처럼 **단위가 붙어 오는 칸**이 있다 — 수만 떼어 읽는다.
|
||
_RE_NUMBER = re.compile(r"^-?[\d,]+(?:\.\d+)?")
|
||
|
||
#: 다짐 공식으로 서는 공종 — 기종은 [주] 원문 그대로.
|
||
#: ⚠ **롤러만** 든다(위 [주]③ 읽기). 넓히면 굴착기가 두 번 선다.
|
||
COMPACTION_ITEMS: dict[str, dict[str, Any]] = {
|
||
"FP-09-16-02": {
|
||
"formula": FORMULA_ROLLER,
|
||
"machine_code": "1306-0100", # 진동롤러(자주식) 10ton
|
||
"machine_note": "산림품셈 9-16-2 [주]① 「장비는 자주식 진동롤러(10ton)를 적용한다」",
|
||
"condition_note": (
|
||
"⚠ [주]⑤ 「대규모 성토지로서 층다짐이 필요한 경우 적용한다」 — 늘 서는 줄이 아님"
|
||
),
|
||
"pair_note": (
|
||
"⚠ [주]③ 의 「굴착기와 진동롤러 조합」은 포설(9-16-1)과 다짐(9-16-2)을 함께 쓰라는"
|
||
" 뜻으로 읽음 — 이 줄에 굴착기를 또 넣으면 포설에서 센 굴착기를 두 번 셈"
|
||
),
|
||
},
|
||
"FP-09-14-02": {
|
||
"formula": FORMULA_PLATE,
|
||
"machine_code": "1730-0015", # 플레이트 콤팩터 1.5
|
||
"machine_note": "산림품셈 9-14-2 절 제목 「다짐(플레이트 콤펙터)」 — 표에 기종 칸이 없음",
|
||
"condition_note": "구조물 되메우기 다짐에 쓰는 소형 장비 줄",
|
||
"pair_note": "",
|
||
#: ⚠ **원문이 답까지 적어 두었다** — 「∙ = 4.26 ㎥/시간」. 표 계수로 셈한 값이 이 값과
|
||
#: 어긋나면 우리가 잘못 읽은 것이므로 **시험이 둘을 맞대 본다.**
|
||
"stated_capacity": Decimal("4.26"),
|
||
},
|
||
}
|
||
|
||
|
||
def _number(cells: list[str]) -> Decimal | None:
|
||
for cell in cells:
|
||
text = str(cell or "").strip()
|
||
found = _RE_NUMBER.match(text)
|
||
if found:
|
||
return Decimal(found.group(0).replace(",", ""))
|
||
return None
|
||
|
||
|
||
def compaction_factors(
|
||
table: dict[str, Any], formula: str = FORMULA_ROLLER
|
||
) -> dict[str, Decimal] | None:
|
||
"""표에서 여섯 계수를 뽑는다. 하나라도 없으면 `None` — **채워 넣지 않는다.**"""
|
||
found: dict[str, Decimal] = {}
|
||
for row in table.get("raw_row") or []:
|
||
cells = [str(c or "").strip() for c in row]
|
||
if not cells:
|
||
continue
|
||
head = _RE_HEAD.match(cells[0].replace(" ", ""))
|
||
if not head:
|
||
continue
|
||
value = _number(cells[1:])
|
||
if value is not None:
|
||
found.setdefault(head.group(1), value)
|
||
keys = set(_KEYS[formula])
|
||
if not keys <= set(found):
|
||
return None
|
||
return {key: found[key] for key in keys}
|
||
|
||
|
||
def capacity_per_hour(factors: dict[str, Decimal], formula: str = FORMULA_ROLLER) -> Decimal:
|
||
"""시간당 작업량(㎥/시간). **식이 둘**이라 어느 식인지 함께 받는다.
|
||
|
||
롤러 `Q = 1000 × V × W × E × D × f / N` · 콤펙터 `Q = A × N × H × f × E / P`
|
||
"""
|
||
if formula == FORMULA_PLATE:
|
||
return (
|
||
factors["A"] * factors["N"] * factors["H"] * factors["f"] * factors["E"] / factors["P"]
|
||
)
|
||
return (
|
||
Decimal(1000)
|
||
* factors["V"]
|
||
* factors["W"]
|
||
* factors["E"]
|
||
* factors["D"]
|
||
* factors["f"]
|
||
/ factors["N"]
|
||
)
|
||
|
||
|
||
def compaction_rows(node: dict[str, Any]) -> list[dict[str, Any]]:
|
||
"""그 공종이 다짐 공식으로 서면 붙일 기계 줄. 아니면 빈 목록."""
|
||
entry = COMPACTION_ITEMS.get(str(node.get("work_item_code") or ""))
|
||
if entry is None:
|
||
return []
|
||
kind = str(entry.get("formula") or FORMULA_ROLLER)
|
||
for table in node.get("tables") or []:
|
||
factors = compaction_factors(table, kind)
|
||
if factors is None:
|
||
continue
|
||
divisor = factors["P"] if kind == FORMULA_PLATE else factors["N"]
|
||
if divisor == _ZERO:
|
||
continue
|
||
capacity = capacity_per_hour(factors, kind)
|
||
if capacity <= 0:
|
||
continue
|
||
if kind == FORMULA_PLATE:
|
||
formula = (
|
||
f"Q = {factors['A']:g} × {factors['N']:g} × {factors['H']:g}"
|
||
f" × {factors['f']:g} × {factors['E']:g} ÷ {factors['P']:g}"
|
||
f" = {capacity:g} ㎥/시간"
|
||
)
|
||
else:
|
||
formula = (
|
||
f"Q = 1000 × {factors['V']:g} × {factors['W']:g} × {factors['E']:g}"
|
||
f" × {factors['D']:g} × {factors['f']:g} ÷ {factors['N']:g} = {capacity:g} ㎥/시간"
|
||
)
|
||
return [
|
||
{
|
||
"machine_code": entry["machine_code"],
|
||
"capacity_per_hour": capacity,
|
||
"cell": f"{node.get('name') or '다짐'} Q(㎥/hr)",
|
||
"ratio_pct": None,
|
||
"table_id": str(table.get("pum_table_id") or ""),
|
||
"row_cells": [],
|
||
"source_text": " · ".join(
|
||
part
|
||
for part in (
|
||
formula,
|
||
entry["machine_note"],
|
||
entry.get("pair_note") or "",
|
||
entry["condition_note"],
|
||
)
|
||
if part
|
||
),
|
||
}
|
||
]
|
||
return []
|