"""돌쌓기 표준경사 — 직고·메찰·성절토로 갈린다 (2026-09-09 사용자 확정 ⑨). ⚠ 겨누는 것 여섯 ① 표대로 갈린다 — 우리 종전 0.3 은 「메쌓기·성토·1.5m 이하」 한 칸이었다 ② ⚠ 경계값은 **앞 칸** — 원문이 `∼3` 이라 직고 3.0m 은 `~3` 칸이다 ③ 7m 초과라야 마지막 칸이다 ④ 사용자가 정했으면 **그 값이 이긴다** ⑤ 근거 문구에 **어느 칸에서 왔는지**와 **성토 열로 잠정임**이 적힌다 ⑥ ⚠ 큰돌쌓기는 이 표의 대상이 아니다(교본 「1:0.3 이상」) """ from __future__ import annotations import math import sys from pathlib import Path ROOT = Path(__file__).resolve().parents[2] sys.path.insert(0, str(ROOT)) from B08_Quantity.B08_Quantity_Engine_UnitQuantity import ( # noqa: E402 boulder_masonry, face_slope_ratio, stone_masonry, ) def 기울기(wet: bool, height_m: float, face: str | None = "성토", **options: object) -> float: """⚠ 성토/절토는 **판정 한 벌**(`common_util_structure_face_role`)이 준다 — 시험에서는 그 자리를 명시해 표의 어느 열을 보는지 분명히 한다.""" return face_slope_ratio(dict(options), wet=wet, height_m=height_m, face=face)[0] def test_표대로_갈린다() -> None: # 메쌓기 성토 assert 기울기(False, 1.2) == 0.30 assert 기울기(False, 2.0) == 0.35 assert 기울기(False, 4.0) == 0.40 assert 기울기(False, 6.0) == 0.45 # 찰쌓기 성토 assert 기울기(True, 1.2) == 0.25 assert 기울기(True, 2.5) == 0.30 assert 기울기(True, 4.0) == 0.35 def test_경계값은_앞_칸이다() -> None: """⚠ 원문 표기가 `∼1.5 · ∼3` 이라 **이하**로 읽는다 — 한 칸 밀리면 값이 갈린다.""" assert 기울기(True, 1.5) == 0.25 # `~1.5` 칸 assert 기울기(True, 1.51) == 0.30 # 다음 칸 assert 기울기(False, 3.0) == 0.35 # `~3` 칸 assert 기울기(False, 3.01) == 0.40 def test_7m_초과라야_마지막_칸() -> None: assert 기울기(False, 7.0) == 0.45 assert 기울기(False, 7.01) == 0.50 def test_사용자가_정하면_그_값이_이긴다() -> None: ratio, basis = face_slope_ratio({"face_slope_ratio": 0.5}, wet=True, height_m=2.5, face="성토") assert ratio == 0.5 assert "사용자 지정" in basis def test_근거에_칸과_잠정임이_적힌다() -> None: _, basis = face_slope_ratio( {}, wet=False, height_m=2.0, face="성토", face_reason="right_cut · 좌측 → 성토면" ) assert "13-4-4" in basis and "메쌓기" in basis # ⚠ 어느 단면유형에서 왔는지가 근거에 실린다 — 값만 있고 까닭이 없으면 못 되짚는다. assert "right_cut" in basis def test_면적이_그_기울기로_선다() -> None: """메쌓기 H=2.0 은 1:0.35 — 종전 0.3 보다 **면적이 커진다**.""" components, _ = stone_masonry( 2.0, 10.0, {"height_m": 2.0, "length_m": 10.0, "back_len_cm": 35}, False, "성토" ) area = next(c for c in components if c.name == "돌쌓기") assert area.amount == 20.0 * math.hypot(1.0, 0.35) assert "표준경사" in area.basis def test_큰돌쌓기는_이_표의_대상이_아니다() -> None: """교본이 「1:0.3 **이상**」으로만 두었다 — 직고로 갈리지 않는다.""" components, _ = boulder_masonry( 5.0, 10.0, {"height_m": 5.0, "length_m": 10.0, "stone_cm": "60~80"} ) area = next(c for c in components if c.name == "큰돌쌓기") assert area.amount == 50.0 * math.hypot(1.0, 0.3) def test_표_밖_값도_그대로_쓴다() -> None: """⚠ 실무 도면에 **S0.7·0.8** 이 실재한다(품셈 표는 0.20~0.50). 표 밖이라고 막거나 가까운 칸으로 **접지 않는다** — 접으면 사용자가 넣은 값이 조용히 다른 값으로 돌아간다(뒷길이에서 이미 겪은 자리). 「사용자 지정」으로 그대로 쓴다. """ for value in (0.7, 0.8, 1.2): ratio, basis = face_slope_ratio( {"face_slope_ratio": value}, wet=True, height_m=2.5, face="성토" ) assert ratio == value assert "사용자 지정" in basis components, _ = stone_masonry( 2.5, 10.0, {"height_m": 2.5, "length_m": 10.0, "back_len_cm": 45, "face_slope_ratio": 0.7}, wet=True, ) area = next(c for c in components if c.name == "돌쌓기") assert area.amount == 25.0 * math.hypot(1.0, 0.7) def test_성절토를_못_가르면_눅이지_않는다() -> None: """⚠ `both_cut` + 「자동」처럼 **가를 근거가 없는 자리**가 있다(판정 한 벌이 `None` 을 줌). 성토로 눅여 표를 고르면 **임의값이 금액으로 굳는다.** 종전값으로 서되 **왜 못 갈랐는지**를 근거에 적어 사용자가 무엇이 없는지 보게 한다. """ ratio, basis = face_slope_ratio( {}, wet=True, height_m=2.5, face=None, face_reason="both_cut · 자동 — 세울 성토면이 없음" ) assert ratio == 0.3 assert "both_cut" in basis and "종전값" in basis def test_판정_한_벌을_그대로_쓴다() -> None: """⚠ 판정을 두 벌로 짜면 예외에서 갈린다 — 공용 함수 결과가 그대로 흘러야 한다.""" from common_util.common_util_structure_face_role import structure_face_role role, reason = structure_face_role("right_cut", "좌") assert role == "성토" _, basis = face_slope_ratio({}, wet=True, height_m=2.5, face=role, face_reason=reason) assert reason in basis