"""규준틀 재료 — **세는 것은 확정이고 수량만 몰랐던 자리** (2026-09-09). 품셈 11-2·11-3 [주]④ 「재료량은 **설계수량에 따른다**」 ⇒ 품셈이 값을 안 주는 것이지 「안 센다」가 아니다. 근주이식·임목파쇄(**셀지 말지가 설계 판단**)와 성격이 다르다. 그래서 **제안값을 보이고 고칠 수 있게** 둔다 — 확정 ⑨·⑩ 과 같은 틀. ⚠ 겨누는 것 여섯 ① 개소가 서면 재료도 선다 ② ⚠ 개소가 안 서면 재료도 안 선다 — 밑수가 그 줄이다 ③ 값은 **제안값**이고 산출 조건이 이긴다 ④ ⚠ 제안값이 **실무 관측값이지 법정 기준이 아님**이 사유에 적힌다 ⑤ 손율은 원문값이 함께 적힌다(비탈 50% · 수평 80%) ⑥ 자재 축으로 간다(할증은 자재총괄에서 한 번만) """ 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_Preparation import ( # noqa: E402 FRAME_MATERIAL_SUGGESTED, frame_material_rows, ) 규준틀 = [ {"item": "비탈 규준틀", "unit": "개소", "amount": 28.0}, {"item": "수평 규준틀", "unit": "개소", "amount": 33.0}, ] def 이름별(rows: list[dict]) -> dict: return {(row["source"], f"{row['name']} {row.get('spec', '')}".strip()): row for row in rows} def test_이름과_규격을_가른다() -> None: """이름 칸에 규격을 섞으면 할증률표(각재 5 · 판재 10)와 안 맞음 — 관급구분 키는 그대로.""" from B08_Quantity.B08_Quantity_Engine_MaterialSummary import build_table rows = frame_material_rows(규준틀) assert {(row["name"], row["spec"]) for row in rows} == { ("각재", "50×50"), ("판재", "T12"), ("못", ""), } table = build_table({"structures": []}, extra_materials=rows) rates = {row["supply_key"]: row["surcharge_pct"] for row in table["rows"]} assert rates == {"각재 50×50": 5, "판재 T12": 10, "못": None} def test_개소가_서면_재료도_선다() -> None: rows = 이름별(frame_material_rows(규준틀)) assert abs(rows[("비탈 규준틀", "각재 50×50")]["amount"] - 28.0 * 0.0044) < 1e-9 assert abs(rows[("수평 규준틀", "못")]["amount"] - 33.0 * 0.03) < 1e-9 def test_개소가_안_서면_재료도_안_선다() -> None: """⚠ 밑수가 개소 줄이다 — 개소가 없으면 재료를 지어내지 않는다.""" assert frame_material_rows([{"item": "비탈 규준틀", "amount": None}]) == [] def test_산출_조건이_이긴다() -> None: rows = 이름별(frame_material_rows(규준틀, {"못": 0.05})) assert abs(rows[("비탈 규준틀", "못")]["amount"] - 28.0 * 0.05) < 1e-9 assert "고른 값" in rows[("비탈 규준틀", "못")]["basis"] def test_관측값이지_법정_기준이_아님이_적힌다() -> None: """⚠ 값만 박고 근거를 안 보이면 「대신 페이지에 남길 것」 지시를 어기는 것이다.""" basis = 이름별(frame_material_rows(규준틀))[("비탈 규준틀", "각재 50×50")]["basis"] assert "실무 관측값" in basis and "법정 기준 아님" in basis assert "설계수량에 따른다" in basis def test_손율이_원문값으로_적힌다() -> None: rows = 이름별(frame_material_rows(규준틀)) assert "손율 50%" in rows[("비탈 규준틀", "판재 T12")]["basis"] assert "손율 80%" in rows[("수평 규준틀", "판재 T12")]["basis"] def test_자재_축으로_간다() -> None: assert all(row["destination"] == "material" for row in frame_material_rows(규준틀)) def test_화면과_인계가_같은_한_벌을_부른다() -> None: """자재총괄 화면(6줄)·인계(9줄)가 갈리던 자리 — 규준틀 재료를 한 곳에서 얹음(2026-09-14). ⚠ 두 라우터가 이 함수를 부르는지까지 본다 — 한쪽이 옛 길로 돌아가면 다시 갈림. """ import inspect from B08_Quantity import B08_Quantity_Router_Material as router table = router.material_table_for( {"structures": []}, {"material_supply": {"각재 50×50": "owner_supplied"}}, {"rows": 규준틀}, ) keys = {row["supply_key"]: row for row in table["rows"]} assert set(keys) == {"각재 50×50", "판재 T12", "못"} assert keys["각재 50×50"]["supply"] == "owner_supplied" # 화면에서 고른 값이 먹음 for route in (router.get_material_summary, router.get_handoff): source = inspect.getsource(route) assert "material_table_for(" in source and "build_material_table(" not in source def test_제안값이_실무_관측값과_같다() -> None: """울진 소광 §8 — 각재 50×50 0.0044㎥ · 판재 T12 0.0029㎥ · 못 0.03㎏(개소당).""" assert FRAME_MATERIAL_SUGGESTED["각재 50×50"][0] == 0.0044 assert FRAME_MATERIAL_SUGGESTED["판재 T12"][0] == 0.0029 assert FRAME_MATERIAL_SUGGESTED["못"][0] == 0.03