feat(M01): 마스터 데이터 관리자 API 서버 쪽 — 읽기 · 시험 계산 · 저장

- M01_MasterData 라우터 + 파일 저장소 (/api/m01 · 시스템관리자 전용)
- 저장 = 판본 대조(409) → 적용 → 틀·로직 변수 검사(새 걸림 422) → 씀
- check_master.py 에 load(folder) · check_saved · calc 드러냄
- 계약 문서 장 이름 보정(공통3장)
- 시험 test_m01_api.py 8개 (임시 폴더 사본)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PAdA5ThqmtVSsbzjusk1cJ
This commit is contained in:
2026-09-19 18:15:20 +09:00
co-authored by Claude Opus 5
parent 6b34f5d7d3
commit e11827f8f4
6 changed files with 602 additions and 7 deletions
+22 -6
View File
@@ -59,10 +59,10 @@ ROW_KEYS = {
}
def load(names: list[str] | None = None) -> dict[str, dict]:
"""첫 층 JSON 모두(밑줄 파일 빼고) — 수는 Decimal."""
def load(names: list[str] | None = None, folder: Path = MASTER) -> dict[str, dict]:
"""첫 층 JSON 모두(밑줄 파일 빼고) — 수는 Decimal. `folder` = 마스터 폴더(M01 시험은 사본)."""
out = {}
for path in sorted(MASTER.glob("*.json")):
for path in sorted(Path(folder).glob("*.json")):
if path.name.startswith("_") or (
names and path.name not in names and path.stem not in names
):
@@ -73,8 +73,19 @@ def load(names: list[str] | None = None) -> dict[str, dict]:
return out
def master() -> mf.Master:
return mf.Master(load())
def master(folder: Path = MASTER) -> mf.Master:
return mf.Master(load(folder=folder))
def check_saved(files: dict[str, dict], changed: list[str]) -> list[str]:
"""저장 전 검사(M01) — 고친 파일의 틀 + 모든 로직의 변수. `files` = 폴더 전부(고친 뒤)."""
out = [x for name in changed for x in check_form(name, files[name])]
return out + check_logics(files, mf.Master(files))
def calc(files: dict[str, dict], ref: str, given: dict) -> dict:
"""시험 계산(M01) — `ref` = 「원문:로직 열쇠」 · 멈추면 FormulaError."""
return mf.run(mf.Master(files), ref, given)
# ── (1) 틀 ────────────────────────────────────────────────────────────
@@ -228,7 +239,12 @@ def check_body(files: dict[str, dict]) -> list[str]:
if not text:
out.append(f"{where} · 본문 절 못 찾음 「{table.get('출처')}")
continue
mine = _flat(table.get("")) | _flat(table.get("기준")) | _flat(table.get("")) | _flat(table.get("값칸"))
mine = (
_flat(table.get(""))
| _flat(table.get("기준"))
| _flat(table.get(""))
| _flat(table.get("값칸"))
)
fake = mine - numbers_in(text)
if fake:
out.append(f"{where} · 허구 {len(fake)} {sorted(fake, key=Decimal)}")