feat(826-2): M01 기계 = 한 대 한 파일 · M01 저장 길 줄이기

- 기계.json 한 파일 → 폴더 기계/(EQ 키마다 파일 + _머리.json · 차례 = 키 차례) · 옮긴 빌더 build_기계폴더_826_2 (655 대 머리 · 줄 · 차례 대조 뒤 옛 파일 지움)
- 읽기 도우미 master_machine_files — cm.load · Store_Cache · Store.read 가 옛 「기계.json」 한 표 꼴로 지어 줌(API · 화면 · 색인 그대로) · 쓰기 = 바뀐 대 파일만
- 기계.json 직접 열던 곳 도우미로: M02_San_Basis_Index · M02_Logic_Standard 지문 · build_용도 · master_keys.adopt · _틀 · _갱신절차
- 저장 몸 M01_MasterData_Store_Save — 전 = 기억 · 후 = 고친 파일만 얕은 사본 · 틀은 고친 줄만 · 조합 · 일위대가 문서는 로직 줄이 빠질 때만 · 재료 찾기 기억 이어받음 · 저장 뒤 쓴 파일만 기억에 갈아 끼움
- 마스터 사본 시험 22 개 = 기계 폴더도 복사 · 821_2 · 815_2 글 대조 = 대 파일 하나 대조(옛 한 파일 글 대조 낡음)
- 새 시험 test_826_2 — 폴더 = 한 표 · 한 대 한 파일 · 새 · 지움 · 409 · 옛 길과 같은 오류 스무 견본

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-12 19:45:00 +09:00
co-authored by Claude Opus 5.5
parent 5fb7b6c16b
commit c7cbef86a0
694 changed files with 40486 additions and 39831 deletions
+23 -41
View File
@@ -28,7 +28,7 @@ import master_text as mtx # noqa: E402
from M01_MasterData import M01_MasterData_Store_Cache as cache # noqa: E402
from M01_MasterData.M01_MasterData_Store_Find import hit as _hit # noqa: E402
from M01_MasterData.M01_MasterData_Store_Find import squash as _squash # noqa: E402
from M01_MasterData.M01_MasterData_Store_Shape import dump, keep_shape # noqa: E402
from M01_MasterData.M01_MasterData_Store_Shape import dump # noqa: E402,F401
_COPY = os.environ.get("AISLO_MASTER_DIR") # 화면 저장 시험 사본 폴더(정함 13) · 없으면 정본
FOLDER: Path = Path(_COPY).resolve() if _COPY else cm.MASTER # 시험은 사본 폴더로 바꿈
@@ -47,7 +47,8 @@ class StoreError(Exception):
# ── 읽기 ──────────────────────────────────────────────────────────────
def _path(file: str) -> Path:
path = FOLDER / file
if "/" in file or "\\" in file or file.startswith("_") or not path.is_file():
there = path.is_file() or (file == cm.mmf.NAME and cm.mmf.here(FOLDER)) # 기계 = 폴더(826-2)
if "/" in file or "\\" in file or file.startswith("_") or not there:
raise StoreError(404, f"없는 파일 「{file}」")
return path
@@ -57,10 +58,19 @@ def version_of(raw: bytes) -> str:
def read(file: str) -> tuple[dict, str]:
raw = _path(file).read_bytes()
_path(file)
if file == cm.mmf.NAME:
return cm.mmf.table(FOLDER)
raw = (FOLDER / file).read_bytes()
return json.loads(raw, parse_float=Decimal, parse_int=Decimal), version_of(raw)
def version(file: str) -> str:
"""판만(읽어 풀지 않음 — 저장 409 확인)."""
_path(file)
return version_of(cm.mmf.raw(FOLDER, file))
# ── 한 번 읽어 두기(`_Store_Cache`) ────────────────────────────────────
def loaded() -> tuple[dict[str, dict], object]:
"""(파일 묶음, 색인) — **읽기 전용**. 고칠 것이면 `cm.load` 로 새로 읽을 것."""
@@ -109,13 +119,15 @@ def files_of(group: str) -> list[dict]:
if group not in cm.mf.GROUPS:
raise StoreError(404, f"없는 그룹 「{group}」")
out = []
for path in [*FOLDER.glob(f"{group}.json"), *FOLDER.glob(f"{group}_*.json")]:
data, version = read(path.name)
for name in cm.mmf.names(FOLDER):
if name != f"{group}.json" and not name.startswith(f"{group}_"):
continue
data, version = read(name)
out.append(
{
"file": path.name,
"file": name,
"book": _book(data),
"chapter": chapter_of(path.name, data),
"chapter": chapter_of(name, data),
"edition": data.get("판"),
"rows": len(data.get(items_key(data)) or []),
"version": version,
@@ -659,37 +671,7 @@ def _apply(data: dict, changes: list[dict], file: str, book: dict) -> None:
def save(batch: list[dict]) -> list[dict]:
with _LOCK:
stale = [p.get("file", "") for p in batch if read(p.get("file", ""))[1] != p.get("version")]
if stale:
raise StoreError(409, {"stale": stale})
before, after = cm.load(folder=FOLDER), cm.load(folder=FOLDER) # after = 고칠 사본
book_path = FOLDER / mk.BOOK.name
book = mk.load_book(book_path)
issued = dict(book["다음"])
names = [part["file"] for part in batch]
for part in batch:
_apply(after[part["file"]], part.get("changes") or [], part["file"], book)
mk.finish(book)
# 저장이 흔드는 줄만 검사(고친 줄 · 그 줄을 부르던 줄 · 그 요소를 보던 줄) —
# 마스터 전체 검사는 `check_master` 몫
narrowed = cache.narrow(FOLDER, batch, before, after)
old = set(cache.check_saved(before, names, mk.load_book(book_path), narrowed))
new = [x for x in cache.check_saved(after, names, book, narrowed) if x not in old]
if new:
raise StoreError(422, {"errors": new})
if book["다음"] != issued:
mk.save_book(book, book_path)
out = []
for name in dict.fromkeys(names):
old_text = (FOLDER / name).read_bytes().decode("utf-8")
text = keep_shape(old_text, before[name], after[name], items_key(after[name])) or dump(
after[name]
)
tmp = FOLDER / f".{name}.tmp"
tmp.write_text(text, encoding="utf-8", newline="\n")
os.replace(tmp, FOLDER / name)
out.append({"file": name, "version": version_of(text.encode("utf-8"))})
# 저장 뒤에는 그 파일을 바로 다시 읽게 · 흔들린 자리를 알려 줘 그 줄만 다시 세게
cache.stale(dict.fromkeys(names), narrowed[2])
return out
"""몸은 `M01_MasterData_Store_Save`(826-2 — 기억에서 전 · 후 · 바뀐 것만 검사)."""
from M01_MasterData import M01_MasterData_Store_Save as saver
return saver.save(batch)