Files
Aislo/resources/tester/test_b08_structure_figure.py
T
eomsangdonandClaude Opus 5 5e983018a3 feat(b08): 구조물도 옹벽 단면 그림 — 관측 원단위 줄에 단면 치수 · 표 사유와 같은 말
- 반중력식 H=2.0 관측 줄에 단면(기초 1.6×0.4 · 벽체 1.6 상단 0.3 앞면 0.118 뒷면 0.032 · 전단키 0.35×0.3 · 버림 0.1 · 물빼기 관 0.4)을 실음 — 출처 소광리 07-구조도 「옹벽2.0」 산출식·일반도
- 그 치수로 표 수량이 다시 나오는지 시험(콘크리트 1.345→1.35 · 버림 0.145→0.15 · 유로폼 3.205→3.2 · 관 0.32)
- 그림: 기초·벽체·전단키 + 버림(키 자리 비움) + 기초잡석(산출 조건 두께 · 버림과 같은 폭) + 물구멍
- 표 사유에 새 한 줄 둘(그림도 같은 말): 관측 줄에 터파기·되메우기·잔토가 없음 · 물빼기 칸을 채워도 관측 값이 붙박이라 안 닿음
- 까닭은 표가 못 선 장만(형식 미정 · 자료에 없는 규격 · 단면 치수 없는 규격) — 표 사유와 같은 말

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016VBGFXB9AbJBwXP19z75Qq
2026-09-14 05:27:49 +09:00

