"""준비공 상태 칸 — 「입력이 필요함」과 「값을 낼 근거가 없음」을 가른다 (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