"""`build_table` 이 양식 있는 종류를 양식으로 갈음 — 값은 전개와 같고 흐름은 그대로. 2026-09-13, PLAN 3장 ④-2. 겨누는 것 넷 ① 찰쌓기 성분이 양식 풀이 값으로 섬 · 구조물에 `library_item` 이 붙음(양식 있음/없음) ② 값·이름·규격·갈 곳이 전개와 같음 — 자재총괄·토공집계 갈래가 안 바뀜(브레인 챙길 것 ③) ③ 양식 없는 종류(메쌓기)는 전개 그대로 · `library_item` 빔 ④ 배합 성분이 자재총괄로 새지 않음(명세 6장·15장) """ from __future__ import annotations import shutil import sys from pathlib import Path import pytest ROOT = Path(__file__).resolve().parents[2] sys.path.insert(0, str(ROOT)) from B08_Quantity.B08_Quantity_Engine_MaterialSummary import ( # noqa: E402 build_table as build_material_table, ) from B08_Quantity.B08_Quantity_Engine_UnitQuantity import MIX_COMPONENTS, build_table # noqa: E402 pytestmark = pytest.mark.skipif(shutil.which("node") is None, reason="node 가 없음") def _structures() -> list[dict]: return [ { "structure_id": "wet", "type_id": "masonry_wet", "start_m": 80.0, "end_m": 90.0, "options": { "height_m": 2.5, "length_m": 10, "foundation": "기초유", "stone_kind": "야면석·호박돌", # 2026-09-14 — 안 고르면 뒷길이 표준 하한(찰 ≤3m 30㎝)이라 야면석 관측 무게(35·45·55)가 # 없어 돌 줄이 안 섬. 돌 줄을 보는 시험이라 45 로 고름 · 빈 칸 길은 아래 시험. "back_len_cm": 45, }, }, { "structure_id": "dry", "type_id": "masonry_dry", "start_m": 140.0, "end_m": 150.0, "options": {"height_m": 2, "length_m": 10, "back_len_cm": 35}, }, ] def test_양식_갈음은_값을_안_움직인다() -> None: names = {"masonry_wet": "돌쌓기(찰)", "masonry_dry": "돌쌓기(메)"} before = build_table(_structures(), names, None, None, 0.2, use_templates=False) after = build_table(_structures(), names, None, None, 0.2) wet_before, dry_before = before["structures"] wet_after, dry_after = after["structures"] assert wet_after["library_item"] == "돌쌓기(찰)" assert dry_after["library_item"] == "" # 양식 없음 — 전개 그대로 assert dry_after["components"] == dry_before["components"] assert len(wet_after["components"]) == len(wet_before["components"]) for got, want in zip(wet_after["components"], wet_before["components"]): assert (got["name"], got["unit"], got["destination"], got["spec"]) == ( want["name"], want["unit"], want["destination"], want["spec"], ) assert got["amount"] == pytest.approx(want["amount"], rel=1e-9, abs=1e-12), got["name"] # 돌 줄은 이름 「돌」 + 규격에 종류(명세 13장 Ⓒ · 2026-09-13 과도기 부채 걷음) · 관측 출처는 그대로. stone = next(c for c in wet_after["components"] if c["name"] == "돌") assert stone["spec"] == "야면석·호박돌" assert stone["source"] == "uljin_library" def test_뒷길이_안_고르면_양식도_전개와_같은_표준_하한() -> None: """품셈 13-4-4 [주]⑩ 하한(찰 ≤3m 30㎝)을 전개와 양식이 한 벌로 씀(브레인 판정 ㉮).""" structures = _structures()[:1] structures[0]["options"].pop("back_len_cm") structures[0]["options"].pop("stone_kind") names = {"masonry_wet": "돌쌓기(찰)"} before = build_table(structures, names, None, None, 0.2, use_templates=False)["structures"][0] after = build_table(structures, names, None, None, 0.2)["structures"][0] assert after["library_item"] == "돌쌓기(찰)" assert [c["name"] for c in after["components"]] == [c["name"] for c in before["components"]] for got, want in zip(after["components"], before["components"]): assert got["amount"] == pytest.approx(want["amount"], rel=1e-9, abs=1e-12), got["name"] assert any("하한 30㎝" in note for note in before["notes"]) def test_자재총괄_갈래와_배합_성분() -> None: names = {"masonry_wet": "돌쌓기(찰)", "masonry_dry": "돌쌓기(메)"} before = build_material_table( build_table(_structures(), names, None, None, 0.2, use_templates=False) ) after = build_material_table(build_table(_structures(), names, None, None, 0.2)) rows = lambda table: {(r["name"], r["unit"]): r["total_amount"] for r in table["rows"]} # noqa: E731 assert rows(after).keys() == rows(before).keys() for key, value in rows(before).items(): assert rows(after)[key] == pytest.approx(value, rel=1e-9), key assert not any(name in MIX_COMPONENTS for name, _unit in rows(after))