Merge branch 'sub_laptop_3' of C:/Aislo_wt/sub_laptop_3 into main_laptop_1
This commit is contained in:
@@ -188,7 +188,7 @@ export function createStackScreen(opt: StackScreenOptions) {
|
||||
ctl?.abort();
|
||||
ctl = new AbortController();
|
||||
const mine = ++seq;
|
||||
void call<{ 연결: DocRecord }>(
|
||||
void call<{ 연결: DocRecord; 사본?: CopyMark }>(
|
||||
"/preview",
|
||||
cp ? { 문서: doc, 끊음: cut } : { 문서: key ? doc : { ...doc, 키: "_새" }, 입력: usable() },
|
||||
ctl.signal,
|
||||
@@ -197,6 +197,7 @@ export function createStackScreen(opt: StackScreenOptions) {
|
||||
if (mine !== seq) return;
|
||||
cache.merge(got.연결);
|
||||
cache.merge({ 문서: cut });
|
||||
if (got.사본) mark = got.사본; // 새로 읽은 줄 「사본 없는 줄 n」(826-3)
|
||||
recalc();
|
||||
draw();
|
||||
})
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
"""사본 = 셈이 실제로 읽은 줄만(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 _rows(copy: dict) -> list[dict]:
|
||||
return [*copy["로직"], *(copy.get("단가") or [])]
|
||||
|
||||
|
||||
def cut(copy: Any, keys: set[str], pool: dict | None = None) -> Any:
|
||||
"""값 사본(`skc` 꼴) → `keys` 줄만 · 사본 없는 줄 = `pool`(다른 칸 사본) › 지금 M01(+ 내 기계) ·
|
||||
판은 바뀐 몫만 다시."""
|
||||
if not skc.has({skc.KEY: copy}) or not keys:
|
||||
return copy
|
||||
rows = _rows(copy)
|
||||
have = _keys(rows)
|
||||
whole = skc.whole_()
|
||||
add = [
|
||||
(pool or {}).get(k) or prices.to_json(whole.index[k[:2]][k])
|
||||
for k in sorted(keys - have)
|
||||
if k in (pool or {}) or 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
|
||||
old = doc[skc.KEY] if skc.has(doc) else None
|
||||
form = doc[dc.KEY] if dc.has(doc) and isinstance(doc[dc.KEY].get("로직"), list) else None
|
||||
mine = {str(r.get("키")): r for c in (form, old) if c for r in _rows(c)}
|
||||
keys = {k for box, got in reads.items() if box <= mine.keys() for k in got}
|
||||
if not keys:
|
||||
return doc # 셈이 이 사본을 안 읽음 — 그대로
|
||||
keys |= set(mym.keys_in({k: v for k, v in doc.items() if k not in (skc.KEY, dc.KEY)}))
|
||||
out = dict(doc)
|
||||
if form:
|
||||
out[dc.KEY] = {**form, **cut(dc.values_of(form), keys)}
|
||||
if old:
|
||||
pool = {str(r.get("키")): r for r in _rows(out[dc.KEY])} if form else None
|
||||
out[skc.KEY] = cut(old, keys, pool)
|
||||
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)
|
||||
@@ -165,14 +165,7 @@ def run(sk: dict, copy: dict, master: Any = None) -> dict:
|
||||
|
||||
# ── 저장 첫머리(옛 길 `clean_doc` 끝) ──────────────────────────────────
|
||||
def stored(key: Any) -> dict | None:
|
||||
from M02_MasterTemplete import M02_MasterTemplete_Store as store
|
||||
|
||||
if not sd.KEY.match(str(key or "")):
|
||||
return None
|
||||
try:
|
||||
return json.loads((store.root() / sd.KIND / f"{key}.json").read_bytes())
|
||||
except (OSError, ValueError):
|
||||
return None
|
||||
return skc.stored_doc(key) # 한 저장 안 한 번 읽음(826-3)
|
||||
|
||||
|
||||
def norm(copy: dict) -> dict:
|
||||
@@ -204,7 +197,8 @@ def settle(out: dict, whole: Any, job: Any, raw: Any) -> dict:
|
||||
if dim.is_imported(out):
|
||||
return {**out, KEY: norm(have)}
|
||||
copy = dc.refresh(have)[0] if job == skc.PRICE else have
|
||||
return {**out, KEY: norm(gap.fill(copy))} # 사본 빈틈 = 지금 값으로 박음(822-1)
|
||||
slim = skc.READS.get() is not None # 저장 관문이 셈 한 번으로 박음(826-3)
|
||||
return {**out, KEY: norm(copy if slim else gap.fill(copy))} # 빈틈 = 지금 값(822-1)
|
||||
# 빈 SK(바탕 · 줄 없음) = 빈 문서(요구서_664 0 장) · 수식 길로 고친 빈 SK = 위 따로(804-4)
|
||||
if not skc.has(out) and not dim.is_imported(out):
|
||||
empty = seed(out)
|
||||
@@ -214,7 +208,9 @@ def settle(out: dict, whole: Any, job: Any, raw: Any) -> dict:
|
||||
calls = {} if job == skc.REGEN else (have or {}).get("부름") or {}
|
||||
first = {**(dc.values_of(out[skc.KEY]) if skc.has(out) else {}), "부름": calls}
|
||||
copy = copy_of({**out, KEY: first}, box[-1] if box else None)
|
||||
if copy and skc.has(out) and (keys := gap.gaps(copy)): # 파생 — 옛 로직 사본에 박음(822-1)
|
||||
if (
|
||||
copy and skc.has(out) and skc.READS.get() is None and (keys := gap.gaps(copy))
|
||||
): # 파생 — 옛 로직 사본에 박음(822-1)
|
||||
out = {**out, skc.KEY: skc.grow(out[skc.KEY], skc.snap([], keys))}
|
||||
copy = {**copy, **dc.values_of(out[skc.KEY])}
|
||||
return {**out, KEY: norm({k: v for k, v in (copy or first).items() if k != "따로"})}
|
||||
|
||||
@@ -145,8 +145,11 @@ def preview(key: str, body: dict) -> dict[str, Any]:
|
||||
mine = _basis(doc, body)
|
||||
before = current(doc)
|
||||
job = body.get("할일") or None
|
||||
after = edited(current(doc, job) if job else before, body, key, doc)
|
||||
was = current(doc, job) if job else before
|
||||
after = edited(was, body, key, doc)
|
||||
out = answer(after, mine)
|
||||
if not dim.is_imported(doc): # 고침으로 새로 읽은 줄 = 지금 값으로 셈 · 저장하면 굳음(826-3)
|
||||
out["사본"]["사본없음"] += len(_rows(after) - _rows(was))
|
||||
out |= {"전": dc.money(dsk.run(doc, before)), "후": dc.money(out["셈"])}
|
||||
if job == skc.PRICE and not dim.is_imported(doc):
|
||||
out["바뀐자원"] = dc.refresh(before)[1]
|
||||
@@ -158,6 +161,10 @@ def preview(key: str, body: dict) -> dict[str, Any]:
|
||||
return json.loads(json.dumps(dc.ml._norm(out), ensure_ascii=False, default=str))
|
||||
|
||||
|
||||
def _rows(copy: dict) -> set[str]:
|
||||
return {str(r.get("키")) for r in [*(copy.get("로직") or []), *(copy.get("단가") or [])]}
|
||||
|
||||
|
||||
def _basis(doc: dict, body: dict) -> dict:
|
||||
"""가져온 식 단가기준 바꿈(807-1 · 몸 `단가기준` 엑셀 | 우리) — 그 밖 SK · 빈 값 = 그대로."""
|
||||
want = body.get("단가기준")
|
||||
|
||||
@@ -99,7 +99,7 @@ def settle(doc: Any, save: bool = False) -> Any:
|
||||
elif job == PRICE and copy:
|
||||
copy = skc.refresh(copy, skc.whole_())[0]
|
||||
if save and copy:
|
||||
copy = skc.grow(copy, stamp(out))
|
||||
copy = skc.grow(copy, stamp(out)) if skc.READS.get() is None else copy # 826-3
|
||||
if not copy:
|
||||
return out
|
||||
got = {**out, KEY: copy}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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):
|
||||
@@ -237,15 +249,29 @@ def taken(raw: Any) -> dict | None:
|
||||
return {**keep, "로직": logic, "판": _fp(logic), "단가": cost, "단가판": _fp(cost)}
|
||||
|
||||
|
||||
def _stored(key: Any) -> dict | None:
|
||||
_FILE: list = [(None, None)] # (자국, SK 파일) — 한 저장 안 같은 파일 되풀이 읽기 막음(826-3)
|
||||
|
||||
|
||||
def stored_doc(key: Any) -> dict | None:
|
||||
"""저장된 SK 파일(읽기만 — 고치지 말 것) · 파일 자국(경로 · 고친 때 · 크기)이 같으면 앞 것."""
|
||||
from M02_MasterTemplete import M02_MasterTemplete_Store as store
|
||||
|
||||
if not sd.KEY.match(str(key or "")):
|
||||
return None
|
||||
file = store.root() / sd.KIND / f"{key}.json"
|
||||
try:
|
||||
got = json.loads((store.root() / sd.KIND / f"{key}.json").read_bytes())
|
||||
st = file.stat()
|
||||
mark = (str(file), st.st_mtime_ns, st.st_size)
|
||||
have = _FILE[0]
|
||||
if have[0] != mark:
|
||||
have = _FILE[0] = (mark, json.loads(file.read_bytes()))
|
||||
except (OSError, ValueError):
|
||||
return None
|
||||
return have[1]
|
||||
|
||||
|
||||
def _stored(key: Any) -> dict | None:
|
||||
got = stored_doc(key)
|
||||
return got[KEY] if has(got) else None
|
||||
|
||||
|
||||
@@ -264,7 +290,7 @@ def settle(doc: Any, save: bool = False) -> Any:
|
||||
copy = refresh(copy, whole_())[0]
|
||||
if copy and any(isinstance(x, dict) and x.get("다시잇기") for x in out.get("더한줄") or []):
|
||||
copy = rejoin(copy, out["더한줄"])
|
||||
if save and copy:
|
||||
if save and copy and READS.get() is None: # 읽은 줄만(826-3) = 저장 관문이 더함
|
||||
copy = grow(copy, stamp(out))
|
||||
out = _formula_job(out, job)
|
||||
return {**out, KEY: copy} if copy else out
|
||||
|
||||
@@ -0,0 +1,247 @@
|
||||
# ruff: noqa: F811 — 임시 층 fixture(world)를 test_m02_labor_ref 에서 받아 씀
|
||||
"""826-3 사본 = 셈이 실제로 읽은 줄만(요구 806 · 808) · SK 저장 길 한 번씩.
|
||||
|
||||
지킴: 사본 줄 = 읽은 줄(닫힌 묶음 수백 줄 → 열 줄 안팎) · 금액 = 닫힌 묶음 사본과 같음 ·
|
||||
M01 값 바뀌어도 그대로 · 옵션(기계) 바꿈 = 미리 셈 「사본없음 n」 + 지금 M01 값 → [저장] 때 박음
|
||||
(남은 줄 = 사본 값 그대로) · [단가 갱신] · [재생성] · 층 복사 · 일위대가 · 사전조합 사본도 ·
|
||||
옛 큰 사본 SK 그대로 읽힘 · 다음 [저장] 때 줄어듦 · 저장 한 번에 M01 모으기 0 · SK 파일 읽기 한 번.
|
||||
M01 바꿈 = 메모리 색인 사본(정본 파일 안 고침) · 층 = 임시 storage.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from M01_MasterData import M01_MasterData_Store as m01
|
||||
from M02_MasterTemplete import M02_Doc_Sk_Path as path
|
||||
from M02_MasterTemplete import M02_Item_Prices as prices
|
||||
from M02_MasterTemplete import M02_Labor_Ref as refs
|
||||
from M02_MasterTemplete import M02_MasterTemplete_Store as store
|
||||
from M02_MasterTemplete import M02_Sangeun_Calc as calc
|
||||
from M02_MasterTemplete import M02_Sangeun_Copy as skc
|
||||
from resources.tester.test_795_2_sc_copy import _labor, _read, _sc, _unit
|
||||
from resources.tester.test_822_1_copy_gap import (
|
||||
DIG,
|
||||
DIG2_NO,
|
||||
MACHINE,
|
||||
_at,
|
||||
_fuel_and_crew,
|
||||
_move,
|
||||
_price,
|
||||
_sk,
|
||||
_stack,
|
||||
_sum,
|
||||
)
|
||||
from resources.tester.test_m02_labor_ref import world # noqa: F401 — 같은 임시 층
|
||||
|
||||
DIG_BASE = "GC000088" # 터파기(기계) 보통토사 — 「기계」 고르기 후보를 닫힌 묶음이 다 담음
|
||||
|
||||
|
||||
def _file(folder, key) -> dict:
|
||||
return json.loads((folder / "sangeun" / f"{key}.json").read_bytes())
|
||||
|
||||
|
||||
def _keys(copy_: dict) -> list[str]:
|
||||
return [str(r.get("키")) for r in [*copy_["로직"], *(copy_.get("단가") or [])]]
|
||||
|
||||
|
||||
def _closed(doc: dict) -> int:
|
||||
"""옛 길(후보 전부 닫힌 묶음) 줄 수."""
|
||||
got = prices.collect([str(doc["바탕"]["로직"])])
|
||||
return sum(len(d[m01.items_key(d)]) for k, d in got.items() if not k.startswith("_"))
|
||||
|
||||
|
||||
def test_base_logic_read_rows_only(world):
|
||||
"""견본(터파기 기계) — 닫힌 묶음 → 읽은 줄만 · 자원 줄 한 자리 ·
|
||||
금액 = 닫힌 묶음 사본."""
|
||||
me = world["personal"]
|
||||
key = _sk(me, 바탕={"로직": DIG_BASE}, 단위="㎥")
|
||||
doc = _file(me, key)
|
||||
have = doc[skc.KEY]
|
||||
assert _closed(doc) >= 3 * len(_keys(have))
|
||||
assert len(_keys(have)) <= 12 and len(have["단가"]) < 10
|
||||
assert _keys(have) == _keys(doc["수식"]) # 두 자리 같은 줄(꼴 그대로)
|
||||
full = {**doc, skc.KEY: skc.stamp(doc)} # 옛 길 사본(후보 전부)
|
||||
assert len(_keys(full[skc.KEY])) >= 3 * len(_keys(have))
|
||||
with store.at(me):
|
||||
old = calc.calc_doc(full, None, False)["기준"]
|
||||
new = calc.calc_doc(doc, None, False)["기준"]
|
||||
assert new == old and new["계"] > 0
|
||||
assert _sum(me, key) == new["계"]
|
||||
|
||||
|
||||
def test_m01_change_kept_then_price(world, monkeypatch):
|
||||
"""만든 뒤 M01(기계 · 유가 · 노임) 바뀜 → 금액 그대로 + 단가새값 →
|
||||
[단가 갱신] → 반영 · 그래도 작음."""
|
||||
me = world["personal"]
|
||||
key = _sk(me, 바탕={"로직": DIG_BASE}, 단위="㎥")
|
||||
was = _sum(me, key)
|
||||
read = set(_keys(_file(me, key)[skc.KEY]))
|
||||
moved = [k for k in read if k[:2] in ("EQ", "LB", "MO")]
|
||||
assert moved
|
||||
_move(monkeypatch, *moved)
|
||||
assert _sum(me, key) == was
|
||||
assert _at(me, path.read, key)["사본"]["단가새값"] is True
|
||||
got = _price(me, key)
|
||||
assert set(got["바뀐"]) == set(moved) and got["후"] > was
|
||||
assert set(_keys(_file(me, key)[skc.KEY])) == read
|
||||
|
||||
|
||||
def test_machine_swap_preview_then_save(world, monkeypatch):
|
||||
"""기계 바꿈(옵션) — 미리 셈 = 새 기계 지금 M01 값 + 「사본없음 n」 · [저장] = 새 줄만 박음 ·
|
||||
남은 줄(유가 · 노임) = 사본 값 그대로 · 안 읽는 옛 기계 줄은 빠짐."""
|
||||
me = world["personal"]
|
||||
key = _sk(me)
|
||||
_stack(me, key, [("기계", MACHINE, "묶음")])
|
||||
first = _file(me, key) # 줄 쌓기(수식 길) = 수식 사본 값 몫
|
||||
before = set(_keys(first["수식"]))
|
||||
assert DIG in before
|
||||
fuel, crew = _fuel_and_crew()
|
||||
w = m01.loaded()[1]
|
||||
dig2 = w.numbers["EQ"][DIG2_NO]["키"]
|
||||
assert dig2 not in before
|
||||
files, live = m01.loaded()
|
||||
_move(monkeypatch, dig2) # 바꾸는 때의 M01 = 새 기계 값 두 배
|
||||
fake_dig2 = m01.loaded()[1]
|
||||
_move(monkeypatch, fuel, crew) # 사본에 든 유가 · 노임도 바뀜(사본 값이 이겨야 함)
|
||||
got = _at(me, path.read, key)
|
||||
doc = copy.deepcopy(got["문서"])
|
||||
for var in doc["변수"]:
|
||||
if (var.get("연결") or {}).get("갈래") == "단가산출":
|
||||
var["연결"]["입력"] = {"기계": f"'{DIG2_NO}'"}
|
||||
look = _at(me, path.preview, key, {"문서": doc})
|
||||
assert look["사본"]["사본없음"] > 0 # 저장하면 지금 값으로 굳음
|
||||
_at(me, path.save, key, {"문서": doc, "판": got["판"]})
|
||||
after = _file(me, key)
|
||||
now = set(_keys(after["수식"]))
|
||||
assert dig2 in now and DIG not in now and {fuel, crew} <= now
|
||||
old_rows = {str(r["키"]): r for r in first["수식"]["단가"]}
|
||||
new_rows = {str(r["키"]): r for r in after["수식"]["단가"]}
|
||||
assert new_rows[fuel] == old_rows[fuel] and new_rows[crew] == old_rows[crew]
|
||||
amount = _sum(me, key)
|
||||
assert amount == look["셈"]["계"]
|
||||
# 기준 = 새 기계만 두 배인 M01 로 처음 만든 같은 SK(유가 · 노임 = 처음 값)
|
||||
monkeypatch.setattr(m01, "loaded", lambda: (files, fake_dig2))
|
||||
ref = _sk(me)
|
||||
_stack(me, ref, [("기계", {**MACHINE, "입력": {"기계": f"'{DIG2_NO}'"}}, "묶음")])
|
||||
assert _sum(me, ref) == amount
|
||||
monkeypatch.setattr(m01, "loaded", lambda: (files, live))
|
||||
assert _sum(me, key) == amount # M01 되돌아가도 그대로
|
||||
|
||||
|
||||
def test_regen_slim(world, monkeypatch):
|
||||
"""[재생성] — 지금 M01 로 다시 찍어도 읽은 줄만 · 그 뒤 M01 바뀌어도 그대로."""
|
||||
me = world["personal"]
|
||||
key = _sk(me, 바탕={"로직": DIG_BASE}, 단위="㎥")
|
||||
read = [k for k in _keys(_file(me, key)[skc.KEY]) if k[:2] in ("EQ", "MO")]
|
||||
_move(monkeypatch, *read)
|
||||
판 = _at(me, path.read, key)["판"]
|
||||
_at(me, path.save, key, {"할일": skc.REGEN, "판": 판})
|
||||
after = _file(me, key)[skc.KEY]
|
||||
assert len(_keys(after)) <= 12
|
||||
was = _sum(me, key)
|
||||
_move(monkeypatch, *read)
|
||||
assert _sum(me, key) == was
|
||||
|
||||
|
||||
def test_layer_copy_slim(world, monkeypatch):
|
||||
"""층 복사(개인 → 회사) — 받은 사본째 · 읽은 줄만 · 원본 M01 바뀐 뒤 복사해도 그 값."""
|
||||
me, co = world["personal"], world["company"]
|
||||
key = _sk(me, 바탕={"로직": DIG_BASE}, 단위="㎥")
|
||||
was = _sum(me, key)
|
||||
_move(monkeypatch, DIG, *[k for k in _keys(_file(me, key)[skc.KEY]) if k[:2] == "EQ"])
|
||||
ref = {"층": "personal", "주인": "7/42", "키": key}
|
||||
with store.at(co):
|
||||
doc = refs.read_ref(ref)
|
||||
new = refs.create("sangeun", refs.carry(refs.origin_mark(doc, ref), ("personal", "7/42")))
|
||||
assert _sum(co, new["이름"]) == was
|
||||
assert _keys(_file(co, new["이름"])[skc.KEY]) == _keys(_file(me, key)[skc.KEY])
|
||||
|
||||
|
||||
def test_labor_and_combo_slim(world, monkeypatch):
|
||||
"""일위대가 · 사전조합 사본 — 닫힌 묶음보다 작음 · 금액 그대로 · M01 바뀌어도 그대로."""
|
||||
me = world["personal"]
|
||||
lines = [{"로직": DIG_BASE, "수량": {"손": 1}}]
|
||||
lab = _labor(me, lines)
|
||||
sc = _sc(me, lines)
|
||||
full = prices.collect([DIG_BASE])
|
||||
closed = sum(len(d[m01.items_key(d)]) for k, d in full.items() if not k.startswith("_"))
|
||||
for doc in (lab, _read(me, "precombo", sc)):
|
||||
assert len(_keys(doc[skc.KEY])) * 3 <= closed
|
||||
assert _keys(doc[skc.KEY]) == _keys(doc["수식"])
|
||||
unit = _unit(me, sc)
|
||||
assert unit > 0
|
||||
_move(monkeypatch, *[k for k in _keys(lab[skc.KEY]) if k[:2] in ("EQ", "LB", "MO")])
|
||||
assert _unit(me, sc) == unit
|
||||
|
||||
|
||||
def test_old_big_copy_reads_then_slims(world):
|
||||
"""옛 큰 사본(후보 전부) SK — 그대로 읽힘(같은 금액 · 열기 = 안 고쳐 씀) ·
|
||||
다음 [저장] 때 줄어듦."""
|
||||
me = world["personal"]
|
||||
key = _sk(me, 바탕={"로직": DIG_BASE}, 단위="㎥")
|
||||
was = _sum(me, key)
|
||||
file = me / "sangeun" / f"{key}.json"
|
||||
raw = json.loads(file.read_bytes())
|
||||
big = skc.stamp(raw)
|
||||
raw[skc.KEY] = big
|
||||
raw["수식"] = {**raw["수식"], **{k: big[k] for k in ("판", "로직", "단가판", "단가")}}
|
||||
file.write_text(json.dumps(raw, ensure_ascii=False), encoding="utf-8")
|
||||
before = file.read_bytes()
|
||||
assert _sum(me, key) == was
|
||||
assert file.read_bytes() == before
|
||||
with store.at(me):
|
||||
got = store.read("sangeun", key)
|
||||
store.write("sangeun", key, got["판"], got["문서"])
|
||||
assert len(_keys(_file(me, key)[skc.KEY])) * 3 <= len(big["로직"]) + len(big["단가"])
|
||||
assert _sum(me, key) == was
|
||||
|
||||
|
||||
def test_save_path_once(world, monkeypatch):
|
||||
"""SK 다시 저장 한 번 — M01 모으기 0 · SK 파일 읽기(셈 쪽) 한 번 · 옛 셈 한 번."""
|
||||
me = world["personal"]
|
||||
key = _sk(me, 바탕={"로직": DIG_BASE}, 단위="㎥")
|
||||
count = {"모으기": 0, "읽기": 0, "셈": 0}
|
||||
collect, run, read_bytes = prices.collect, calc.run, Path.read_bytes
|
||||
|
||||
def counted(name, fn):
|
||||
def wrap(*a, **k):
|
||||
count[name] += 1
|
||||
return fn(*a, **k)
|
||||
|
||||
return wrap
|
||||
|
||||
def reading(self):
|
||||
if self.name == f"{key}.json":
|
||||
count["읽기"] += 1
|
||||
return read_bytes(self)
|
||||
|
||||
with store.at(me):
|
||||
got = store.read("sangeun", key)
|
||||
monkeypatch.setattr(prices, "collect", counted("모으기", collect))
|
||||
monkeypatch.setattr(calc, "run", counted("셈", run))
|
||||
monkeypatch.setattr(Path, "read_bytes", reading)
|
||||
store.write("sangeun", key, got["판"], got["문서"])
|
||||
# 읽기 = 사본 정함 한 번 + 판 맞춤(쓰기 전) + 새 판(쓰기 뒤)
|
||||
assert count == {"모으기": 0, "읽기": 3, "셈": 1}
|
||||
|
||||
|
||||
def test_doc_path_save_keeps_both_slots(world):
|
||||
"""수식 길 저장(옵션 Type · 지역 바꿈) — 사본 · 수식 두 칸 같은 줄 · 새로 읽은 줄 더함 ·
|
||||
금액 = 미리 셈 = 같은 옵션으로 처음 만든 것."""
|
||||
me = world["personal"]
|
||||
key = _sk(me, 바탕={"로직": DIG_BASE}, 단위="㎥")
|
||||
got = _at(me, path.read, key)
|
||||
doc = copy.deepcopy(got["문서"])
|
||||
for var in doc["변수"]:
|
||||
if var.get("열쇠") in ("Type", "지역"):
|
||||
var["연결"]["값"] = {"Type": "TypeⅢ", "지역": "경북"}[var["열쇠"]]
|
||||
look = _at(me, path.preview, key, {"문서": doc})
|
||||
assert look["사본"]["사본없음"] > 0
|
||||
_at(me, path.save, key, {"문서": doc, "판": got["판"]})
|
||||
after = _file(me, key)
|
||||
assert set(_keys(after[skc.KEY])) == set(_keys(after["수식"]))
|
||||
assert len(_keys(after[skc.KEY])) >= 6
|
||||
assert _sum(me, key) == look["셈"]["계"] != got["셈"]["계"]
|
||||
Reference in New Issue
Block a user