Files
Aislo/M01_MasterData/LogicBuilder/M01_LB_Calc.py
T
eomsangdonandClaude Opus 5.5 a9efc452cc feat(m01-lb): 로직 만들기 판 시험 계산 서버 길 · 화면 부르기
- M01_LB_Calc.py — POST /api/m01/lb/calc {row, inputs, tables}: /calc 와 같은 셈 · 부를 때마다 새 사본에 표를 바꿔 끼움(전역 캐시 · 파일 안 건드림 · 저장 안 함)
- GET /api/m01/lb/table-users — 표 「용도.로직키」 + 로직 글 대조
- main.py 등록 한 줄(시스템 관리자 · 폴더째 지우면 이 줄도 지움)
- M01_LB_Api.ts — lbCalcLater(0.3초 · 최신만 · 쉬운 말) · tableUsers · fetchLbTable
- 시험 test_m01_lb_calc.py — 견본 표 덮어 값 바뀜 · 캐시 · 파일 그대로 · 표 없으면 /calc 와 같음 · 404 · 사용처 · 403

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KmELpdXFDRZHhfbGuRhSmL
2026-10-01 11:45:22 +09:00

115 lines
4.4 KiB
Python

"""로직 만들기 판 시험 길(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"(?<![A-Z0-9]){re.escape(key)}(?![0-9])")
users = []
for data in files.values():
if data.get("그룹") != "로직":
continue
for row in data.get("줄") or []:
in_use = str(row.get("키")) in listed
in_text = bool(named.search(json.dumps(row, ensure_ascii=False, default=str)))
if in_use or in_text:
via = "둘" if in_use and in_text else "용도" if in_use else "글"
users.append({"key": row.get("키"), "이름": row.get("이름"), "via": via})
return {"key": key, "users": users}
def _call(fn, *args):
try:
return _num(fn(*args))
except store.StoreError as e:
raise HTTPException(status_code=e.status, detail=e.detail) from e
@router.post("/calc")
def post_calc(body: LbCalcBody) -> 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)