Files
Aislo/M02_MasterTemplete/StructOptions/M02_SO_Check.py
T
eomsangdonandClaude Opus 5.5 3abd2bdfd4 feat(구조물표): 322-2 M02 표 화면 — 탭 셋(한눈 표 · 종류별 · 부품) · 반영 · 부품 · 알약 색 저장 · 목록 + enabled 함께 · 검사 짝(화면 = 서버 · 1-3 규칙 · 만든 뒤 고정) · 명세 폼 보임 따름 · 판 알림
- 한눈 표(M02_SO_Def_Table): 군 · 종류 ▲▼ · 이름 · 목록 · 자리꼴 · 정본(고정) · 횡단도 · 카드 이름 · 형상(+ 벽 기본 형태 · 물빼기) · 알약 색 · 3D · 검사 · 처음 값 = 흐림 「기본」 · 바꿈 = 진함 · [처음 값으로]
- 종류별: 반영 칸 + 명세 이름 나무 줄마다 보임(컨테이너 = 그 안 칸) · 못 숨김 · 미리보기 숨김 따름 · 「안 씀」 = 목록 B06 만
- 부품 탭(M02_SO_Def_Parts): 부품 · 읽는 칸 · 자리꼴 · 정본 · 쓰는 종류 · 켬/끔(정의 부품 = 끈 것만)
- 편집 손질(M02_SO_Def_Reflect): 처음 값이면 반영에서 지움 · [저장] 때 enabled = 목록 both 일 때만 참
- 검사 짝 M02_SO_Def_Check.ts ↔ M02_SO_Check.py(글까지 같음): 반영 · 부품 꼴 · 형상 none → 횡단도 none · 부품 자리꼴 · 정본 · 기본값 없는 읽는 칸 새로 숨김 금지(앞 판 숨김 그대로) · 종류 지우기 · 자리꼴 · 정본 · 군 글자 · 선택지 값 고정 · 서버 check(doc, 지금 판) · 앞 판 없으면 꼴만
- 명세 폼: 쓰는 옵션 키가 모두 숨긴 칸 · 상자는 안 그림(값 그대로) · B05 · B06 시설 · 구조물 명세 폼이 정의 「표시」를 따름
- 판 알림: GET 구조물 목록에 definition_version · structures.json 판 ≠ 지금 판이면 「구조물 정의가 판 N 으로 바뀜 — 저장하면 반영」 한 번
- 시험: test_322_2_check_mirror(거울 · 규칙 · 반영 손질 · 서버 저장 · 422) · 판 알림 · 옛 정의 화면 시험 = 선택지 값 대신 이름 표 · 못 숨김 아닌 칸

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ji4nuyLVHcxrXWHTdb5nRD
2026-10-14 15:40:38 +09:00

170 lines
7.6 KiB
Python

