"""로직 만들기 판 시험 길(PLAN 35-2) — 고친 표로 시험 계산 · 표를 쓰는 로직. POST /api/m01/lb/calc {row, inputs, tables: {표 키: 표 통째}} → `/calc` 와 같은 답 GET /api/m01/lb/table-users ?key=표 키 → {key, users: [{key, 이름, via}]} 계산은 `/calc`(`M01_MasterData_Store.calc`)와 같은 길 — 부를 때마다 새로 읽은 마스터 사본 (`cm.load`)에 로직 줄 · 표를 바꿔 끼워 셈. 전역 캐시 · 파일은 안 건드림(동시 요청에 안전) · 표 고침은 저장하지 않음(정함 5). 권한 = `/api/m01` 과 같음(등록 자리 `main.py` 한 줄 · 폴더째 지우면 그 줄도 지움). """ from __future__ import annotations import json import re from typing import Any from fastapi import APIRouter, HTTPException from pydantic import BaseModel from M01_MasterData import M01_MasterData_Store as store from M01_MasterData.M01_MasterData_Router import _num cm = store.cm router = APIRouter(prefix="/api/m01/lb", tags=["M01 LogicBuilder (시험)"]) class LbCalcBody(BaseModel): row: dict[str, Any] inputs: dict[str, Any] = {} tables: dict[str, dict[str, Any]] = {} def _put_tables(files: dict[str, dict], tables: dict[str, dict]) -> None: """표 키 → 고친 표 — 그 표가 든 파일 사본 자리에 바꿔 끼움 · 키는 못 바꿈 · 없는 표는 404.""" for key, table in tables.items(): for data in files.values(): if data.get("그룹") not in cm.mf.TABLE_GROUPS: continue items = data.get("표") or [] at = next((i for i, t in enumerate(items) if str(t.get("키")) == key), None) if at is not None: items[at] = {**store._dec(table), "키": key} break else: raise store.StoreError(404, f"없는 표 「{key}」") def calc(row: dict, inputs: dict, tables: dict[str, dict]) -> dict: """`Store.calc` 와 같은 셈 + 표 바꿔 끼우기 — 답 모양도 같음.""" given = {k: store._dec(v) for k, v in inputs.items()} files = cm.load(folder=store.FOLDER) # 부를 때마다 새 사본 — 캐시 아님 _put_tables(files, tables) key = store._swap(files, str(row.get("키") or ""), row, None) try: given = cm.mf.fill_empty(cm.mf.Master(files).logic(key), given) result = cm.calc(files, key, given) except (cm.mf.FormulaError, ArithmeticError, KeyError, TypeError, ValueError) as e: return {"ok": False, "reason": str(e) or type(e).__name__} if "결과" in result: return {"ok": True, "result": result["결과"], "middle": result["중간"]} return { "ok": True, "lines": result["줄"], "sums": {k: result[k] for k in cm.mf.BUNDLE}, "middle": result["중간"], } def table_users(key: str) -> dict: """표를 쓰는 로직 — 표 「용도.로직키」 + 로직 글에 표 키가 든 것(읽기 전용 캐시에서).""" files, _ = store.loaded() table = next( ( t for data in files.values() if data.get("그룹") in cm.mf.TABLE_GROUPS for t in data.get("표") or [] if str(t.get("키")) == key ), None, ) if table is None: raise store.StoreError(404, f"없는 표 「{key}」") listed = {str(k) for k in (table.get("용도") or {}).get("로직키") or []} named = re.compile(rf"(? dict: return _call(calc, body.row, body.inputs, body.tables) @router.get("/table-users") def get_table_users(key: str) -> dict: return _call(table_users, key)