"""기초잡석 — 버림 폭에서 나온다 (2026-09-09 사용자 확정 3차 ②). ⚠ 겨누는 것 여섯 ① 두께는 **0.2 m**(사용자 확정) — 품셈 12-25 는 ㎥당 품만 주고 두께를 안 정함 ② 폭을 다시 세지 않는다 — **버림 폭이 곧 잡석다짐 폭**(KCS 34 50 05)이라 두께 비만 곱함 ③ ⚠ 그래서 **관측 원단위로 오는 옹벽에도 값이 선다**(버림 0.145㎥/m ⇒ 잡석 0.29㎥/m · 2026-09-14 원문 값 — 전 라이브러리 반올림 0.15 ⇒ 0.30) ④ 두께는 화면에서 바꿀 수 있다 ⑤ ⚠ 자재총괄에 안 섞인다 — 운반·부설·다짐 품이 붙는 **공종**이다 ⑥ ⚠ 묶음으로 서는 구조물은 줄을 또 세우지 않는다(옹벽 조각이 이미 셈) """ 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_Handoff import build_handoff # noqa: E402 from B08_Quantity.B08_Quantity_Engine_MaterialSummary import ( # noqa: E402 build_table as build_material, ) from B08_Quantity.B08_Quantity_Engine_UnitQuantity import ( # noqa: E402 RUBBLE_BASE_THICKNESS_M, build_table, ) def 돌쌓기(thickness: float | None = None) -> dict: return build_table( [ { "structure_id": "s1", "type_id": "masonry_wet", "start_m": 0.0, "end_m": 10.0, "options": { "height_m": 2.5, "length_m": 10.0, "back_len_cm": 45, "foundation": "기초유", }, } ], None, None, None, thickness, ) def 옹벽() -> dict: return build_table( [ { "structure_id": "w1", "type_id": "retaining_wall", "start_m": 100.0, "end_m": 110.0, "options": {"form": "반중력식", "height_m": 2.0, "length_m": 10.0}, } ], {"retaining_wall": "옹벽"}, ) def 성분(table: dict, name: str) -> dict | None: for structure in table["structures"]: for component in structure["components"]: if component["name"] == name: return component return None def test_두께는_사용자_확정값() -> None: assert RUBBLE_BASE_THICKNESS_M == 0.2 def test_버림_두께_비로_나온다() -> None: """버림 1.2㎥ × (0.2 ÷ 0.1) = 2.4㎥ — 폭을 다시 세지 않는다.""" table = 돌쌓기() assert abs(성분(table, "버림콘크리트")["amount"] - 1.2) < 1e-9 assert abs(성분(table, "기초잡석")["amount"] - 2.4) < 1e-9 def test_관측_원단위_구조물에도_선다() -> None: """⚠ 옹벽은 관측 원단위로 오는데 그 표에 기초잡석 줄이 없다 — 버림에서 나온다.""" assert abs(성분(옹벽(), "기초잡석")["amount"] - 2.9) < 1e-9 # 0.29㎥/m × 10m def test_두께를_바꾸면_따라간다() -> None: assert abs(성분(돌쌓기(0.3), "기초잡석")["amount"] - 3.6) < 1e-9 def test_자재총괄에_안_섞인다() -> None: """⚠ 운반·부설·다짐 품이 붙는 공종이다 — 자재로 서면 같은 잡석을 두 번 센다.""" assert 성분(돌쌓기(), "기초잡석")["destination"] == "unit_price" assert all(row["name"] != "기초잡석" for row in build_material(돌쌓기())["rows"]) def test_인계에_공종_줄로_선다() -> None: rows = build_handoff(unit_quantity_table=돌쌓기())["work_items"] row = next(r for r in rows if r["name"] == "기초잡석") assert row["work_item_code"] == "FP-12-25" assert abs(row["quantity"] - 2.4) < 1e-9 and row["in_bill"] is True def test_묶음_구조물은_줄을_또_세우지_않는다() -> None: """⚠ 옹벽 묶음에 이미 FP-12-25 조각이 있다 — 여기서 또 세우면 두 번 센다.""" rows = build_handoff(unit_quantity_table=옹벽())["work_items"] assert all(row["name"] != "기초잡석" for row in rows) parts = rows[0]["composite_parts"] gravel = next(part for part in parts if part["code"] == "FP-12-25") assert abs(gravel["quantity"] - 2.9) < 1e-9