"""막자갈(뒤채움 조약돌) — **품셈이 정한 두께 범위 안에 드는가** (2026-09-09). ⚠⚠ **이 시험은 「같은지」가 아니라 「범위 안인지」를 잰다.** 우리 막자갈은 품셈과 **다른 식**이다. 품셈 13-4-4 [주]⑨ 는 뒤채움 조약돌 두께를 **직고별 범위**로만 준다(상부 20~40㎝ · 하부 직고별 30~140㎝). 두 길이 **같은 자리에 도착하는지**를 본다. ⚠⚠ **2026-09-09 오후 식이 갈아탔다** — 랩탑 보조가 확정 5차 작은 것 3 으로 막자갈을 「입적 − 몸통 − 고임돌」에서 **뒷채움 사다리꼴**로 바꿨다(정본 여섯 탭 상 0.30 · 하 0.45). 그래서 아래 기록을 전부 다시 쟀다 — **옛 값(0.278~0.943)은 지금 값이 아니다.** ⚠ **등호로 바꾸지 말 것** — 등호로 두면 우리 식이 품셈 식에 끌려간다. 우리 식은 우리 식이다. ⚠ **범위 밖으로 나가면 「틀림」이 아니라 「봐야 함」이다** — 우리 식이 맞을 수도 있다. 다만 **두께식이 바뀌면 조용히 새 나갈 자리**라(값은 계속 나오므로 아무도 못 본다) 그 자리에 이 그물을 둔다. ⚠ **이 시험이 잰 범위는 직고 1.0~7.5 m · 뒷길이 35~75㎝ 다.** **검산은 「어디까지 쟀는지」를 함께 적지 않으면 다음 사람이 착시를 본다** — 앞서 4.0 까지만 재고 「다 범위 안」이라 한 적이 있다(그때 식으로는 4.5 부터 밖이었다). ⚠ **큰돌쌓기(13-6)는 이 시험의 대상이 아니다** — 두께를 **직경 위 끝**으로 보고(`BOULDER_MASONRY`) 막자갈도 안 낸다. 그래서 높이 한계가 없어도 이 자리에 안 걸린다(2026-09-09 실측 확인). 품셈 13-4-4 [주]⑨ <성토의 경우 뒤채움 조약돌의 두께> 직고 ∼1.5m 상부 20~40 · 하부 30~60 ⇒ 평균 0.25~0.50 m 직고 ∼3.0m 상부 20~40 · 하부 45~75 ⇒ 평균 0.33~0.58 m 직고 ∼5.0m 상부 20~40 · 하부 60~100 ⇒ 평균 0.40~0.70 m 직고 ∼7.0m 상부 20~40 · 하부 80~140 ⇒ 평균 0.50~0.90 m """ 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_UnitQuantity import stone_masonry # noqa: E402 #: (직고 상한, 평균두께 범위) — 원문 상·하부 범위의 평균이다. PUMSEM_RANGE_M: tuple[tuple[float, float, float], ...] = ( (1.5, 0.25, 0.50), (3.0, 0.33, 0.58), (5.0, 0.40, 0.70), (7.0, 0.50, 0.90), ) def 범위(height_m: float) -> tuple[float, float]: for limit, low, high in PUMSEM_RANGE_M: if height_m <= limit: return low, high return PUMSEM_RANGE_M[-1][1], PUMSEM_RANGE_M[-1][2] def 두께(height_m: float, back_cm: int) -> float: """우리 막자갈을 **면적으로 나눈 값** — 품셈이 말하는 「뒤채움 두께」와 같은 축.""" components, _ = stone_masonry( height_m, 10.0, {"height_m": height_m, "length_m": 10.0, "back_len_cm": back_cm}, wet=True, face="성토", ) got = {c.name: c.amount for c in components} return got["막자갈"] / got["돌쌓기"] def test_3m_까지는_품셈_범위_안에_든다() -> None: """⚠ 실패하면 「틀렸다」가 아니라 **「봐야 한다」**는 뜻이다 — 두께식이 바뀐 자리를 알리는 그물. ⚠ **3.0m 까지인 것이 우연이 아니다** — 정본 여섯 탭을 전수 확인한 구간이 **H=1.0~3.0** 이다(`STONE_BACKFILL_WIDTH_M` 머리 주석). 근거가 있는 구간에서는 품셈 범위와 맞고, 근거가 없는 구간에서 갈린다. """ for height, back in ((1.0, 45), (1.2, 35), (2.5, 45), (3.0, 45), (3.0, 55), (3.0, 75)): low, high = 범위(height) got = 두께(height, back) assert low <= got <= high, ( f"H={height} ℓ3={back}㎝ 뒤채움 두께 {got:.3f}m 가 품셈 13-4-4 [주]⑨ 범위" f" {low}~{high}m 밖 — 우리 식이 맞을 수도 있으니 **틀림이 아니라 봐야 함**" ) def test_3m_를_넘으면_범위_아래로_나간다() -> None: """⚠⚠ **지금 상태를 기록해 두는 시험이다**(2026-09-09 오후 실측). ⚠ **방향이 뒤집혔다** — 옛 식은 높은 벽에서 품셈 범위 **위**로 나갔는데, 새 식은 **아래**로 나간다. 새 식은 두께가 H 에 안 붙기 때문이다. H 1.0 0.364 · 1.5 0.364 (안) · 2.0~3.0 0.359 (안) H 3.5~5.0 0.354 · 6.0~7.0 0.348 · 7.5 0.342 (전부 **밖 — 아래**) 까닭은 새 막자갈이 **(상 0.30 + 하 0.45) ÷ 2 × H × 연장** 이라 두께(= 막자갈 ÷ 비탈면적)가 **0.375 ÷ √(1+경사²)** 로 거의 붙박이인 데 있다. 직고가 커지면 표준경사가 커져 비탈면적만 늘어 **오히려 조금씩 얇아진다.** 품셈 표는 반대로 직고와 함께 두꺼워진다. ⚠ **틀렸다고 단정하지 않는다** — 정본 여섯 탭이 근거이고 품셈 표는 **범위**다. 다만 정본 전수 확인 구간이 **H=1.0~3.0** 이고 갈리는 자리도 **3.0m 부터**라 겹친다 — **높은 벽의 정본 근거가 없다**는 뜻이므로 확정 5차 작은 것 3 을 다시 볼 때 이 자리를 볼 것. ⚠ 이 시험이 깨지면(범위 안으로 들어오면) **두께식이 바뀐 것**이니 위 기록을 고칠 것. """ 안쪽 = 두께(3.0, 45) 바깥 = 두께(3.5, 45) assert 범위(3.0)[0] <= 안쪽 <= 범위(3.0)[1] assert 바깥 < 범위(3.5)[0] assert 0.35 < 바깥 < 0.36 def test_직고가_커져도_두께가_거의_안_움직인다() -> None: """⚠ **뒤집힌 시험이다** — 종전에는 「직고가 커지면 두꺼워진다」였다(품셈 표와 같은 방향). 새 식에서는 막자갈 **물량**이 H 에 비례해 늘지만 **두께**(물량 ÷ 비탈면적)는 거의 붙박이다. 이 사실 자체를 못 박아, 나중에 누가 「두께가 직고를 따라간다」고 잘못 읽는 것을 막는다. """ thicknesses = [두께(h, 45) for h in (1.2, 2.5, 4.0, 6.0)] assert max(thicknesses) - min(thicknesses) < 0.02 # ⚠ 물량 자체는 직고를 따라 는다 — 두께가 안 움직인다고 물량까지 굳은 것이 아니다. 부피 = [두께(h, 45) * h for h in (1.2, 2.5, 4.0, 6.0)] assert 부피 == sorted(부피) def test_뒷길이는_두께를_안_움직인다() -> None: """⚠ 옛 식에서는 뒷길이가 밑수였다 — 지금은 정본 폭 붙박이라 아예 안 본다.""" assert len({round(두께(2.5, b), 6) for b in (35, 45, 55, 60, 75)}) == 1 def test_등호가_아니라_범위다() -> None: """⚠ 이 시험의 뜻을 못 박는다 — 품셈 값과 **같지 않아도 된다**. 같아지길 요구하면 우리 식(정본 뒷채움 사다리꼴)이 품셈 식에 끌려간다. """ low, high = 범위(2.5) got = 두께(2.5, 45) assert got != low and got != high # 경계값과 같을 이유가 없다 assert low < got < high def test_근거_구간_밖이면_사유가_뜬다() -> None: """⚠ **값은 계속 나온다** — 사유가 없으면 아무도 못 본다. 교본 7-3 이 「찰 3.0m 이하 · 메 2.0m 이하」로 두지만 **코드가 높이를 막지 않고**, 표준경사표(13-4-4 [주]⑪)는 직고 7m 까지 칸을 준다 — 높은 벽이 실제로 설 수 있다. 막지도 눅이지도 않고 **정본 확인 구간 밖이라는 사실**만 사유로 낸다(임의 확정 금지). """ from B08_Quantity.B08_Quantity_Engine_UnitQuantity import stone_masonry def 사유(height_m: float) -> list[str]: _, notes = stone_masonry( height_m, 10.0, {"height_m": height_m, "length_m": 10.0, "back_len_cm": 45}, wet=True, face="성토", ) return [n for n in notes if "정본 확인 구간" in n] assert 사유(3.0) == [] # 근거가 있는 구간에서는 잔소리하지 않는다 밖 = 사유(3.5) assert len(밖) == 1 assert "13-4-4 [주]⑨" in 밖[0] and "실무자 확인" in 밖[0] def test_큰돌쌓기는_이_식을_안_쓴다() -> None: """⚠ 높이 한계가 없는 것은 큰돌쌓기뿐이라 **거기까지 번지면 살아 있는 자리**가 된다. 큰돌쌓기(13-6)는 막자갈 줄 자체를 안 낸다 — 이 갈림이 거기로 안 번진다. """ from B08_Quantity.B08_Quantity_Engine_UnitQuantity import boulder_masonry components, _ = boulder_masonry( 5.0, 10.0, {"height_m": 5.0, "length_m": 10.0, "stone_cm": "60~80"} ) assert not any(c.name == "막자갈" for c in components)