From badbfc65612575160b85f5ad9ab9699273090e5f Mon Sep 17 00:00:00 2001 From: umsangdon Date: Thu, 12 Nov 2026 19:49:31 +0900 Subject: [PATCH] =?UTF-8?q?perf(826-3-1):=20=EB=8B=A8=EA=B0=80=EC=82=B0?= =?UTF-8?q?=EC=B6=9C=20=C2=B7=20=EC=9D=BC=EC=9C=84=EB=8C=80=EA=B0=80=20?= =?UTF-8?q?=C2=B7=20=EC=82=AC=EC=A0=84=EC=A1=B0=ED=95=A9=20=EC=82=AC?= =?UTF-8?q?=EB=B3=B8=20=3D=20=EC=85=88=EC=9D=B4=20=EC=8B=A4=EC=A0=9C?= =?UTF-8?q?=EB=A1=9C=20=EC=9D=BD=EC=9D=80=20=EC=A4=84=EB=A7=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 사본 셈 색인(Copy.get)이 내준 줄 키를 사본 줄 묶음별로 적음(skc.READS) - 저장 관문(Store._server — SK · 일위대가 · 사전조합 · 층 복사 · 열 복사)에서 문서 사본(사본 · 수식 값 몫)을 이 문서 셈이 읽은 줄만 남김 · 읽었는데 사본 없는 줄 = 지금 M01 값으로 더함(M02_Copy_Slim 새 파일) - 파일 꼴 그대로 · 프로젝트 층 그대로 · 저장된 파일은 다음 저장 때 줄어듦 - 사용자 층 사본 68 건 전수: 금액 옛 코드와 같음 · M01 값 두 배 흉내 뒤에도 그대로 · SK 58.4 MB → 3.7 MB · 일위대가 9.2 MB → 1.7 MB Co-Authored-By: Claude Opus 5.5 --- M02_MasterTemplete/M02_Copy_Slim.py | 99 +++++++++++++++++++ .../M02_MasterTemplete_Store.py | 8 +- M02_MasterTemplete/M02_Sangeun_Copy.py | 14 ++- 3 files changed, 117 insertions(+), 4 deletions(-) create mode 100644 M02_MasterTemplete/M02_Copy_Slim.py diff --git a/M02_MasterTemplete/M02_Copy_Slim.py b/M02_MasterTemplete/M02_Copy_Slim.py new file mode 100644 index 000000000..94f2a9572 --- /dev/null +++ b/M02_MasterTemplete/M02_Copy_Slim.py @@ -0,0 +1,99 @@ +"""사본 = 셈이 실제로 읽은 줄만(PLAN 826-3 · 요구 806 · 808 · 조사 804 5 절) — 서버 단독. + +[저장] 관문(`M02_MasterTemplete_Store._server` — SK · 일위대가 · 사전조합 · 층 복사 · 열 복사)에서: + `saved` 동안 사본 셈 색인(`skc.Copy.get`)이 내준 줄 키를 그 색인의 사본 줄 묶음별로 적음 + (`skc.READS`) → 문서 사본(`사본` · `수식` 값 몫)을 이 문서 셈이 읽은 줄만 남김 · 읽었는데 + 사본에 없는 줄(옵션 · 기계 · 지역을 바꿔 새로 읽은 줄) = 지금 M01(+ 층 내 기계) 값으로 더함. +이 문서 셈 = 사본 줄 묶음이 이 문서 사본 안에 드는 색인(남의 SK · SC 참조 셈 = 제 사본 → 안 셈). +파일 꼴 그대로 — 옛 코드는 사본에 없는 줄을 지금 M01 로 읽음(지금 옵션에서 안 읽는 줄 = 금액 같음). +읽은 줄 없음(셈 안 돎) · 프로젝트 층 = 그대로 · 이미 저장된 파일은 다음 [저장] 때 줄어듦. +""" + +from __future__ import annotations + +from typing import Any, Callable + +from M02_MasterTemplete import M02_Doc_Copy as dc +from M02_MasterTemplete import M02_Item_Prices as prices +from M02_MasterTemplete import M02_MyMachine as mym +from M02_MasterTemplete import M02_Sangeun_Copy as skc + + +def _keys(rows: list[dict]) -> set[str]: + return {str(r.get("키")) for r in rows} + + +def cut(copy: Any, reads: dict, more: Any = ()) -> Any: + """값 사본(`skc` 꼴) → 이 사본 셈이 읽은 줄만 + 읽은 · 사본 없는 줄(지금 값) · + 판은 바뀐 몫만 다시.""" + if not skc.has({skc.KEY: copy}): + return copy + rows = [*copy["로직"], *(copy.get("단가") or [])] + have = _keys(rows) + keys = {k for box, got in reads.items() if box <= have for k in got} | set(more) + if not keys: + return copy # 셈이 이 사본을 안 읽음 — 그대로 + whole = skc.whole_() + add = [ + prices.to_json(whole.index[k[:2]][k]) + for k in sorted(keys - have) + if k in whole.index.get(k[:2], {}) + ] + keep = [r for r in rows if str(r.get("키")) in keys] + add + logic = [r for r in keep if str(r.get("키"))[:2] not in skc.RES] + cost = [r for r in keep if str(r.get("키"))[:2] in skc.RES] + out = {**copy, "로직": logic, "단가": cost} + if logic != copy["로직"]: + out["판"] = skc._fp(logic) + if cost != (copy.get("단가") or []): + out["단가판"] = skc._fp(cost) + return out + + +def cut_doc(doc: Any, reads: dict) -> Any: + """문서의 로직 사본(`사본`) · 수식 사본 값 몫(`수식`)을 줄임 — + 문서 안 내 기계 키는 늘 담음(822-1).""" + if not isinstance(doc, dict): + return doc + more = mym.keys_in({k: v for k, v in doc.items() if k not in (skc.KEY, dc.KEY)}) + out = dict(doc) + if skc.has(doc): + out[skc.KEY] = cut(doc[skc.KEY], reads, more) + if dc.has(doc) and isinstance(doc[dc.KEY].get("로직"), list): + got = doc[dc.KEY] + out[dc.KEY] = {**got, **cut(dc.values_of(got), reads, more)} + return out + + +def _project() -> bool: + from M02_MasterTemplete import M02_Labor_Ref as refs + + return refs.here()[0] == "project" + + +def _record(kind: str, out: Any) -> None: + """저장 길이 이 문서 셈을 안 돌린 몫만 한 번 셈(읽은 줄 적기) — 사전조합 · 수식 길로 고친 SK.""" + if not isinstance(out, dict): + return + if kind == "precombo" and skc.has(out): + from M02_MasterTemplete import M02_Labor_Doc as labor + + labor.calc(out, {}) + elif kind == "sangeun" and dc.has(out) and out[dc.KEY].get("따로"): + from M02_MasterTemplete import M02_Doc_Sk as dsk + + dsk.run(out, out[dc.KEY]) + + +def saved(kind: str, make: Callable[[], Any]) -> Any: + """[저장] 관문 — `make()`(정리 · 셈 · 사본 정함) 동안 읽은 줄을 적고 사본을 그 줄로 줄임.""" + if _project(): + return make() + reads: dict = {} + token = skc.READS.set(reads) + try: + out = make() + _record(kind, out) + finally: + skc.READS.reset(token) + return cut_doc(out, reads) diff --git a/M02_MasterTemplete/M02_MasterTemplete_Store.py b/M02_MasterTemplete/M02_MasterTemplete_Store.py index 561310534..aa1f42a59 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_Store.py +++ b/M02_MasterTemplete/M02_MasterTemplete_Store.py @@ -577,9 +577,11 @@ def _server(kind: str, doc: Any, source: Path | None = None) -> Any: if source is not None and isinstance(doc, dict): doc = refs.carry(doc, refs.where(source)) own.carry(doc, source) # 개인 자재(148-3) — 받는 개인 층 목록에 같은 키로 - if kind == "labor": - return labor.server(doc) - return refs.clean_doc(kind, doc) + from M02_MasterTemplete import M02_Copy_Slim as slim # 사본 = 읽은 줄만(826-3) + + return slim.saved( + kind, lambda: labor.server(doc) if kind == "labor" else refs.clean_doc(kind, doc) + ) return doc diff --git a/M02_MasterTemplete/M02_Sangeun_Copy.py b/M02_MasterTemplete/M02_Sangeun_Copy.py index 950b41bac..923409d86 100644 --- a/M02_MasterTemplete/M02_Sangeun_Copy.py +++ b/M02_MasterTemplete/M02_Sangeun_Copy.py @@ -25,6 +25,7 @@ from __future__ import annotations import hashlib import json +from contextvars import ContextVar from datetime import datetime from decimal import Decimal from typing import Any @@ -39,6 +40,8 @@ JOB = "사본할일" PRICE, REGEN = "단가갱신", "재생성" RES = ("LB", "MT", "EQ", "MO", "FX") # 자원 단가 = 단가 기준 시점 묶음 _MASTER = prices.mf.Master +#: 읽은 줄 적기(826-3 · `M02_Copy_Slim`) — {사본 줄 키 묶음: 그 색인이 내준 줄 키} · 꺼짐 = None +READS: ContextVar[dict | None] = ContextVar("읽은줄", default=None) def whole_() -> Any: @@ -67,11 +70,13 @@ class Copy: """M01 색인 위 사본 줄 — `get` · `logic` · `value` · `label` = 엔진 `Master` 한 벌 (사본 줄 먼저) · 그 밖 칸(자재 색인 · 찾기 답)은 속 M01 것을 읽음.""" - get, label, value, logic = _MASTER.get, _MASTER.label, _MASTER.value, _MASTER.logic + label, value, logic = _MASTER.label, _MASTER.value, _MASTER.logic def __init__(self, whole: Any, rows: list[dict], tag: Any = None) -> None: self._whole, self._tag, self.twice = whole, tag, whole.twice self.index, self.numbers = dict(whole.index), dict(whole.numbers) + mine = isinstance(tag, tuple) and tag[:1] == ("내기계",) # 층 내 기계 바탕 = 안 적음 + self._box = None if mine else frozenset(str(r.get("키")) for r in rows) for row in rows: key = str(row.get("키")) tid = key[:2] @@ -79,6 +84,13 @@ class Copy: if row.get("원문번호"): self._own(self.numbers, whole.numbers, tid)[str(row["원문번호"])] = row + def get(self, ref: str) -> dict: + row = _MASTER.get(self, ref) + seen = READS.get() + if seen is not None and self._box is not None: + seen.setdefault(self._box, set()).add(str(row.get("키"))) + return row + @staticmethod def _own(mine: dict, theirs: dict, tid: str) -> dict: if mine.get(tid) is theirs.get(tid):