172 lines
6.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""구조물도 상단 그림 — 그림 치수가 **수량표와 같은 한 벌**인가 (PLAN 12장).
⚠ 옛 B07 그림은 두께 식을 따로 적어, 상부·하부 두께를 넣어도 **그림만 옛 두께**였음.
"""
from __future__ import annotations
import math
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_ObservedUnit import ( # noqa: E402
expand_observed,
load_observed_table,
)
from B08_Quantity.B08_Quantity_Engine_StructureFigure import ( # noqa: E402
attach_figures,
build_figure,
figure_reason,
)
from B08_Quantity.B08_Quantity_Engine_UnitQuantity import ( # noqa: E402
back_length_default_note,
stone_masonry,
)
def _sheet(**options) -> dict:
return {
"title": "돌쌓기(메)",
"type_id": "masonry_dry",
"height_m": 2.0,
"options": {"height_m": 2.0, **options},
}
def _paths(shapes: list[dict]) -> list[dict]:
return [shape for shape in shapes if shape["kind"] == "path"]
def _area(points: list[list[float]]) -> float:
return abs(sum(x0 * y1 - x1 * y0 for (x0, y0), (x1, y1) in zip(points, points[1:]))) / 2
def test_벽_두께는_사용자가_넣은_값을_따른다() -> None:
wall = _paths(build_figure(_sheet(back_len_cm=35, face_slope_ratio=0.3)))[0]["points"]
# 식: 상부 0.35+0.30=0.65 · 하부 0.65+0.30×(21)=0.95 · 기울기 0.3 → 앞면 위 끝 0.6
assert wall[:4] == [[0.0, 0.0], [0.6, 2.0], [1.25, 2.0], [0.95, 0.0]]
given = _paths(
build_figure(
_sheet(
back_len_cm=35, face_slope_ratio=0.3, thickness_top_m=0.5, thickness_bottom_m=1.2
)
)
)[0]["points"]
assert given[2] == [1.1, 2.0] and given[3] == [1.2, 0.0]
def test_터파기_넓이는_수량식과_같다() -> None:
dig = _paths(build_figure(_sheet(back_len_cm=35, face_slope_ratio=0.3)))[1]
assert dig["dash"]
assert abs(_area(dig["points"]) - 2.0 * ((0.65 + 0.95) / 2 + 0.2)) < 1e-9
def test_못_그리는_장은_그림_대신_까닭이_실린다() -> None:
payload = {
"sheets": [
_sheet(),
{**_sheet(stone_cm="60~80"), "type_id": "boulder_masonry"},
_sheet(back_len_cm=45),
]
}
attach_figures(payload)
no_back, boulder, drawn = payload["sheets"]
# 뒷길이 미정은 기본값이 있어 **그림이 섬**(2026-09-14 브레인 판정) — 까닭은 직경 축만.
assert no_back["figure_reason"] is None and no_back["figure"]
assert boulder["figure"] is None and "직경" in boulder["figure_reason"]
assert drawn["figure_reason"] is None and drawn["figure"]
def _texts(shapes: list[dict]) -> list[str]:
return [shape["text"] for shape in shapes if shape["kind"] == "text"]
def test_기본값으로_그린_장은_표_사유와_같은_말을_적는다() -> None:
options = {"height_m": 2.0}
_rows, table_notes = stone_masonry(2.0, 1.0, options, wet=False)
table_default = [note for note in table_notes if "뒷길이를 안 골라" in note]
assert table_default == [back_length_default_note(options, 45)]
texts = _texts(build_figure(_sheet()))
assert table_default[0] in texts
assert "뒷길이 ℓ₃ = 45㎝ (기본)" in texts
# 두께도 기본 45 로 — 상부 0.45+0.30
assert "상부 0.75 m" in texts
chosen = _texts(build_figure(_sheet(back_len_cm=45)))
assert "뒷길이 ℓ₃ = 45㎝" in chosen and not any("안 골라" in text for text in chosen)
assert back_length_default_note({"back_len_cm": 45}, 45) is None
def _wall_sheet(**options) -> dict:
return {
"title": "옹벽 H=2",
"type_id": "retaining_wall",
"height_m": 2.0,
"options": {"height_m": 2.0, "form": "반중력식", **options},
}
def test_옹벽_단면_치수가_표의_관측_수량을_그대로_낸다() -> None:
"""표 = 그림 — 그림이 쓰는 단면으로 표 수량(라이브러리 반올림)이 다시 나와야 함."""
entry = load_observed_table().find("retaining_wall", {"form": "반중력식", "height_m": 2.0})
section = entry["section"]
amounts = {item["name"]: item["amount"] for item in entry["components"]}
footing, wall, key, blinding = (section[k] for k in ("footing", "wall", "key", "blinding"))
bottom = wall["top_m"] + wall["front_batter_m"] + wall["back_batter_m"]
assert abs(bottom - 0.45) < 1e-9
concrete = (
footing["width_m"] * footing["thickness_m"]
+ key["width_m"] * key["depth_m"]
+ (wall["top_m"] + bottom) / 2 * wall["height_m"]
)
blind = blinding["thickness_m"] * (
footing["width_m"] + 2 * blinding["overhang_m"] - key["width_m"]
)
form = math.hypot(wall["height_m"], wall["back_batter_m"]) + math.hypot(
wall["height_m"], wall["front_batter_m"]
)
weep = section["weep"]
pipe = wall["height_m"] / weep["area_m2"] * weep["pipe_length_m"]
assert round(concrete, 2) == amounts["콘크리트"] # 1.345
assert round(blind, 2) == amounts["버림콘크리트"] # 0.145
assert round(form, 1) == amounts["유로폼"] # 3.205
assert round(pipe, 2) == amounts["물구멍관"]
assert footing["thickness_m"] + wall["height_m"] == 2.0
def test_옹벽_그림은_기초잡석_두께를_산출_조건에서_받고_사유는_표와_같다() -> None:
shapes = build_figure(_wall_sheet(), rubble_thickness_m=0.3)
texts = _texts(shapes)
assert "기초잡석 T=0.3 m — 산출 조건 · 폭은 버림과 같음" in texts
assert "버림 T=0.10 · 폭 1.80 키 0.35 = 1.45 m" in texts
wall = _paths(shapes)[0]["points"]
assert [0.95, 0.4] in wall and [1.068, 2.0] in wall and [1.368, 2.0] in wall
# 표 사유와 같은 말 — 터파기 줄이 없다는 것
_rows, table_notes = expand_observed(
"retaining_wall",
{"form": "반중력식", "height_m": 2.0},
_wall_sheet() | {"options": {"length_m": 10}},
)
missing = [note for note in table_notes if "터파기" in note]
assert missing and missing[0] in texts
assert not any(text.startswith("기초잡석") for text in _texts(build_figure(_wall_sheet(), 0)))
def test_옹벽_못_그리는_장은_표와_같은_까닭() -> None:
assert "형식" in figure_reason({**_wall_sheet(), "options": {"height_m": 2.0}})
other = figure_reason(_wall_sheet(height_m=1.6) | {"height_m": 1.6})
assert other.startswith("그림 없음 — ") and "자료에 없습니다" in other
note = [t for t in _texts(build_figure(_wall_sheet(weep_hole_area_m2=3))) if "안 닿음" in t]
assert note and "Ø50" in note[0]
def test_양식_장은_양식이_푼_뒷길이로_그린다() -> None:
sheet = {**_sheet(), "formula_sheet": {"vars": {"L3": 55}}}
texts = _texts(build_figure(sheet))
assert "뒷길이 ℓ₃ = 55㎝ (기본)" in texts and "상부 0.85 m" in texts