"""계산 자리 판(PLAN 504-2 · 요구 309-2) — 시스템 관리자 「계산 자리」 창이 읽고 쓰는 판 하나. 판 = `resources/master_template/calc_sites.json` `{version, sites: {묶음: "both" | "server"}}`. both = 조작 중 브라우저 셈 + [저장] 서버 정본(지금 등록부 자리) · server = 조작 중에도 서버 길 (G1 · D3 = 504-2 · ③ 토량 · ④ 이월 · ⑧ 고정 브라우저(시험용 · fixed 는 정본 칸 표시) = 511-A 창구). 판이 없거나 깨지면 기본값(전부 both = 등록부 그대로). [저장] · [확정] 서버 정본 길은 판을 안 읽음. 묶음 12 줄 = 등록부(`design_calc_registry*.ts`) # 전부 · 같은지 시험 `test_309_2_calc_sites.py`. """ from __future__ import annotations import json import os from pathlib import Path from typing import Any ROOT = Path(__file__).resolve().parents[1] PATH = ROOT / "resources" / "master_template" / "calc_sites.json" VALUES = ("both", "server") # 묶음 · 등록부 # · 바꿀 수 있음 · 잠금 줄의 고정 자리(both · server · browser) · 까닭 열쇠 GROUPS: tuple[dict[str, Any], ...] = ( {"key": "ground_sample", "nos": ["G1"], "switchable": True, "fixed": None, "why": None}, {"key": "corridor_mesh", "nos": ["D3"], "switchable": True, "fixed": None, "why": None}, { "key": "quantity", "nos": ["E1", "F7", "F8", "C4"], "switchable": True, "fixed": None, "why": "quantity", }, {"key": "carry", "nos": ["R9", "F10"], "switchable": True, "fixed": None, "why": "carry"}, # 묶음 단계(511-B · 단계째 서버 Node `stage.*`) — 짝 모으기 B-3 ~ B-5 끝난 줄부터 엶 # 노선(511-B B-5 · 노선 저장 폴리라인 = Node `route.polyline` · 조작 중 「서버」 = 같은 id) {"key": "route", "nos": ["R1", "R3"], "switchable": True, "fixed": None, "why": None}, # 따라감(511-B B-5 · 서버 정본 Node · 조작 중 「서버」 = 잠잠 뒤 창구 맥락 + route · follow 두 단계) { "key": "follow", "nos": ["P5", "P6", "F1", "F2"], "switchable": True, "fixed": None, "why": None, }, # 측점(511-B B-2 · 서버 측점 생성 = Node `stations.sections` · 조작 중 「서버」 = `stations.list`) { "key": "stations", "nos": ["R5", "R6", "R7", "F3"], "switchable": True, "fixed": None, "why": None, }, # 종단 = 짝 모음 끝(B-3 · 서버 Node `profile.base` · `profile.alignment`) — 엶 { "key": "profile", "nos": ["P1", "P2", "P3", "P4", "P8"], "switchable": True, "fixed": None, "why": None, }, { "key": "cross", "nos": ["G2", "G4", "C1", "C2", "C3", "C5", "C6", "F4", "F5", "F6"], "switchable": False, "fixed": "both", "why": "pair_B4", }, # 폼 기본값 도구(B04 · B08 파이썬 · 고를 것 없음) {"key": "form_default", "nos": ["F9"], "switchable": False, "fixed": "both", "why": None}, { "key": "fixed_server", "nos": ["E2", "G3", "D1", "D2", "N1"], "switchable": False, "fixed": "server", "why": "server", }, { "key": "fixed_browser", "nos": ["R2", "R4", "R8", "P7", "C7", "D4"], "switchable": True, "fixed": "browser", "why": "browser", }, ) # 판 줄 차례 = 묶음 차례(파일 늘 같은 꼴) ORDER = tuple(g["key"] for g in GROUPS if g["switchable"]) SWITCHABLE = frozenset(ORDER) LOCKED = frozenset(g["key"] for g in GROUPS if not g["switchable"]) def read_sites() -> dict[str, Any]: """판 — 없음 · 깨짐 · 모르는 값 = 그 줄 기본값(both).""" try: raw = json.loads(PATH.read_text(encoding="utf-8")) except (OSError, ValueError): raw = {} stored = raw.get("sites") if isinstance(raw, dict) else None stored = stored if isinstance(stored, dict) else {} sites = {k: stored.get(k) if stored.get(k) in VALUES else "both" for k in ORDER} version = raw.get("version") if isinstance(raw, dict) else None return {"version": version if isinstance(version, int) else 0, "sites": sites} def check_changes(changes: dict[str, str]) -> None: """잠금 줄 · 모르는 줄 · 모르는 값 = ValueError(창구가 400).""" for key, value in changes.items(): if key in LOCKED: raise ValueError(f"잠긴 줄 — {key}") if key not in SWITCHABLE: raise ValueError(f"모르는 줄 — {key}") if value not in VALUES: raise ValueError(f"모르는 값 — {key}={value}") def write_sites(changes: dict[str, str]) -> tuple[dict[str, Any], list[str]]: """검사 → 바뀐 줄만 얹어 판 번호 +1 · 통째 바꿔 씀(반쯤 쓴 판 없음). 돌려줌 = 새 판 · 바뀐 줄.""" check_changes(changes) current = read_sites() changed = sorted(k for k, v in changes.items() if current["sites"][k] != v) if not changed: return current, [] board = {"version": current["version"] + 1, "sites": {**current["sites"], **changes}} temp = PATH.with_suffix(".json.tmp") temp.write_text(json.dumps(board, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") os.replace(temp, PATH) return board, changed