Files
Aislo/resources/tester/test_b08_structure_table_holes.py
T
eomsangdonandClaude Opus 5.5 bf1fa50817 feat(M02): 48-1 구조물 정의 한 벌 — 정의 틀 문서 · 판 1 옮김 · 읽기 길
- 정의 틀 = docs/raw/M02_구조물옵션정의/정의_틀.md (담는 것 · 키 규칙 · 꼴 · 배치 · 그룹 형상 · 규칙 · 동작 · 판 이력)
- resources/master_template/구조물정의.json 판 1 = 옛 B05 레지스트리(37종 · 옵션 235) + 옵션표시 + 옵션그룹 키 그대로 옮김
- GET /api/m02/structure-definition(로그인) · PUT(시스템 관리자만 · 판 +1 · 이력 판_N) · 검사(꼴 · 키 겹침 · 규칙 대상)
- GET /structure-types · B05 구조물 검사가 새 정의를 읽음(응답 꼴 그대로) · 옵션 보이기 · 그룹 저장도 정의 칸으로
- 프로젝트 structures.json 에 저장 때 정의 판 · 정의에 없는 키 값은 남김(오류 없음)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EYJJRCfHfdCGNhmXTjQKV7
2026-10-05 16:09:33 +09:00

99 lines
4.0 KiB
Python

"""구조물도 그림을 그리려다 드러난 **표 구멍** (2026-09-14 · PLAN 12장 남은 종류 그림).
⚠ 셋 — 값이 서기는 하거나(떼흙막이) 조용히 0 이라 아무 시험도 안 잡던 자리.
① 골막이·바닥막이는 개소당·㎡당 값인데 내역 단위가 「m · 연장 0」 → **수량 0, 내역에 안 섬**
② 떼흙막이는 개소당 값이 연장으로 나뉘어 **떼 0.139/m** 로 보였음
③ 개거 「L형수로 H=0.2」 — 등록부 선택지 글자와 엔진 키가 달라 골라도 영영 안 섬
④ 기슭막이 사유 「돌 종류·뒷길이 칸이 없어」 — 칸이 선 뒤로 거짓 · 같은 말 두 번
"""
from __future__ import annotations
import json
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_Handoff import build_handoff # noqa: E402
from B08_Quantity.B08_Quantity_Engine_StructureSheet import build_standard_sheets # noqa: E402
from B08_Quantity.B08_Quantity_Engine_UnitQuantity import build_table # noqa: E402
def _table(type_id: str, start: float, end: float, **options) -> dict:
structure = {
"structure_id": type_id,
"type_id": type_id,
"start_m": start,
"end_m": end,
"options": options,
}
return build_table([structure], None)
def _sheet(table: dict) -> dict:
return build_standard_sheets(table)["sheets"][0]
def _unit(sheet: dict, name: str) -> float:
return next(row["unit_amount"] for row in sheet["rows"] if row["name"] == name)
def test_골막이는_개소당으로_서고_내역_수량이_1_개소() -> None:
table = _table(
"erosion_check", 50, 50, form="돌", height_m=1.5, top_length_m=4, bottom_length_m=2
)
item = table["structures"][0]
assert (item["billing_unit"], item["billing_quantity"]) == ("개소", 1.0)
sheet = _sheet(table)
assert sheet["unit_label"] == "개소당" and sheet["unpriced_rows"] == []
row = next(r for r in build_handoff(unit_quantity_table=table)["work_items"] if r["unit"])
assert (row["unit"], row["quantity"]) == ("개소", 1.0) # 전: m · 0.0 → 내역에 안 섬
def test_떼흙막이는_개소당_값이_그대로_보인다() -> None:
sheet = _sheet(_table("soil_guard", 60, 70, form="떼", height_m=0.5, length_m=10))
assert sheet["unit_label"] == "개소당"
assert abs(_unit(sheet, "떼") - 1.39) < 1e-9 # 전: 0.139 (m당으로 나뉨)
def test_바닥막이는_제곱미터당() -> None:
table = _table("bed_sill", 80, 80, form="돌붙임(찰)", area_m2=12, height_m=0.3)
sheet = _sheet(table)
assert sheet["unit_label"] == "㎡당" and sheet["billing_total"] == 12
assert abs(_unit(sheet, "돌붙임") - 1.0) < 1e-9 and abs(_unit(sheet, "입적") - 0.45) < 1e-9
def test_개거_L형수로는_등록부_글자로_선다() -> None:
registry = json.loads(
(ROOT / "resources" / "master_template" / "구조물정의.json").read_text(encoding="utf-8")
)
choices = next(
option["choices"]
for item in registry["types"]
if item["type_id"] == "open_ditch"
for option in item["options"]
if option["key"] == "ditch_spec"
)
for choice in choices:
item = _table("open_ditch", 0, 0, ditch_spec=choice, length_m=5)["structures"][0]
assert item["components"], (choice, item["notes"])
concrete = next(
c
for c in _table("open_ditch", 0, 0, ditch_spec="L형수로 H=0.2", length_m=5)["structures"][
0
]["components"]
if c["name"] == "콘크리트"
)
assert abs(concrete["amount"] - 0.1508 * 5) < 1e-9
def test_기슭막이_사유에_없는_칸_이야기가_없다() -> None:
notes = _table("revetment", 0, 10, form="돌쌓기(찰)", height_m=2, length_m=10)["structures"][0][
"notes"
]
assert not any("칸이 없어" in note for note in notes)
assert any("뒷길이를 안 골라" in note for note in notes) # 같은 사실은 돌쌓기 한 줄이 말함