- 개소를 넣어도 공종이 없어 단가가 안 서는 줄(국가지점번호판·안내판·차단기·수방대책 자재) — 「입력이 필요함」이면 넣고 기다려도 안 섬 - 개소가 선 줄은 자재총괄 사급 줄로 감 → 「자재 단가」 탭에 칸이 섬(화약류·치즐과 같은 통로) · 인계 공종 줄은 막힘 아님(두 번 안 셈) - 개소 안 넣은 줄은 종전대로 「입력이 필요함」 - 936be972: 국가지점번호판 3개소 넣음 → 셋째 상태 · 자재 단가 탭 「국가지점번호판 개소 3.0」 · 되돌림 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016VBGFXB9AbJBwXP19z75Qq
136 lines
6.5 KiB
Python
136 lines
6.5 KiB
Python
"""준비공 상태 칸 — 「입력이 필요함」과 「값을 낼 근거가 없음」을 가른다 (2026-09-14 브레인 ㉴).
|
|
|
|
훑기에서 잡은 거짓 사유: 상태 칸이 **입력만 넣으면 서는 줄**(표토 두께·거리 · 임목폐기물 조사값 ·
|
|
부대시설 개소 · 임목파쇄 부피)까지 「값을 낼 근거가 없음」으로 덮었다. 반대로 인계는
|
|
**우리가 만들어야 하는 줄**(사방 원단위 · 뿌리 부피)까지 「입력이 필요합니다」로 보냈다.
|
|
|
|
① 입력이면 풀리는 줄 → 상태 「입력이 필요함」 · 인계 `input_missing`
|
|
② 자료·산식이 없는 줄 → 상태 「값을 낼 근거가 없음」 · 인계 `unit_data_missing`
|
|
③ 앞 단계(횡단·사면표)가 안 선 줄도 사람이 할 일이라 ①
|
|
④ 규준틀이 필요 없는 노선(비탈길이 10m 이상 구간 없음)은 「해당 없음」
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from B08_Quantity.B08_Quantity_Engine_Handoff import build_handoff
|
|
from B08_Quantity.B08_Quantity_Engine_Preparation import build_table
|
|
from B08_Quantity.B08_Quantity_Engine_Preparation_Status import (
|
|
STATUS_NEEDS_INPUT,
|
|
STATUS_NOT_APPLICABLE,
|
|
STATUS_PENDING,
|
|
)
|
|
|
|
SLOPE = {"tree_removal_fill": 600.0, "tree_removal_cut": 400.0, "face_dressing_cut": 300.0}
|
|
SHORT_SLOPES = [{"lengths": {"fill": 4.0}, "distance_m": 20.0, "fill_height_m": 2.0}]
|
|
|
|
|
|
def _table(**overrides: object) -> dict:
|
|
args: dict = {
|
|
"slope_totals": SLOPE,
|
|
"structures": [{"type_id": "erosion_check"}],
|
|
"slope_rows": SHORT_SLOPES,
|
|
"road_surface_area_m2": 1000.0,
|
|
"chipping_enabled": True,
|
|
"tree_waste": {},
|
|
"ancillary_counts": {},
|
|
}
|
|
args.update(overrides)
|
|
return build_table(**args)
|
|
|
|
|
|
def _by_item(table: dict) -> dict[str, dict]:
|
|
return {row["item"]: row for row in table["rows"]}
|
|
|
|
|
|
def test_입력이면_서는_줄은_입력이_필요함() -> None:
|
|
rows = _by_item(_table())
|
|
for item in (
|
|
"표토 운반·적치",
|
|
"임목파쇄",
|
|
"임목폐기물 처리",
|
|
"국가지점번호판",
|
|
"가설창고(컨테이너)",
|
|
):
|
|
assert rows[item]["status"] == STATUS_NEEDS_INPUT, item
|
|
# 두께를 넣으면 거리 칸이 남는다 — 여전히 입력이 필요함.
|
|
with_thickness = _by_item(_table(topsoil_thickness_m=0.2))
|
|
assert with_thickness["표토 운반·적치"]["status"] == STATUS_NEEDS_INPUT
|
|
|
|
|
|
def test_자료나_산식이_없는_줄은_근거가_없음() -> None:
|
|
rows = _by_item(_table())
|
|
assert rows["뿌리 운반"]["status"] == STATUS_PENDING
|
|
assert (
|
|
rows[next(k for k, r in rows.items() if r["group"] == "사방공")]["status"] == STATUS_PENDING
|
|
)
|
|
|
|
|
|
def test_앞_단계가_안_섰으면_입력이_필요함() -> None:
|
|
rows = _by_item(_table(slope_totals={}, slope_rows=[], road_surface_area_m2=None))
|
|
assert rows["표토제거"]["status"] == STATUS_NEEDS_INPUT
|
|
assert rows["뿌리 적재"]["status"] == STATUS_NEEDS_INPUT
|
|
assert rows["비탈 규준틀"]["status"] == STATUS_NEEDS_INPUT
|
|
assert rows["수평 규준틀"]["status"] == STATUS_NEEDS_INPUT
|
|
|
|
|
|
def test_규준틀이_필요_없는_노선은_해당_없음() -> None:
|
|
assert _by_item(_table())["비탈 규준틀"]["status"] == STATUS_NOT_APPLICABLE
|
|
|
|
|
|
def test_인계_막힘_갈래가_상태를_따른다() -> None:
|
|
table = _table()
|
|
handed = {row["name"]: row for row in build_handoff(preparation_table=table)["work_items"]}
|
|
assert handed["표토 운반·적치"]["blocked_kind"] == "input_missing"
|
|
assert handed["임목파쇄"]["blocked_kind"] == "input_missing"
|
|
# 우리가 만들어야 하는 줄 — 「입력이 필요합니다」로 보내면 사용자가 헛걸음한다.
|
|
assert handed["뿌리 운반"]["blocked_kind"] == "unit_data_missing"
|
|
assert handed["골막이"]["blocked_kind"] == "unit_data_missing"
|
|
# 해당 없음은 막힘이 아니다.
|
|
assert handed["비탈 규준틀"]["blocked_kind"] is None
|
|
# 개소를 안 넣은 부대시설은 입력 갈래(개소를 넣으면 수량이 섬).
|
|
assert handed["국가지점번호판"]["blocked_kind"] == "input_missing"
|
|
|
|
|
|
def test_품셈_공종이_없는_부대시설은_셋째_상태로_자재_단가에_잇는다() -> None:
|
|
"""개소를 넣어도 공종이 없어 단가가 영영 안 선다 — 「입력이 필요함」으로 두면 사용자가 기다린다.
|
|
|
|
브레인 판정(2026-09-14): 「품셈 공종 없음 — 단가를 직접 넣어야 함」 · 「자재 단가」 탭 통로
|
|
(화약류·치즐과 같은 자리) — 수량은 자재총괄(사급) 줄로 가고 단가를 넣으면 금액이 선다.
|
|
"""
|
|
from B08_Quantity.B08_Quantity_Engine_Preparation_Status import STATUS_NO_WORK_ITEM
|
|
from B08_Quantity.B08_Quantity_Router_Material import material_table_for
|
|
|
|
entered = _table(ancillary_counts={"national_point_sign": 3, "site_container": 1})
|
|
rows = _by_item(entered)
|
|
assert rows["국가지점번호판"]["status"] == STATUS_NO_WORK_ITEM
|
|
assert STATUS_NO_WORK_ITEM == "품셈 공종 없음 — 단가를 직접 넣어야 함"
|
|
assert "자재 단가" in rows["국가지점번호판"]["reason"]
|
|
# 공종이 있는 가설창고는 그대로 값 있음.
|
|
assert rows["가설창고(컨테이너)"]["status"] == "값 있음"
|
|
# 인계 — 막힘이 아니라 자재 줄로 가는 줄(두 번 세지 않음).
|
|
sign = next(
|
|
row
|
|
for row in build_handoff(preparation_table=entered)["work_items"]
|
|
if row["name"] == "국가지점번호판"
|
|
)
|
|
assert sign["blocked_kind"] is None and sign["in_bill"] is False
|
|
# 자재총괄 사급 줄 — 「자재 단가」 탭이 이 이름으로 칸을 세운다.
|
|
materials = material_table_for({"rows": []}, {}, entered)["rows"]
|
|
board = [row for row in materials if row["name"] == "국가지점번호판"]
|
|
assert len(board) == 1
|
|
assert (board[0]["unit"], board[0]["net_amount"], board[0]["supply"]) == (
|
|
"개소",
|
|
3.0,
|
|
"contractor_supplied",
|
|
)
|
|
# 개소를 안 넣은 공종 없는 줄은 자재로 안 감(수량이 없음).
|
|
assert not [row for row in materials if row["name"] == "임도 안내판"]
|
|
|
|
|
|
def test_표_머리가_두_갈래를_따로_센다() -> None:
|
|
table = _table()
|
|
rows = table["rows"]
|
|
assert table["input_count"] == sum(1 for r in rows if r["status"] == STATUS_NEEDS_INPUT)
|
|
assert table["pending_count"] == sum(1 for r in rows if r["status"] == STATUS_PENDING)
|
|
assert table["input_count"] > 0 and table["pending_count"] > 0
|