"""구조물 표 검사(PLAN 322-2 · 1-3 규칙) — 반영 · 부품 꼴 · 표 줄과 부품이 맞음 · 만든 뒤 고정.
- 반영 = `{종류: {목록 · 횡단도 · 카드이름 · 형상 · 형상칸 · 3D}}` · 값 = 부품 상자에 있는 것
- 부품 = `{"갈래.부품": false}`(끈 것만)
- 표 줄(정의 ⊕ 처음값) — 형상 none 이면 횡단도도 none(218) · 부품 자리꼴 · 정본 = 그 종류 줄 ·
부품이 읽는 칸 중 기본값 없는 칸(비워 두는 뜻 칸 빼고)은 새로 숨기지 못함(정의 「표시」 · 숨기면
그림이 늘 기본값을 씀 · 앞 판에서 이미 숨긴 칸은 그대로 둠)
- 앞 판이 없으면(옛 판 읽기) 꼴만 검사
- 앞 판과 비교 — 종류 지우기 없음 · 자리꼴 · 정본 · 군 글자 고정 · 선택지 값 고정(이름은 이름 표로)
화면 짝 = `M02_SO_Def_Check.ts`(같은 글)
거울 시험 = `resources/tester/test_322_2_check_mirror.py`.
"""
from __future__ import annotations
from typing import Any
from common_util.common_util_structure_catalog import load_parts, resolve_catalog
BRANCHES = ("횡단도", "카드이름", "형상", "3D")
OFF_VALUE = {"횡단도": "none", "카드이름": "name", "형상": "none", "3D": "none"}
LISTS = ("both", "b06", "hidden")
_FIELDS = {"목록", "형상칸", *BRANCHES}
def _shape_errors(doc: dict[str, Any], known: set[str]) -> list[str]:
"""반영 · 부품 꼴."""
parts = load_parts()["parts"]
out: list[str] = []
reflect = doc.get("반영", {})
if not isinstance(reflect, dict):
out.append("반영은 {종류: 묶음} 이어야 함")
else:
for tid, row in reflect.items():
if tid not in known or not isinstance(row, dict):
out.append(f"반영 「{tid}」 — 없는 종류이거나 묶음 아님")
continue
for field, value in row.items():
where = f"반영 「{tid}.{field}」"
if field not in _FIELDS:
out.append(f"{where} — 모르는 칸")
elif field == "목록":
if value not in LISTS:
out.append(f"{where} — both · b06 · hidden 중 하나")
elif field == "형상칸":
ok = isinstance(value, dict) and all(
(isinstance(v, str) and bool(v.strip()))
if k == "form"
else (k == "weep" and isinstance(v, bool))
for k, v in value.items()
)
if not ok:
out.append(f"{where} — form 글 · weep 참거짓")
elif not isinstance(value, str) or (
value != OFF_VALUE[field] and value not in parts[field]
):
shown = value if isinstance(value, str) else _js(value)
out.append(f"{where} — 없는 부품 「{shown}」")
off = doc.get("부품", {})
if not isinstance(off, dict):
out.append("부품은 {갈래.부품: 참거짓} 이어야 함")
else:
for key, value in off.items():
branch, _, part = key.partition(".")
if part not in parts.get(branch, {}):
out.append(f"부품 「{key}」 — 없는 부품")
elif not isinstance(value, bool):
out.append(f"부품 「{key}」 — 참거짓")
return out
def _js(value: Any) -> str:
"""TS `String(value)` 와 같은 글(검사 글 짝)."""
if value is None:
return "null"
if isinstance(value, bool):
return "true" if value else "false"
if isinstance(value, (list, tuple)):
return ",".join(_js(v) for v in value)
if isinstance(value, dict):
return "[object Object]"
if isinstance(value, float) and value.is_integer():
return str(int(value))
return str(value)
def _hidden_in(doc: dict[str, Any] | None, tid: str) -> list[str]:
row = ((doc or {}).get("표시") or {}).get(tid) or {}
return [key for key, shown in row.items() if shown is False]
def _row_errors(doc: dict[str, Any], before: dict[str, Any] | None) -> list[str]:
"""표 줄 — 218 규칙 · 부품 자리꼴 · 정본 · 읽는 칸 보임(새로 숨김만)."""
parts = load_parts()["parts"]
rows = resolve_catalog(doc)
out: list[str] = []
for t in doc.get("types") or []:
row = rows.get(t["type_id"])
if row is None:
continue
if row["shape"] == "none" and row["station"] != "none":
out.append(f"「{row['name']}」 형상 none — 횡단도도 none 이어야 함")
kept = _hidden_in(before or doc, t["type_id"]) # 앞 판 없음 = 새로 숨긴 것 없음
hidden = [key for key in _hidden_in(doc, t["type_id"]) if key not in kept]
# 숨기면 그림이 기본값을 씀 — 기본값도 비워 두는 뜻도 없는 칸만 못 숨김
keys = {
o["key"]
for o in t.get("options") or []
if o.get("default") is None and not o.get("empty_means")
}
used = (
("횡단도", row["station"]),
("카드이름", row["title"]),
("형상", row["shape"]),
("3D", row["model3d"]),
)
for branch, part_id in used:
part = parts[branch].get(part_id)
if part is None:
continue
if row["placement"] not in part["자리꼴"] or row["owner"] not in part["정본"]:
out.append(
f"「{row['name']}」 {branch} 「{part_id}」 — 자리꼴 {row['placement']} · "
f"정본 {row['owner']} 에 안 맞음"
)
for key in part["읽는 칸"]:
if key in keys and key in hidden:
out.append(
f"「{row['name']}」 {branch} 「{part_id}」 이 읽는 칸 「{key}」 — "
"숨길 수 없음"
)
return out
def _fixed_errors(doc: dict[str, Any], before: dict[str, Any]) -> list[str]:
"""앞 판과 비교 — 만든 뒤 고정."""
out: list[str] = []
now = {t["type_id"]: t for t in doc.get("types") or []}
for old in before.get("types") or []:
tid = old["type_id"]
t = now.get(tid)
if t is None:
out.append(f"종류 「{tid}」 — 지울 수 없음")
continue
fixed = (
("자리꼴", old.get("placement"), t.get("placement")),
("정본", old.get("managed_by"), t.get("managed_by")),
("군 글자", old.get("group"), t.get("group")),
)
for what, a, b in fixed:
if a != b:
out.append(f"「{tid}」 {what} — 만든 뒤 고정")
options = {o["key"]: o for o in t.get("options") or []}
for o in old.get("options") or []:
n = options.get(o["key"])
if o.get("input") != "select" or n is None:
continue
if sorted(o.get("choices") or []) != sorted(n.get("choices") or []):
out.append(f"「{tid}.{o['key']}」 선택지 값 — 고칠 수 없음(이름은 이름 표로)")
return out
def catalog_errors(doc: dict[str, Any], before: dict[str, Any] | None) -> list[str]:
"""구조물 표 검사 — 틀린 것 글들(빈 목록 = 통과). `before` = 지금 판(없으면 고정 검사 안 함)."""
known = {t["type_id"] for t in doc.get("types") or [] if isinstance(t, dict)}
shape = _shape_errors(doc, known)
if shape:
return shape
return _row_errors(doc, before) + _fixed_errors(doc, before) if before else []