# -*- coding: utf-8 -*- """메쌓기 높이 사유(B2) · 성토/절토 칸(B3) — 2026-09-14 브레인 판정 ㉰·㉲. ㉰ 두 기준 공존 — 임도기술교본 7-3:27 「메쌓기 2.0m 이하」 ↔ 품셈 13-4-4 [주]⑪ 원문 L7192 「3m 이상이면 전부 또는 하부를 찰쌓기」. 막지 않고 사유(B08 줄) · 칸 경고(B05, `warn_above`). ㉲ `face_role` — 비우면 단면유형 + 설치 측 자동 판정(계산이지 기본값이 아님) · 고르면 그 값이 이김. """ from __future__ import annotations import json import sys from pathlib import Path ROOT = Path(__file__).resolve().parents[2] if str(ROOT) not in sys.path: sys.path.insert(0, str(ROOT)) from B08_Quantity.B08_Quantity_Engine_UnitQuantity import expand, stone_masonry # noqa: E402 from common_util.common_util_structure_face_role import structure_face_role_of # noqa: E402 REGISTRY = json.loads( (ROOT / "B05_Profile" / "B05_Profile_Structure_Types.json").read_text(encoding="utf-8") ) FIELDS = (ROOT / "B05_Profile" / "B05_Profile_UI_Structures_Fields.ts").read_text(encoding="utf-8") def _options(type_id: str) -> dict[str, dict]: item = next(t for t in REGISTRY["types"] if t["type_id"] == type_id) return {o["key"]: o for o in item["options"]} def _height_notes(height: float, wet: bool) -> list[str]: _rows, notes = stone_masonry(height, 10.0, {"back_len_cm": 45}, wet) return [n for n in notes if "메쌓기 높이" in n] def test_메쌓기_높이_두_기준_사유() -> None: assert _height_notes(2.0, False) == [] over_guide = _height_notes(2.5, False) assert len(over_guide) == 1 and "7-3:27" in over_guide[0] and "L7192" in over_guide[0] over_limit = _height_notes(3.0, False) assert len(over_limit) == 1 and "3m 이상이면" in over_limit[0] assert _height_notes(3.5, True) == [] # 찰쌓기는 이 사유 대상 아님 def test_등록부_메쌓기_높이_칸은_경고만() -> None: height = _options("masonry_dry")["height_m"] assert height["warn_above"] == 2.0 and "L7192" in height["warn_message"] assert "is-outside-standard" in FIELDS and "warn_above" in FIELDS def test_성토절토_칸이_이긴다() -> None: assert structure_face_role_of("both_fill", {"face_role": "절토"}) == ( "절토", "사용자 지정 절토면(성토/절토 칸)", ) # 비우면 지금 판정 그대로 assert structure_face_role_of("both_fill", {})[0] == "성토" for type_id in ("masonry_wet", "masonry_dry", "revetment"): role = _options(type_id)["face_role"] assert role["choices"] == ["성토", "절토"] and role["default"] is None, type_id def test_전개가_칸_값으로_기울기를_고른다() -> None: structure = { "structure_id": "w", "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, "face_role": "절토"}, } result = expand(structure, None, None, "both_fill") wall = next(c for c in result.components if c.name == "돌쌓기") # 찰쌓기 · 절토 · 직고 ≤3m → 1:0.25 (성토였으면 1:0.30) assert "절토" in wall.basis and "1:0.25" in wall.basis