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
This commit is contained in:
2026-10-05 16:09:33 +09:00
co-authored by Claude Opus 5.5
parent 4600b9970c
commit bf1fa50817
22 changed files with 1194 additions and 285 deletions
@@ -12,7 +12,11 @@ import os
import uuid
from typing import Any, Iterable
from B05_Profile.B05_Profile_Structures_Schema import StructureInstance, structure_type_map
from B05_Profile.B05_Profile_Structures_Schema import (
StructureInstance,
definition_version,
structure_type_map,
)
from common_util.common_util_json import atomic_write_json
from M02_MasterTemplete.StructOptions.M02_SO_Options import hidden_keys
@@ -125,6 +129,8 @@ def save_structures(
"revision": revision,
"structures": [item.model_dump(mode="json") for item in items],
"migrated_legacy": sorted(history),
# 저장 때 구조물 정의 판(PLAN 48-1) — 뒤에 정의가 바뀌어도 이 값이 어느 정의로 적혔는지 앎
"definition_version": definition_version(),
}
atomic_write_json(structures_file_path(project_root), payload)
return revision
@@ -192,12 +198,15 @@ def _validate_options(item: StructureInstance, definition, hidden: set[str] = fr
for key, value in item.options.items():
option = allowed.get(key)
if option is None:
raise ValueError(f"{definition.name}에 정의되지 않은 옵션입니다: {key}")
# 지금 정의에 없는 키 = 지난 정의 값 — 버리지 않고 그대로 남김(PLAN 48-0 ② · 오류 없음)
continue
if option.input == "number":
if isinstance(value, bool) or not isinstance(value, (int, float)):
raise ValueError(f"{definition.name}의 {option.label}은(는) 숫자여야 합니다.")
if not (value == value and abs(value) != float("inf")) or value < 0:
raise ValueError(f"{definition.name}의 {option.label}은(는) 0 이상이어야 합니다.")
elif option.input == "bool" and not isinstance(value, bool):
raise ValueError(f"{definition.name}의 {option.label}은(는) 예 · 아니오여야 합니다.")
elif option.input == "select" and option.choices and str(value) not in option.choices:
raise ValueError(f"{definition.name}의 {option.label} 값이 선택지에 없습니다: {value}")
for option in definition.options: