Files
Aislo/resources/tester/test_b08_tree_removal_split.py
eomsangdonandClaude Opus 5 4eceedc7d9 fix(axis): 표 읽기 1차 — 뭉친 줄 표기 맞추기·비고 거름·「-」 칸 · 지장목제거를 품셈에 이음
- 뭉친 줄 해석기: 「굴 삭 기 (무한궤도)」를 굴착기(무한궤도)로 맞춰 큰돌쌓기 메쌓기(13-6-1)·
  13-7-2 장비 몫이 붙음(형식을 적은 이름만 · 형식 없는 굴삭기는 판정 Ⓒ대로 규격 미정)
- 비고·합계 줄을 자원으로 안 읽음(단목베기 4-2-2 가짜 막힘 제거) · 「-」 칸은 그 갈래에 품 없음
  (산림복원용 흙막이 13-13-2 가 특별인부·굴삭기 빠진 채 조용히 서던 것을 사유와 함께 막음)
- 지장목제거(판정 Ⓑ): 잡관목제거 → FP-04-02-02 #5m미만 · 뿌리뽑기 → FP-09-21 + 임목축적 등급 갈래 ·
  준비공 「제근·뿌리다듬기」는 같은 면적이라 참조로만(이중계상 막이) · 미결 목록에서 뺌
- 기계 이름 별칭(백호·굴삭기→굴착기)은 별칭표 대상이 아닌 카탈로그 이름 정규화로 주석 정리
- 일위대가 전후 대조(HEAD↔수정): 새로 선 공종 13-6-1·13-7-2 · 새로 막힌 공종 13-13-2(사유 있음) ·
  내역 금액: 단목베기·제근은 밑수 미확보로 계속 막혀 변화 없음 · 전체 시험 1513 통과

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BW6Jdsh18WPtYUR6THZJqn
2026-09-13 17:52:04 +09:00

94 lines
3.7 KiB
Python

"""지장목제거를 두 줄로 가름 (2026-09-09 사용자 확정 5차 2번).
실무(영월 설계내역서 1.9)는 **한 면적에 두 작업**을 얹는다 —
1.9.1 뿌리뽑기(장비+인력) 11,035㎡ @475
1.9.2 잡관목제거 벌목(5m미만) 11,035㎡ @882 ← **같은 11,035㎡**
⚠ 겨누는 것 다섯
① 두 줄이 선다
② ⚠ **같은 면적**이 두 줄에 그대로 들어간다 — **이중계상이 아니다**(다른 작업이 얹히는 것)
③ 그 사실이 사유에 적힌다
④ ⚠ 작업 갈래(뿌리뽑기·잡관목제거)를 **지반 갈래로 읽지 않는다** — 읽으면
「시공법 미지정으로 공종을 못 고름」이라는 **틀린 사유**가 붙는다
⑤ 잡관목제거는 **품셈에 이름이 없음**이 사유에 남는다(공종 보류 — 확정 5차 3번)
"""
from __future__ import annotations
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
from B08_Quantity.B08_Quantity_Engine_EarthworkSummary import ( # noqa: E402
SummaryInput,
build_table,
)
from B08_Quantity.B08_Quantity_Engine_Handoff import build_handoff # noqa: E402
사면 = {"tree_removal_fill": 9000.0, "tree_removal_cut": 8873.8}
def 집계() -> dict:
return build_table(
SummaryInput(
earthwork_totals={},
slope_totals=사면,
haul_rows=[],
rock_classes=["토사"],
rock_ratios_pct={},
application_ratios={},
)
)
def 줄들() -> list[dict]:
return [
row
for row in build_handoff(summary_table=집계())["work_items"]
if row["name"] == "지장목제거"
]
def test_두_줄이_선다() -> None:
assert [row["spec"] for row in 줄들()] == ["뿌리뽑기", "잡관목제거"]
def test_같은_면적이_두_줄에_들어간다() -> None:
"""⚠ 이중계상이 아니다 — 한 면적에 **다른 두 작업**이 얹히는 실무 서식이다."""
amounts = {row["quantity"] for row in 줄들()}
assert amounts == {17873.8}
def test_이중계상이_아님이_사유에_적힌다() -> None:
for row in 줄들():
assert "이중계상 아님" in row["in_bill_reason"] or "이중계상 아님" in str(
row["spec_detail"]
)
def test_작업_갈래를_지반_갈래로_읽지_않는다() -> None:
"""⚠ 읽으면 「시공법 미지정으로 공종을 못 고름」이라는 틀린 사유가 붙는다."""
for row in 줄들():
assert row["ground_class"] is None
unmatched = build_handoff(summary_table=집계())["unmatched_work_items"]
assert not any("지장목제거" in item for item in unmatched) # 두 작업 갈래 모두 이어짐
def test_잡관목제거는_단목베기_5m_미만으로_이어진다() -> None:
"""⭐ 2026-09-13 판정 — 지장목제거는 품셈에 있다(벌목 4장 + 제근 9-20~21)."""
잡관목 = next(row for row in 줄들() if row["spec"] == "잡관목제거")
assert 잡관목["work_item_code"] == "FP-04-02-02"
assert 잡관목["variant_value"] == "5m 미만"
뿌리뽑기 = next(row for row in 줄들() if row["spec"] == "뿌리뽑기")
assert 뿌리뽑기["work_item_code"] == "FP-09-21"
assert 뿌리뽑기["variant_axis"] == "stand_volume_class"
def test_뿌리뽑기는_임목축적_등급을_갈래로_싣는다() -> None:
"""⭐ 판정 Ⓑ — 품이 등급(소림·중림·밀림)으로 갈려 갈래를 잃으면 안 된다."""
rows = build_handoff(summary_table=집계(), stand_volume_class="중림")["work_items"]
뿌리뽑기 = next(r for r in rows if r["name"] == "지장목제거" and r["spec"] == "뿌리뽑기")
assert 뿌리뽑기["variant_value"] == "중림"