Files
Aislo/M02_MasterTemplete/StructOptions/M02_SO_Check.py
T
eomsangdonandClaude Opus 5.5 0da2fa0767 fix(구조물표): 322-2 다듬기(브레인 화면 확인) — 화면에 영어 키 안 보임 · 검사 글 한국어(화면 · 서버 짝) · 부품 탭 읽는 칸 = 정의 이름 · 줄바꿈 · 한눈 표 폭 줄임 · 바꿨다 되돌리면 [저장] 꺼짐
- 자리꼴 점 · 구간 · 터 · 정본 관 지점 · 구조물(키는 툴팁) · 고르기 글 = 부품 짧은 이름(괄호 설명 · 키는 툴팁) · 횡단도 없음 = 「안 만듦」 · 「벽 스윕」 → 「벽」 · 종류 키는 툴팁
- 검사 글: 「형상이 「없음」이면 횡단도도 「안 만듦」」 · 부품 짧은 이름 · 칸 이름 · 종류 이름(M02_SO_Def_Check.ts = M02_SO_Check.py)
- 부품 탭: 읽는 칸 = 그 부품을 쓰는 종류의 칸 이름(단위) · 표 줄바꿈(table-layout fixed)
- 한눈 표 형상칸 「기본 형태」 = 그림 형태 고르기(REVET_FORMS)
- [저장] 켬 = 저장할 꼴(tidy · 목록 짝 enabled)끼리 지금 판과 다를 때만(M02_SO_Def_Reflect.differs)
- 시험: 322-2 검사 글 · 되돌림 견본

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

184 lines
8.2 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
import re
from typing import Any
from common_util.common_util_structure_catalog import load_parts, resolve_catalog
BRANCHES = ("횡단도", "카드이름", "형상", "3D")
# 화면 글(322-2 다듬기 · TS `M02_SO_Def_Reflect` 짝) — 자리꼴 · 정본 한국어 · 부품 짧은 이름
PLACE_KO = {"point": "점", "interval": "구간", "site": "터"}
OWNER_KO = {"pipe_points": "관 지점", "structures": "구조물"}
def short_name(full: str) -> str:
"""괄호 설명을 뗀 부품 이름(TS `shortName` 짝)."""
return re.sub(r"\s*\(.*\)\s*$", "", full)
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']}」 형상이 「없음」이면 횡단도도 「안 만듦」")
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"]),
)
labels = {o["key"]: o.get("label") or o["key"] for o in t.get("options") or []}
for branch, part_id in used:
part = parts[branch].get(part_id)
if part is None:
continue
name = short_name(part["이름"])
place = PLACE_KO.get(row["placement"], row["placement"])
owner = OWNER_KO.get(row["owner"], row["owner"])
if row["placement"] not in part["자리꼴"] or row["owner"] not in part["정본"]:
out.append(f"「{row['name']}」 {branch} 「{name}」 — {place} · {owner} 에 안 맞음")
for key in part["읽는 칸"]:
if key in keys and key in hidden:
out.append(
f"「{row['name']}」 {branch} 「{name}」 이 쓰는 칸 「{labels[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"]
name = old.get("name") or tid
t = now.get(tid)
if t is None:
out.append(f"종류 「{name}」 — 지울 수 없음")
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"「{name}」 {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 []):
label = o.get("label") or o["key"]
out.append(f"「{name}」 「{label}」 선택지 값 — 고칠 수 없음(이름만 고침)")
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 []