- 양식 갈음의 종류 이름 치환(_downstream_name) 삭제 · 돌쌓기·골막이·바닥막이(메) 전개도 「돌」+규격 - 자재총괄 줄에 spec·supply_key — 관급구분 열쇠는 이름+규격, 옛 열쇠(이름만·종류 이름)도 받음 - 화면 자재총괄 이름 칸·원단위 성분 칸에 규격 표시, 관급 선택은 supply_key 로 저장 - 원단위 합계 열쇠에 규격 포함 · 종류 이름 DESTINATION 항목 정리 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BW6Jdsh18WPtYUR6THZJqn
131 lines
5.9 KiB
Python
131 lines
5.9 KiB
Python
"""찰쌓기 양식 = 지금 전개 — 값이 한 줄도 안 갈리는지 (2026-09-13, PLAN 3장 ② ③).
|
|
|
|
판정 Ⓑ 「첫 양식 값은 지금 전개와 같게」. 양식(`resources/library_structure/masonry_wet.json`)을
|
|
식 풀이기(TS → Node)로 풀어 낸 값이 `build_table`(돌쌓기 전개 + 기초잡석)과 같아야 함.
|
|
어긋나면 **양식이 틀린 것**.
|
|
|
|
⚠ 전개는 부동소수, 양식은 분수 — 상대 1e-9 안이면 같은 값으로 봄.
|
|
⚠ 줄이 「안 서는」 경우(버림 안 넣음 · 채집석 구입)는 `when` 으로 — 안 선 줄은 전개에도 없어야 함.
|
|
⚠ 원문 「-」 칸은 일부러 다름(양식 `differs_from_engine`) — 여기서는 그 규격을 안 넣음.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import itertools
|
|
import shutil
|
|
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_Formula import evaluate_sheets # noqa: E402
|
|
from B08_Quantity.B08_Quantity_Engine_StructureTemplate import ( # noqa: E402
|
|
load_template,
|
|
template_sheet,
|
|
template_vars,
|
|
)
|
|
from B08_Quantity.B08_Quantity_Engine_UnitQuantity import ( # noqa: E402
|
|
build_table,
|
|
face_slope_ratio,
|
|
)
|
|
|
|
pytestmark = pytest.mark.skipif(shutil.which("node") is None, reason="node 가 없음")
|
|
|
|
|
|
def _structure(height: float, length: float, **options) -> dict:
|
|
return {
|
|
"structure_id": "s1",
|
|
"type_id": "masonry_wet",
|
|
"name": "돌쌓기(찰)",
|
|
"start_m": 100.0,
|
|
"end_m": 100.0 + length,
|
|
"length_m": length,
|
|
"height_m": height,
|
|
"options": {"height_m": height, "length_m": length, **options},
|
|
}
|
|
|
|
|
|
CASES = [
|
|
dict(height=h, length=10.0, back_len_cm=l3, stone_kind=kind, foundation=found)
|
|
for h, l3, kind, found in itertools.product(
|
|
(1.0, 1.5, 2.5, 3.5),
|
|
(35, 45, 55),
|
|
("", "야면석·호박돌", "깬돌"),
|
|
("기초유", "기초버림", ""),
|
|
)
|
|
] + [
|
|
# 사용자가 정한 칸 — 기울기 · 두께 · 실무 관행 계수 · 물구멍 면적 · 잡석 두께 · 짧은 연장
|
|
dict(height=2.0, length=7.5, back_len_cm=45, face_slope_ratio=0.35, foundation="기초유"),
|
|
dict(height=2.0, length=10.0, back_len_cm=55, thickness_top_m=0.9, foundation="기초버림"),
|
|
dict(height=2.0, length=10.0, back_len_cm=45, thickness_bottom_m="1.4", stone_kind="깬잡석"),
|
|
dict(height=2.0, length=10.0, stone_kind="야면석·호박돌", stone_coeff_basis="실무 관행"),
|
|
dict(height=1.5, length=10.0, back_len_cm=35, weep_hole_area_m2=3, foundation="기초유"),
|
|
dict(height=2.5, length=12.0, back_len_cm=60, stone_kind="견치돌", rubble=0.3),
|
|
# 줄이 안 서는 갈래 — `when`
|
|
dict(height=2.0, length=10.0, blinding_concrete="안 넣음", foundation="기초유"),
|
|
dict(height=2.0, length=10.0, stone_supply="구입", stone_kind="깬돌"),
|
|
dict(height=2.0, length=10.0, blinding_concrete="안 넣음", stone_supply="구입"),
|
|
dict(height=2.0, length=10.0, rubble=0.0),
|
|
# 규격 칸 — 강도 · 물구멍 지름
|
|
dict(height=2.0, length=10.0, fill_concrete_mpa="180", weep_hole_diameter_mm=75),
|
|
]
|
|
|
|
|
|
@pytest.mark.parametrize("case", CASES, ids=lambda c: "-".join(f"{v}" for v in c.values()))
|
|
def test_양식_값이_전개와_같다(case: dict) -> None:
|
|
case = dict(case)
|
|
rubble = case.pop("rubble", None)
|
|
height = case.pop("height")
|
|
length = case.pop("length")
|
|
structure = _structure(height, length, **case)
|
|
|
|
# ⚠ 전개만 봄 — `build_table` 은 이제 양식으로 갈음하므로(④-2) 끄지 않으면 자기와 대조가 됨.
|
|
engine = build_table(
|
|
[structure], {"masonry_wet": "돌쌓기(찰)"}, None, None, rubble, use_templates=False
|
|
)
|
|
components = engine["structures"][0]["components"]
|
|
|
|
template = load_template("masonry_wet")
|
|
assert template is not None
|
|
slope, _basis = face_slope_ratio(structure["options"], wet=True, height_m=height)
|
|
settings = {} if rubble is None else {"rubble_base_thickness_m": rubble}
|
|
values = template_vars(template, structure, slope, settings)
|
|
solved = evaluate_sheets([template_sheet(template, values)])
|
|
assert solved is not None, "Node 풀이가 안 돌았다"
|
|
assert [row["error"] for row in solved[0]] == [None] * len(solved[0]), solved[0]
|
|
rows = [row for row in solved[0] if not row["skipped"]]
|
|
|
|
assert len(rows) == len(components), (
|
|
[r["name"] for r in rows],
|
|
[c["name"] for c in components],
|
|
)
|
|
# ⓘ 「실무 관행」 계수가 돌종류를 지우던 전개 결함은 2026-09-13 고침(브레인 판정) — 차이 없음.
|
|
for row, component in zip(rows, components):
|
|
# 명세 13장 Ⓒ — 돌 줄도 양식·전개 모두 이름 고정 「돌」 + 규격에 종류(2026-09-13).
|
|
assert row["name"] == component["name"]
|
|
assert row["spec"] == component["spec"], (row, component)
|
|
assert template["rows"][row["seq"] - 1]["destination"] == component["destination"], row
|
|
assert float(row["amount"]) == pytest.approx(component["amount"], rel=1e-9, abs=1e-12), (
|
|
row["name"],
|
|
row["amount"],
|
|
component["amount"],
|
|
)
|
|
|
|
|
|
def test_실무_관측값은_대안_후보로_보인다() -> None:
|
|
"""판정 Ⓑ — 지금 전개가 기본, 실무 관측(물구멍 2.5㎡)은 칸 옆 대안 후보."""
|
|
hole = load_template("masonry_wet")["vars"]["HOLE_AREA"]
|
|
assert hole["default"] == 2
|
|
assert any(item["value"] == 2.5 for item in hole["candidates"])
|
|
|
|
|
|
def test_표는_키_오름차순() -> None:
|
|
"""근사 LOOKUP 의 전제(명세 13장) — 정확 일치만 쓰더라도 표 모양은 한 규칙으로."""
|
|
for name, table in load_template("masonry_wet")["tables"].items():
|
|
assert table["keys"] == sorted(table["keys"]), name
|
|
for column, cells in table["columns"].items():
|
|
assert len(cells) == len(table["keys"]), (name, column)
|