단끊기는 공정 네 줄을 토질 갈래마다 더하고 [주]⑤ 작업반장 1인/보통인부 20인을 더함. 원문 합계 줄은 절취를 ㎥당으로 더한 값이라 안 씀(까닭을 적은 공종만 섬). 같은 모양 뭉기기 13-12-1 은 안 더함. 「단위:」 없이 분모만 적은 본문 밑수를 읽어 조림·시비·떼심기 22표가 밑수 없음에서 풀림. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BW6Jdsh18WPtYUR6THZJqn
147 lines
6.9 KiB
Python
147 lines
6.9 KiB
Python
"""표 읽기 (2026-09-13 축 C 1장 · 브레인 차례 ②).
|
||
|
||
지키는 것
|
||
① 밑수가 **절 이름에만** 있는 표는 이름 전체가 「수+단위+당」일 때만 뽑는다(4-2-2 「1,000㎡당」)
|
||
② 뭉친 줄: 「굴 삭 기 (무한궤도)」 표기 맞추기 · 비고 줄 거름 · 「-」 칸은 그 갈래에 품 없음
|
||
③ 조합 기종 「굴착기+부착용집게」는 공백이 달라도 카탈로그 「부착용 집게」에 붙는다
|
||
④ 형식을 안 적은 「굴 삭 기」는 잠정 우선순위로 고르지 않는다(판정 Ⓒ)
|
||
⑤ 실무값 대조 — 단목베기 5m 미만이 영월 설계내역 「잡관목제거 벌목(5m미만)」 882원/㎡ 근처로 선다
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from decimal import Decimal
|
||
|
||
from B08_Quantity.B08_Quantity_Build_WorkItemMaster import basis_from_name
|
||
from B09_Estimation.B09_Estimation_ResourceAxis import (
|
||
AxisResult,
|
||
CatalogEntry,
|
||
ResourceCatalog,
|
||
match_table,
|
||
)
|
||
|
||
|
||
def test_절_이름_밑수는_이름_전체가_밑수일_때만():
|
||
assert basis_from_name("1,000㎡당") == (1000.0, "㎡")
|
||
assert basis_from_name("100본당") == (100.0, "본")
|
||
assert basis_from_name("나무주사(100본당)") == (None, None)
|
||
assert basis_from_name("자재의 1회당 표준 운반량") == (None, None)
|
||
|
||
|
||
def _packed(*rows: list[str]) -> dict:
|
||
return {
|
||
"pum_table_id": "T-시험",
|
||
"pum_form": "requirement",
|
||
"basis_quantity": 10.0,
|
||
"basis_unit": "㎡",
|
||
"raw_row": [list(row) for row in rows],
|
||
}
|
||
|
||
|
||
MACHINES = [
|
||
CatalogEntry("0201-0080", "굴착기(무한궤도)", "machine", "0.8"),
|
||
CatalogEntry("0211-0080", "굴착기(타이어)", "machine", "0.8"),
|
||
CatalogEntry("0201-0020", "굴착기(무한궤도)", "machine", "0.2"),
|
||
CatalogEntry("7206-0020", "부착용 집게", "machine", "0.2"),
|
||
CatalogEntry("7206-0100", "부착용 집게", "machine", "1.0"),
|
||
CatalogEntry("1002", "보통인부", "labor"),
|
||
CatalogEntry("1037", "벌목부", "labor"),
|
||
CatalogEntry("1003", "특별인부", "labor"),
|
||
]
|
||
|
||
|
||
def _codes(result: AxisResult, variant: str) -> dict[str, Decimal]:
|
||
return {r.resource_code: r.amount for r in result.rows if r.variant == variant}
|
||
|
||
|
||
def test_뭉친_줄_굴삭기_표기를_맞추고_비고는_거른다():
|
||
table = _packed(
|
||
["직경 40㎝이상 ∼60㎝미만", "직경 60㎝이상 ∼80㎝미만"],
|
||
["보통인부", "", "인", "1.04(1.17)", "1.08(1.22)"],
|
||
["굴 삭 기 (무한궤도)", "0.8㎥", "h", "3.84", "3.52"],
|
||
["비고", "- 본 품의 집재거리는 100m까지를 기준하며 매 100m 증가마다 30%씩 가산한다."],
|
||
)
|
||
result = AxisResult()
|
||
match_table({"work_item_code": "FP-13-06-01"}, table, ResourceCatalog(entries=MACHINES), result)
|
||
first = _codes(result, "직경 40㎝이상 ∼60㎝미만")
|
||
assert first["0201-0080"] == Decimal("0.384") # 무한궤도 — 이름에 형식이 적혀 있음
|
||
assert "0211-0080" not in first
|
||
assert "FP-13-06-01" not in result.partial_items # 비고 줄로 막히지 않음
|
||
|
||
|
||
def test_조합_기종은_공백이_달라도_붙는다():
|
||
table = _packed(
|
||
["5m 미만", "5m이상~8m미만"],
|
||
["벌목부 보통인부", "", "인 인", "2.14 0.51", "2.80 0.66"],
|
||
["굴착기+부착용집게", "0.2㎥", "hr", "2.71", "3.54"],
|
||
)
|
||
result = AxisResult()
|
||
match_table({"work_item_code": "FP-04-02-02"}, table, ResourceCatalog(entries=MACHINES), result)
|
||
assert set(_codes(result, "5m 미만")) >= {"0201-0020", "7206-0020"}
|
||
assert "FP-04-02-02" not in result.partial_items
|
||
|
||
|
||
def test_형식_없는_굴삭기와_빈칸_표시():
|
||
table = _packed(
|
||
["식생매트설치", "복 토"],
|
||
["특 별 인 부", "", "인", "0.014", "-"],
|
||
["굴 삭 기", "0.6 ㎥", "시간", "-", "0.031"],
|
||
)
|
||
result = AxisResult()
|
||
match_table({"work_item_code": "FP-13-13-02"}, table, ResourceCatalog(entries=MACHINES), result)
|
||
# 「-」 — 복토 갈래에는 특별인부가 안 든다(자리만 지킴)
|
||
assert _codes(result, "식생매트설치") == {"1003": Decimal("0.0014")}
|
||
# 형식이 없는 굴삭기는 무한궤도·타이어 중 고르지 않는다 → 막고 사유를 남긴다
|
||
assert "굴 삭 기" in result.partial_items.get("FP-13-13-02", "")
|
||
|
||
|
||
def test_단목베기_5m_미만이_실무값_근처로_선다():
|
||
from B09_Estimation.B09_Estimation_UnitPrice import build_unit_prices
|
||
|
||
build = build_unit_prices()
|
||
assert "FP-04-02-02" not in build.partial_ratio
|
||
# 합판거푸집 — 일부러 안 푼 표는 **그 까닭**이 사유로 뜬다(사용횟수 비율 미구현)
|
||
assert "사용횟수" in (build.component_gaps.get("FP-12-04") or "")
|
||
total = build.book.resolve("B-FP-04-02-02#5m미만").total
|
||
# 영월 설계내역 1.9.2 「잡관목제거 벌목(5m미만)」 @882 — 노임 연도 차이로 ±5 % 안이면 같은 읽기
|
||
assert Decimal("838") <= total <= Decimal("926"), total
|
||
|
||
|
||
def test_단위_글자_없는_분모형_밑수도_본문에서_읽는다():
|
||
"""「(인/100m당)」 — 「단위:」 없이 분모만 적은 22 곳(2026-09-13). 「인」은 밑수가 아니다."""
|
||
from B08_Quantity.B08_Quantity_Build_WorkItemMaster import basis_from_source
|
||
|
||
assert basis_from_source(["(인/100m당)", "", "| 공정별 |"], 3) == (100.0, "m")
|
||
assert basis_from_source(["(인/본당)", "| 규격 |"], 2) == (1.0, "본")
|
||
assert basis_from_source(["(무한궤도/0.7㎥)", "| 규격 |"], 2) == (None, None) # 「당」 없음
|
||
|
||
|
||
_단끊기 = {
|
||
"pum_table_id": "F-단끊기",
|
||
"pum_form": "requirement",
|
||
"basis_quantity": 100.0,
|
||
"basis_unit": "m",
|
||
"condition_note": ["공정별", "보통인부(인)", "비고"],
|
||
"raw_row": [
|
||
["보통토사", "경질ㆍ고사점토 및 자갈섞인 점토", "호박돌 섞인 토사", "", ""],
|
||
["절 취", "2.4", "3.3", "5.4", ""],
|
||
["수평잡기 및 단정리", "0.34", "0.34", "0.34", ""],
|
||
["잡석 및 뿌리 정리", "0.36", "0.36", "0.36", ""],
|
||
["절·성토면 고르기", "1.17", "1.17", "1.17", ""],
|
||
["합계", "2.03", "2.09", "2.23", ""],
|
||
],
|
||
}
|
||
|
||
|
||
def test_단끊기는_공정_줄을_더하고_작업반장을_20인당_1인_더한다():
|
||
catalog = ResourceCatalog(entries=[*MACHINES, CatalogEntry("1001", "작업반장", "labor")])
|
||
result = AxisResult()
|
||
match_table({"work_item_code": "FP-05-16-01"}, _단끊기, catalog, result)
|
||
assert _codes(result, "보통토사") == {"1002": Decimal("0.0427"), "1001": Decimal("0.002135")}
|
||
assert _codes(result, "호박돌 섞인 토사")["1002"] == Decimal("0.0727")
|
||
assert "FP-05-16-01" not in result.partial_items
|
||
# 적어 두지 않은 공종은 같은 모양이어도 더하지 않는다(뭉기기 13-12-1 은 줄마다 단위가 다름)
|
||
other = AxisResult()
|
||
match_table({"work_item_code": "FP-13-12-01"}, _단끊기, catalog, other)
|
||
assert not other.rows
|