Files
Aislo/M01_MasterData/M01_MasterData_Store_Save.py
eomsangdonandClaude Opus 5.5 c7cbef86a0 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>
2026-11-12 19:45:00 +09:00

141 lines
6.4 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""M01 저장 — `M01_MasterData_Store.save` 의 몸(826-2 · 저장 길 줄이기).
전 = 기억(`Store_Cache.loaded`) · 후 = 고친 파일만 얕은 사본(줄 목록만 새로 · 줄은 바꿔 끼움만) —
폴더를 다시 읽지 않음. 검사 = 저장이 흔드는 것만: 로직은 `Store_Cache.narrow` · 틀은 고친 줄만 ·
연결 · 준용은 요소를 고쳤을 때 · 조합은 조합 파일을 고쳤거나 로직 줄이 빠질 때 · 일위대가 · 사전조합
문서(모든 층 파일)는 로직 줄이 빠질 때 그 키만 · 키 겹침 · 대장 대조 · 돌고 도는 참조는 전체(가벼움).
전 검사는 기억 색인(재료 고르기 찾기 기억이 데워져 있음) · 후 색인은 그 기억을 이어받음
(자재품목 줄이 바뀌면 그 줄이 걸리는 찾기만 버림). 저장 뒤 = 쓴 파일만 기억에 갈아 끼움.
"""
from __future__ import annotations
import os
from M01_MasterData import M01_MasterData_Store as store
from M01_MasterData import M01_MasterData_Store_Cache as cache
from M01_MasterData.M01_MasterData_Store_Shape import dump, keep_shape
cm, mk = store.cm, store.mk
mmf = cm.mmf
def _copy(data: dict) -> dict:
"""고칠 사본 — 머리는 그대로 나누고 줄 목록만 새로(`_apply` 는 줄을 바꿔 끼우기만 함)."""
key = store.items_key(data)
return {**data, key: list(data.get(key) or [])}
def _rows(data: dict) -> dict[str, dict]:
return {str(r.get("키")): r for r in data.get(store.items_key(data)) or []}
def _only(name: str, data: dict, keys: set[str]) -> dict:
"""틀 검사 몫 — 줄마다 보는 검사라 고친 줄만(조합 파일은 이름 겹침을 보므로 통째)."""
if name == cm.mcb.FILE:
return data
key = store.items_key(data)
return {**data, key: [r for r in data.get(key) or [] if str(r.get("키")) in keys]}
def _index(files: dict, whole, moved: list[dict]):
"""후 색인 — 전 색인의 찾기 기억을 이어받음. `moved` = 바뀐 자재품목 줄(전 · 후)."""
out = cm.mf.Master(files)
mp = cm.mf.mp
mm = mp.mm
if not moved:
for name in ("_found", "_market", "_by_class"):
if hasattr(whole, name):
setattr(out, name, getattr(whole, name))
return out
found = getattr(whole, "_found", None) or {}
texts = [
(mm.plain(r.get("이름")).replace("×", "x"), mm.plain_spec(r.get("규격")), r) for r in moved
]
def hit(memo_key) -> bool: # `master_pick._search` 의 거름과 같음 — 걸리면 답이 바뀔 수 있음
term, kind, detail = memo_key[:3]
words = mp._words(term)[0]
return any(
all(w in name or w in spec for w in words)
and (not kind or row.get("구분") == kind)
and (not detail or row.get("상세구분") == detail)
for name, spec, row in texts
)
out._found = {k: v for k, v in found.items() if not hit(k)}
return out
def _errors(files, whole, names, book, narrowed, keys, gone) -> list[str]:
out = [x for name in names for x in cm.check_form(name, _only(name, files[name], keys[name]))]
out += cm.check_logics(files, whole, book, narrowed[:2])
if narrowed[2]: # 요소·표를 고쳤으면 연결 · 준용도 봄(로직 좁힘과 따로)
out += cm.check_links(whole)
if cm.mcb.FILE in names or gone:
out += cm.mcb.check_all(files, whole)
return out
def save(batch: list[dict]) -> list[dict]:
with store._LOCK:
stale = [
p.get("file", "") for p in batch if store.version(p.get("file", "")) != p.get("version")
]
if stale:
raise store.StoreError(409, {"stale": stale})
before, whole = store.loaded()
names = list(dict.fromkeys(part["file"] for part in batch))
after = {**before, **{name: _copy(before[name]) for name in names}}
book_path = store.FOLDER / mk.BOOK.name
book = mk.load_book(book_path)
old_book = {"키": dict(book["키"]), "다음": dict(book["다음"])} # `issue` 는 새 키만 더함
issued = dict(book["다음"])
for part in batch:
store._apply(after[part["file"]], part.get("changes") or [], part["file"], book)
mk.finish(book)
keys, gone, moved = {}, set(), []
for name in names:
was, now = _rows(before[name]), _rows(after[name])
keys[name] = set(now) - set(was)
keys[name] |= {
str(c.get("key"))
for p in batch
if p["file"] == name
for c in p.get("changes") or []
if c.get("op") != "add"
}
if after[name].get("그룹") == "로직":
gone |= set(was) - set(now)
moved += [s[k] for k in keys[name] for s in (was, now) if k[:2] == "MT" and k in s]
# 저장이 흔드는 줄만 검사(고친 줄 · 그 줄을 부르던 줄 · 그 요소를 보던 줄) —
# 마스터 전체 검사는 `check_master` 몫
narrowed = cache.narrow(store.FOLDER, batch, before, after)
old = set(_errors(before, whole, names, old_book, narrowed, keys, gone))
fresh = _index(after, whole, moved)
new = _errors(after, fresh, names, book, narrowed, keys, gone)
# 일위대가 · 사전조합 문서가 담은 로직이 빠짐 — 빠지기 전엔 있던 키라 전 검사는 없음
new = [x for x in new + cache.labor_errors(fresh, gone) if x not in old]
if new:
raise store.StoreError(422, {"errors": new})
if book["다음"] != issued:
mk.save_book(book, book_path)
out = [{"file": name, "version": _write(name, before[name], after[name])} for name in names]
# 저장 뒤 — 쓴 파일만 기억에 갈아 끼우고 흔들린 자리를 알려 줘 그 줄만 다시 세게
cache.adopt(store.FOLDER, after, fresh, names, narrowed[2])
return out
def _write(name: str, before: dict, after: dict) -> str:
if name == mmf.NAME and mmf.here(store.FOLDER): # 기계 = 바뀐 대 파일만(826-2)
mmf.write(store.FOLDER, after, before)
return store.version(name)
path = store.FOLDER / name
old_text = path.read_bytes().decode("utf-8")
key = store.items_key(after)
text = keep_shape(old_text, before, after, key) or dump(after)
tmp = store.FOLDER / f".{name}.tmp"
tmp.write_text(text, encoding="utf-8", newline="\n")
os.replace(tmp, path)
return store.version_of(text.encode("utf-8"))