- 기계.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>
141 lines
6.4 KiB
Python
141 lines
6.4 KiB
Python
"""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"))
|