tmp/ 가 창끼리 안 건너가는 것이 확정돼(랩탑이 시간 두고 두 번 확인) 시험·예외가 저절로 건너가도록 git 안으로 옮김. 사용자 확정. - 내용은 하나도 안 고침 — 자리만 옮김. tmp/tests 는 남겨 둠. - 같은 이름이 이미 있던 64 개는 랩탑 것을 그대로 두고 건너뜀. - helper_b05_*.js 둘은 랩탑이 .cjs 로 이미 올린 것과 **줄바꿈만 다른 같은 내용**이라 복사본을 도로 뺌(시험이 .cjs 를 부름). - resources/tester/ 에서 전체 1176 통과 · 29 건너뜀 · 실패 0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
411 lines
20 KiB
Python
411 lines
20 KiB
Python
"""구조물 원단위 전개식 검사 — PLAN 8-6·8-8·8-15.
|
||
|
||
이 일감의 진짜 위험은 계산이 아니라 **이중계상**이다. 그래서 시험도 거기에 무게를 둔다.
|
||
㉢ 배합(시멘트·모래)을 여기서 쪼개면 B09 일위대가와 겹쳐 두 배가 된다.
|
||
㉠ 할증을 여기서 붙이면 자재총괄과 겹친다.
|
||
· 터파기·되메우기는 토공으로 합산되는 값이라 내역 줄과 구분돼야 한다.
|
||
|
||
실무 관측값(울진 13종)은 **검산 정답지**이지 맞춰야 할 목표가 아니다 — 크게 벌어지면
|
||
전개식을 의심하되, 맞추려고 식을 비틀지는 않는다.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
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_UnitQuantity import ( # noqa: E402
|
||
MIX_COMPONENTS,
|
||
STONE_BACK_LENGTH_TABLE,
|
||
Component,
|
||
StructureQuantity,
|
||
build_table,
|
||
expand,
|
||
stone_masonry,
|
||
verify_no_mix_components,
|
||
)
|
||
|
||
|
||
def 돌쌓기찰(height: float = 1.5, length: float = 1.0, **options) -> dict:
|
||
"""저장된 제원 모양 그대로 — `structures.json` 의 한 항목."""
|
||
return {
|
||
"structure_id": "s1",
|
||
"type_id": "masonry_wet",
|
||
"start_m": 35.0,
|
||
"end_m": 35.0 + length,
|
||
"options": {"height_m": height, "length_m": length, **options},
|
||
}
|
||
|
||
|
||
def 성분(result: StructureQuantity, name: str) -> Component:
|
||
return next(c for c in result.components if c.name == name)
|
||
|
||
|
||
# ── ㉢ 배합 분해 금지 — 이 일감 최대 위험 ────────────────────────────
|
||
|
||
|
||
def test_배합_성분이_산출물에_없을것() -> None:
|
||
"""모르터·콘크리트까지만 낸다 — 시멘트·모래는 B09 일위대가 몫이다.
|
||
|
||
⚠ **정확히 같은 이름**으로 본다. 부분문자열로 재면 `막자갈`(뒤채움 재료)이 배합
|
||
`자갈` 로 오탐된다 — 개발 중 실제로 걸렸던 자리라 시험도 같은 규칙으로 둔다.
|
||
"""
|
||
result = expand(돌쌓기찰())
|
||
names = {c.name for c in result.components}
|
||
assert not (names & MIX_COMPONENTS), f"배합 성분이 섞였다: {names & MIX_COMPONENTS}"
|
||
assert "막자갈" in names # 뒤채움 재료는 배합이 아니라 남아 있어야 한다
|
||
|
||
|
||
def test_모르터까지만_내고_멈춤() -> None:
|
||
result = expand(돌쌓기찰())
|
||
assert 성분(result, "모르터").unit == "㎥"
|
||
assert 성분(result, "채움콘크리트").unit == "㎥"
|
||
|
||
|
||
def test_배합이_섞이면_검사가_잡을것() -> None:
|
||
"""실무 라이브러리를 베끼다 딸려 들어오기 쉬운 자리라 코드로 막는다."""
|
||
tainted = StructureQuantity(
|
||
structure_id="x",
|
||
type_id="masonry_wet",
|
||
name="돌쌓기(찰)",
|
||
components=[Component("시멘트", "대", 2.6, "material")],
|
||
)
|
||
assert verify_no_mix_components([tainted])
|
||
assert not verify_no_mix_components([expand(돌쌓기찰())])
|
||
|
||
|
||
def test_표에도_위반이_드러남() -> None:
|
||
table = build_table([돌쌓기찰()])
|
||
assert table["mix_components_found"] == []
|
||
|
||
|
||
# ── ㉠ 할증 금지 ────────────────────────────────────────────────────
|
||
|
||
|
||
def test_할증_전_값임을_못박음() -> None:
|
||
"""할증은 자재총괄 한 곳뿐이다(PLAN 8-7 ㉠)."""
|
||
assert build_table([돌쌓기찰()])["surcharge_applied"] is False
|
||
|
||
|
||
# ── 터파기·되메우기는 토공으로 합산 ─────────────────────────────────
|
||
|
||
|
||
def test_성분마다_갈_곳이_표시됨() -> None:
|
||
"""내역 줄의 실체는 작업 공종이고, 터파기는 토공으로 합쳐진다(울진 D12~D14 실증)."""
|
||
result = expand(돌쌓기찰())
|
||
assert 성분(result, "터파기").destination == "earthwork"
|
||
assert 성분(result, "되메우기").destination == "earthwork"
|
||
assert 성분(result, "잔토처리").destination == "earthwork"
|
||
assert 성분(result, "돌쌓기").destination == "unit_price"
|
||
# ⚠ 이름이 「야면석」 → 「돌」로 바뀜(2026-09-09 확정 5차 큰 것 7) — 종류를 안 고르면
|
||
# 관측표가 아니라 **계산식**으로 서고 줄 이름도 정본 계산표대로 「돌」이다.
|
||
assert 성분(result, "돌").destination == "material"
|
||
|
||
|
||
def test_잔토는_터파기_빼기_되메우기() -> None:
|
||
result = expand(돌쌓기찰())
|
||
assert 성분(result, "잔토처리").amount == pytest.approx(
|
||
성분(result, "터파기").amount - 성분(result, "되메우기").amount
|
||
)
|
||
|
||
|
||
# ── 전개식 — 실무 시트 값과 대조 ────────────────────────────────────
|
||
|
||
|
||
def test_실무_시트_m당_값_재현() -> None:
|
||
"""`기슭막이(찰쌓기, H=1.5, 기초무)` 시트 — 터파기 1.55 · 되메우기 0.30 · 잔토 1.25.
|
||
|
||
⚠ 우리 값은 1.5375 로 실무 1.55 와 0.012 차이가 난다. **우리 쪽이 맞다** —
|
||
평균두께가 0.825 인데 실무 시트는 **표기값 0.83 으로 다시 계산**해서 1.545 가 됐다.
|
||
품셈 1-2-2 의 소수 자리는 표기 규칙이고 계산은 전정밀로 둔다(PLAN 8-16).
|
||
맞추려고 식을 비틀지 않는다 — 허용오차를 그 차이만큼 둔다.
|
||
"""
|
||
result = expand(돌쌓기찰(height=1.5, length=1.0))
|
||
assert result.height_m == 1.5
|
||
assert result.length_m == 1.0
|
||
assert 성분(result, "터파기").amount == pytest.approx(1.55, abs=0.02)
|
||
assert 성분(result, "되메우기").amount == pytest.approx(0.30, abs=0.01)
|
||
assert 성분(result, "잔토처리").amount == pytest.approx(1.25, abs=0.02)
|
||
|
||
|
||
def test_평균두께는_전정밀로() -> None:
|
||
"""실무가 0.83 으로 반올림해 재계산한 자리 — 우리는 0.825 를 그대로 쓴다."""
|
||
result = expand(돌쌓기찰(height=1.5, length=1.0))
|
||
# 터파기 = 높이 × (평균두께 + 0.2) × 연장 = 1.5 × 1.025 = 1.5375
|
||
assert 성분(result, "터파기").amount == pytest.approx(1.5375)
|
||
|
||
|
||
def test_비탈면적은_기울기만큼_길어짐() -> None:
|
||
"""1:0.3 이면 정면적 1.5 → 돌쌓기 1.566 (실무 표기 1.57).
|
||
|
||
⚠ 2026-09-08 ㉘ 정정 — 이 시험이 **기울기 몫을 두 번 곱하는 것**(× 1.566 × 1.04)을
|
||
계약으로 못 박고 있었다. 실무 시트의 「돌쌓기 = 정면적 × 1.04」에서 그 1.04 가
|
||
**곧 기울기 몫**이다(1:0.3 → 1.0440). 값이 나오고 자원도 맞아 아무도 안 봤다.
|
||
"""
|
||
import math
|
||
|
||
# ⚠ 2026-09-09 — 기울기가 **품셈 표준경사표로 자동 판정**되면서 찰 H=1.5 는 1:0.25 가
|
||
# 되었다(확정 ⑨). 이 시험이 보는 것은 「기울기 몫을 한 번만 먹는가」이므로
|
||
# **기울기를 못 박고** 본다.
|
||
components, _ = stone_masonry(1.5, 1.0, {"face_slope_ratio": 0.3}, wet=True)
|
||
masonry = next(c for c in components if c.name == "돌쌓기")
|
||
assert masonry.amount == pytest.approx(1.5 * math.hypot(1.0, 0.3))
|
||
|
||
|
||
def test_돌쌓기_면적은_기울기_몫을_한_번만_먹는다_거울시험() -> None:
|
||
"""⚠ 거울 시험 — `정면적 × hypot` 과 어긋나면 깨진다. 기울기를 바꿔도 따라와야 한다."""
|
||
import math
|
||
|
||
from B08_Quantity.B08_Quantity_Engine_UnitQuantity import boulder_masonry
|
||
|
||
for ratio in (0.3, 0.5, 1.0):
|
||
options = {"face_slope_ratio": ratio}
|
||
components, _ = stone_masonry(2.5, 10.0, options, wet=True)
|
||
돌쌓기 = next(c for c in components if c.name == "돌쌓기")
|
||
assert 돌쌓기.amount == pytest.approx(25.0 * math.hypot(1.0, ratio))
|
||
|
||
options["stone_cm"] = "60~80"
|
||
components, _ = boulder_masonry(2.5, 10.0, options)
|
||
큰돌 = next(c for c in components if c.name == "큰돌쌓기")
|
||
assert 큰돌.amount == pytest.approx(25.0 * math.hypot(1.0, ratio))
|
||
|
||
|
||
def test_1대0점3_에서_실무_시트값과_맞는다() -> None:
|
||
"""실무 돌골막이 시트 — 정면적 18.87 ㎡ → 돌쌓기 19.62 ㎡. 오차 0.5 % 안."""
|
||
from B08_Quantity.B08_Quantity_Engine_UnitQuantity import STONE_MASONRY
|
||
|
||
# ⚠ 실무 시트는 1:0.3 으로 그린 것이라 **그 기울기를 못 박고** 대조한다(2026-09-09).
|
||
# 자동 판정에 맡기면 직고 18.87m 로 읽혀 7m 초과 칸(1:0.45)이 되어 대조가 깨진다.
|
||
components, _ = stone_masonry(18.87, 1.0, {"face_slope_ratio": 0.3}, wet=True)
|
||
masonry = next(c for c in components if c.name == "돌쌓기")
|
||
assert masonry.amount == pytest.approx(19.62, rel=0.005)
|
||
# 시트가 반올림해 적은 1.04 가 곧 기울기 몫임을 함께 박는다.
|
||
assert STONE_MASONRY["sheet_check_factor_at_0_3"] == pytest.approx(
|
||
masonry.amount / 18.87, rel=0.005
|
||
)
|
||
|
||
|
||
def test_길이에_비례() -> None:
|
||
one = expand(돌쌓기찰(length=1.0))
|
||
ten = expand(돌쌓기찰(length=10.0))
|
||
assert 성분(ten, "터파기").amount == pytest.approx(성분(one, "터파기").amount * 10)
|
||
|
||
|
||
def test_메쌓기는_콘크리트_모르터가_없음() -> None:
|
||
result = expand({**돌쌓기찰(), "type_id": "masonry_dry"})
|
||
names = [c.name for c in result.components]
|
||
assert "채움콘크리트" not in names
|
||
assert "모르터" not in names
|
||
assert "돌쌓기" in names
|
||
|
||
|
||
# ── 계수표 — 식에 박지 않는다 ───────────────────────────────────────
|
||
|
||
|
||
def test_뒷길이를_바꾸면_계수가_따라감() -> None:
|
||
"""실무 방식의 약점을 고친 자리 — 뒷길이가 바뀌어도 식을 안 고친다(PLAN 8-8 ㉮)."""
|
||
a = expand(돌쌓기찰(stone_back_length_cm=45))
|
||
b = expand(돌쌓기찰(stone_back_length_cm=55))
|
||
ratio = STONE_BACK_LENGTH_TABLE[55]["fill_concrete_m3_per_m2"] / (
|
||
STONE_BACK_LENGTH_TABLE[45]["fill_concrete_m3_per_m2"] or 1
|
||
)
|
||
assert 성분(b, "채움콘크리트").amount == pytest.approx(성분(a, "채움콘크리트").amount * ratio)
|
||
|
||
|
||
def test_원본에_없는_칸은_지어내지_않음() -> None:
|
||
"""뒷길이 60㎝ **야면석**의 돌중량은 관측표가 비어 있다 — 값을 만들지 않고 알린다.
|
||
|
||
⚠ 2026-09-09 확정 5차 큰 것 7 로 **계산식이 기본**이 되면서, 종류를 안 고른 자리는
|
||
이제 표가 비어도 계산식으로 선다. 관측표를 보는 것은 **야면석 계열뿐**이라 그 조건으로
|
||
옮긴다 — 「빈 칸을 안 지어낸다」는 못이 사라진 것이 아니라 **자리가 좁아진 것**이다.
|
||
"""
|
||
result = expand(돌쌓기찰(stone_back_length_cm=60, stone_kind="야면석·호박돌"))
|
||
assert not any(c.unit == "ton" for c in result.components)
|
||
assert any("돌중량" in note for note in result.notes)
|
||
|
||
|
||
# ── 모르는 종류·빈 값 ────────────────────────────────────────────────
|
||
|
||
|
||
def test_전개식이_없는_종류는_물량을_내지_않음() -> None:
|
||
"""옹벽은 이제 **관측 원단위표**로 간다(치수가 저장돼 있지 않아 식을 못 세움).
|
||
형식(반중력식…)을 안 고르면 규격이 안 맞아 **미확보**로 드러난다 — 값을 지어내지 않는다."""
|
||
result = expand({"type_id": "retaining_wall", "options": {"height_m": 2.0, "length_m": 5.0}})
|
||
assert result.components == []
|
||
assert any("자료에 없습니다" in note for note in result.notes)
|
||
|
||
|
||
def test_모르는_종류는_전개식도_원단위도_없음() -> None:
|
||
result = expand({"type_id": "듣도보도못한구조물", "options": {"height_m": 2.0}})
|
||
assert result.components == []
|
||
assert any("수량 산출식이 아직 없습니다" in note for note in result.notes)
|
||
|
||
|
||
def test_높이가_없으면_전개하지_않음() -> None:
|
||
result = expand(돌쌓기찰(height=0.0))
|
||
assert result.components == []
|
||
assert result.notes
|
||
|
||
|
||
def test_표_모양() -> None:
|
||
table = build_table([돌쌓기찰(length=10.0), {**돌쌓기찰(), "type_id": "masonry_dry"}])
|
||
assert table["structure_count"] == 2
|
||
assert table["totals"]
|
||
assert all({"name", "unit", "amount", "destination"} <= set(entry) for entry in table["totals"])
|
||
|
||
|
||
def test_물구멍_잠정값이_근거에_적힐것() -> None:
|
||
"""⚠ 「미확정」만 적으면 무엇을 정해야 하는지 모른다 — 지금 값과 법 범위를 함께 적는다."""
|
||
# ⚠ 이름이 「물구멍」 → 「물구멍관」 으로 바뀜(2026-09-08) — 자재 카탈로그가 이름으로
|
||
# 줄을 찾고, 그 규약이 「공백 없는 한 낱말」이다(B09 확인).
|
||
basis = 성분(expand(돌쌓기찰()), "물구멍관").basis
|
||
# ⚠ 문구가 「잠정」 → 「안 정함」으로 바뀜(2026-09-09) — **무엇을 안 정했는지**가
|
||
# 낱말에 붙어 화면에서 바로 읽힌다.
|
||
assert "안 정함" in basis
|
||
assert "Ø50" in basis # 실무 관측값
|
||
assert "2~3㎡" in basis # 법이 정한 범위
|
||
|
||
|
||
def test_큰돌쌓기를_돌쌓기_식으로_돌리지_않을것() -> None:
|
||
"""⚠ 2026-09-07 발견 — 큰돌쌓기는 품셈 **13-6**, 돌쌓기는 **13-4** 로 **규격 축이 다르다**.
|
||
돌쌓기는 뒷길이(35·45·55·60㎝), 큰돌쌓기는 직경(40~60·60~80·80~100㎝).
|
||
앞서 `stone_masonry(dry)` 로 전개해 직경 60~80㎝ 짜리가 「뒷길이 45㎝」 계수로 돌고 있었다.
|
||
**값이 나오기는 해서 어떤 시험도 안 잡던 자리** — 「값이 있기는 하니 안 보이는」 그것이다.
|
||
|
||
⚠ 지금은 13-6 축 전개식이 섰다. 그래서 **뒷길이 계수에서 나오던 성분이 없는지**로 잰다 —
|
||
「전개가 안 된다」가 아니라 「**틀린 축으로 안 돈다**」가 지켜야 할 것이다.
|
||
"""
|
||
result = expand(
|
||
{
|
||
"type_id": "boulder_masonry",
|
||
"start_m": 10.0,
|
||
"end_m": 20.0,
|
||
"options": {"height_m": 2.0, "length_m": 10.0, "stone_cm": "60~80"},
|
||
}
|
||
)
|
||
bases = " ".join(component.basis for component in result.components)
|
||
assert "뒷길이" not in bases # 13-4 계수표가 안 걸렸다
|
||
assert "13-6" in bases
|
||
names = {component.name for component in result.components}
|
||
# ⚠ 버림 콘크리트가 2026-09-09 확정 ⑭ 로 들어왔다 — 기초 바닥에 따로 치는 줄이다.
|
||
assert names == {"큰돌쌓기", "버림콘크리트", "터파기", "되메우기", "잔토처리"}
|
||
|
||
|
||
# ── 큰돌쌓기(품셈 13-6) — 규격 축이 직경이다 (2026-09-07 ⑲) ────────
|
||
|
||
|
||
def 큰돌(diameter: str = "60~80", height: float = 2.0, length: float = 10.0) -> dict:
|
||
return {
|
||
"structure_id": "b1",
|
||
"type_id": "boulder_masonry",
|
||
"start_m": 10.0,
|
||
"end_m": 10.0 + length,
|
||
"options": {"height_m": height, "length_m": length, "stone_cm": diameter},
|
||
}
|
||
|
||
|
||
def test_큰돌쌓기가_직경_축으로_설것() -> None:
|
||
"""⚠ 앞서 돌쌓기(13-4) 뒷길이 계수로 돌던 자리 — 이제 13-6 축으로 선다."""
|
||
result = expand(큰돌())
|
||
area = 성분(result, "큰돌쌓기")
|
||
assert area.unit == "㎡"
|
||
assert "직경 60~80㎝" in area.basis and "13-6" in area.basis
|
||
# 돌쌓기 계열 성분(뒷길이 계수로 나오던 것)은 안 나온다.
|
||
names = {c.name for c in result.components}
|
||
assert "고임돌" not in names and "야면석" not in names and "막자갈" not in names
|
||
|
||
|
||
def test_표에_없는_직경은_지어내지_않을것() -> None:
|
||
result = expand(큰돌(diameter="120~150"))
|
||
assert result.components == []
|
||
assert any("돌 직경이 아직 입력되지 않았습니다" in note for note in result.notes)
|
||
|
||
|
||
def test_직경이_비어_있으면_전개하지_않음() -> None:
|
||
result = expand({"type_id": "boulder_masonry", "options": {"height_m": 2.0, "length_m": 5.0}})
|
||
assert result.components == []
|
||
|
||
|
||
def test_품에_포함된_것을_따로_세우지_않을것() -> None:
|
||
"""13-6 [주]① 「고임돌 및 채움콘크리트 품은 포함되어 있다」 — 세우면 이중계상."""
|
||
result = expand(큰돌())
|
||
assert any("품에 포함" in note for note in result.notes)
|
||
|
||
|
||
def test_재료_원단위가_없음을_알릴것() -> None:
|
||
"""13-6 [주]⑦ 「재료량은 설계수량을 적용한다」 — 뒷길이별 돌중량 표에 해당하는 것이 없다."""
|
||
result = expand(큰돌())
|
||
assert any("재료(큰돌) 원단위 미확보" in note for note in result.notes)
|
||
|
||
|
||
def test_메찰_구분이_없음을_알릴것() -> None:
|
||
result = expand(큰돌())
|
||
assert any("메/찰" in note for note in result.notes)
|
||
|
||
|
||
def test_터파기는_직경_위끝을_벽두께로_볼것() -> None:
|
||
"""품셈에 큰돌쌓기 터파기 폭 규정이 없어 돌쌓기 방식을 준용한다 — 근거에 그 사실을 적는다."""
|
||
result = expand(큰돌(diameter="60~80", height=2.0, length=10.0))
|
||
dig = 성분(result, "터파기")
|
||
assert dig.amount == pytest.approx(2.0 * (0.8 + 0.2) * 10.0)
|
||
assert "준용" in dig.basis
|
||
|
||
|
||
# ── 돌쌓기 뒷길이 — 저장 칸 이름이 달랐다 (2026-09-07 발견) ─────────
|
||
|
||
|
||
def test_저장_칸_이름으로_뒷길이를_읽을것() -> None:
|
||
"""⚠ 레지스트리는 `back_len_cm` 인데 엔진이 `stone_back_length_cm` 을 읽고 있었다.
|
||
저장값이 영영 안 닿아 **뒷길이를 75 로 골라도 45 계수**가 붙던 자리다."""
|
||
from B08_Quantity.B08_Quantity_Engine_UnitQuantity import _back_length
|
||
|
||
assert _back_length({"back_len_cm": "55"}) == 55
|
||
assert _back_length({"stone_back_length_cm": 35}) == 35 # 옛 이름도 본다
|
||
assert _back_length({}) == 45 # 없으면 기본
|
||
|
||
|
||
def test_뒷길이를_접지_않는다() -> None:
|
||
"""⚠ 2026-09-08 정정 — 앞서 「덮는 위 칸으로 접는다」를 계약으로 못 박고 있었다.
|
||
|
||
품셈 13-4-3·13-4-4 [주]① 이 25·30·35·45·55·60·75 **일곱 규격**을 다 주므로 접을
|
||
까닭이 없었다. 접으면 **40㎝ 가 45㎝ 계수로 조용히** 돌고 999㎝ 도 60 으로 접혔다.
|
||
"""
|
||
from B08_Quantity.B08_Quantity_Engine_UnitQuantity import _back_length
|
||
|
||
assert _back_length({"back_len_cm": "25"}) == 25
|
||
assert _back_length({"back_len_cm": "75"}) == 75
|
||
assert _back_length({"back_len_cm": "40"}) == 40, "표에 없는 값도 그대로 — 접지 않는다"
|
||
|
||
|
||
def test_표에_없는_뒷길이는_물량을_안_낸다() -> None:
|
||
"""⚠ 다른 규격 계수가 조용히 도는 것보다 「없다」가 낫다."""
|
||
from B08_Quantity.B08_Quantity_Engine_UnitQuantity import stone_masonry
|
||
|
||
components, notes = stone_masonry(
|
||
2.5, 10.0, {"height_m": 2.5, "length_m": 10.0, "back_len_cm": 40}, wet=True
|
||
)
|
||
assert components == []
|
||
assert "품셈 표(25·30·35·45·55·60·75㎝)에 없어" in notes[0]
|
||
|
||
|
||
def test_큰돌쌓기_메찰을_고르면_그_사유가_사라진다() -> None:
|
||
"""⚠ 사유가 거짓이면 **사유 칸 전체를 못 믿게 된다**(2026-09-09).
|
||
|
||
`bond` 는 레지스트리에 있는 칸인데 조건 없이 「못 고름」을 붙이고 있었다.
|
||
⚠ 얻는 것은 노무 품 갈래뿐 — 재료 원단위는 13-6 [주]⑦ 때문에 여전히 안 선다.
|
||
"""
|
||
from B08_Quantity.B08_Quantity_Engine_UnitQuantity import boulder_masonry
|
||
|
||
base = {"height_m": 2.5, "length_m": 10.0, "stone_cm": "60~80"}
|
||
_, without = boulder_masonry(2.5, 10.0, dict(base))
|
||
_, chosen = boulder_masonry(2.5, 10.0, {**base, "bond": "메쌓기"})
|
||
assert any("메/찰" in note for note in without)
|
||
assert not any("메/찰" in note for note in chosen)
|
||
# 재료 미확보 사유는 **그대로 남아야 한다** — 그건 아직 참이다.
|
||
assert any("재료(큰돌) 원단위 미확보" in note for note in chosen)
|