Files
Aislo/resources/tester/test_822_1_copy_gap.py
T
eomsangdonandClaude Opus 5.5 cbe9a3d2ee fix(822-1): 사본 빈틈 막기 — 셈이 읽은 M01 줄을 저장 때 값 사본에 박음 · 가져온 식 우리 = 사본 · 빈틈 표지
- 새 M02_Doc_Gap: 문서 셈이 읽은 요소 · 표 줄 가운데 값 사본에 없는 것(gaps) → 지금 M01 닫힌 묶음으로 더함(fill)
- 수식 길(M02_Doc_Sk_Path.edited): 줄 쌓기 품셈 기계 · 바꿔 끼운 기계 · 자원 줄 · 단가산출 참조 · 내 기계 모두 저장 때 박음(옛 _mine 대신)
- 저장 첫머리(M02_Doc_Sk.settle): 따로 = [저장] · [단가 갱신] 때 빈틈 메움 · 파생 = 옛 로직 사본에 더함
- 가져온 식 단가기준 우리(M02_Doc_Import): 셈 · 연결 기록 = 로직 사본 위(지금 M01 직접 읽던 빈틈) · 엑셀 기준 그대로
- 읽기 답 사본 표지 「사본없음」 n · 화면 배지 「사본 없는 줄 n」(지금 값으로 셈 · 저장하면 굳음) · 일괄 고쳐 쓰지 않음
- 시험 test_822_1_copy_gap 14 · storage SK 122 고치기 전후 금액 같음 · 빈틈 0

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-12 17:21:13 +09:00

279 lines
12 KiB
Python

# ruff: noqa: F811 — 임시 층 fixture(world)를 test_m02_labor_ref 에서 받아 씀
"""822-1 사본 빈틈 막기(요구 797-1) — 길마다: 만들기 → [저장] → M01 값(임시 · 메모리 덮개)을
기계 가격 · 노임 · 유가 · 자재 값으로 바꿈 → 금액 그대로 + 「단가새값」 → [단가 갱신] → 반영.
길 = 수식 길 줄 쌓기(품셈 기계 · 바꿔 끼운 기계 · 자원 줄 · 단가산출 참조) ·
옛 로직 길(더한줄 로직 · 재료 · 인력) · 가져온 식(단가기준 우리) · 층 복사 · [재생성] ·
빈틈 SK 표시(`사본없음`).
M01 바꿈 = `skc.Copy` 아닌 진짜 색인 사본(`dc.base` 가 못 벗김) · 정본 파일 안 고침.
"""
from __future__ import annotations
import copy
import json
import pytest
from M01_MasterData import M01_MasterData_Store as m01
from M02_MasterTemplete import M02_Doc_Sk_Path as path
from M02_MasterTemplete import M02_Labor_Ref as refs
from M02_MasterTemplete import M02_MasterTemplete_Store as store
from M02_MasterTemplete import M02_Sangeun_Copy as skc
from resources.tester.test_m02_labor_ref import world # noqa: F401 — 같은 임시 층
DIG, DIG_NO, DIG_LOGIC = "EQ000022", "0201-0100", "GC000267" # 굴착기(무한궤도) 1.0㎥
DIG2_NO = "0201-0070" # 바꿔 끼울 굴착기
MAN = "LB000002" # 보통인부
PCT = "GC001109" # 금속기와 해체(인력 둘 + 노무비 3 %)
def _live():
return m01.loaded()[1]
def _move(monkeypatch, *keys: str) -> None:
"""M01 값 두 배 — 키마다 값 칸(기계 = 가격 · 자재 = 물가 칸) · 진짜 색인 사본(Copy 아님) ·
수식 문서 폴더 그대로."""
files, live = m01.loaded()
fake = copy.copy(live)
fake.index, fake.numbers = dict(live.index), dict(live.numbers)
for key in keys:
tid = key[:2]
row = copy.deepcopy(live.index[tid][key])
if tid == "EQ":
row["값"]["가격"] *= 2
elif tid == "MT":
for k in ("물가자료", "유통물가", "물가정보", "거래가격"):
row[k] = row[k] * 2 if row.get(k) else row.get(k)
else:
row["값"] *= 2
fake.index[tid] = {**fake.index[tid], key: row}
if row.get("원문번호"):
fake.numbers[tid] = {**fake.numbers[tid], str(row["원문번호"]): row}
monkeypatch.setattr(m01, "loaded", lambda: (files, fake))
def _at(folder, fn, *args):
with store.at(folder):
return fn(*args)
def _sk(folder, **body) -> str:
body = {"이름": "sub1임시 빈틈", "단위": "시간", "더한줄": [], **body}
return _at(folder, lambda: refs.create("sangeun", body)["이름"])
def _var(sym: str, link: dict, name: str = "") -> dict:
return {"기호": "P", "아래": sym, "위": "", "이름": name or sym, "단위": "", "연결": link}
def _line(sym: str, part: str = "경비") -> dict:
return {"이름": sym, "단위": "hr", "비목": part, "수량": "1", "단가": f"P_{sym}"}
def _stack(folder, key, rows: list[tuple[str, dict, str]]) -> None:
"""줄 쌓기 — 지금 문서에 (기호, 연결, 비목) 줄을 더해 수식 길로 저장."""
got = _at(folder, path.read, key)
doc = copy.deepcopy(got["문서"])
doc.setdefault("변수", []).extend(_var(s, link) for s, link, _ in rows)
doc.setdefault("금액", []).extend(_line(s, part) for s, _, part in rows)
_at(folder, path.save, key, {"문서": doc, "판": got["판"]})
def _sum(folder, key):
return _at(folder, path.read, key)["셈"]["계"]
def _price(folder, key) -> dict:
"""[단가 갱신] 저장 — 바뀐 자원 키 · 뒤 금액."""
look = _at(folder, path.preview, key, {"할일": skc.PRICE})
판 = _at(folder, path.read, key)["판"]
_at(folder, path.save, key, {"할일": skc.PRICE, "판": 판})
return {"바뀐": sorted(x["키"] for x in look["바뀐자원"]), "후": _sum(folder, key)}
MACHINE = {"갈래": "단가산출", "키": DIG_LOGIC, "입력": {"기계": f"'{DIG_NO}'"}}
def _fuel_and_crew(no: str = DIG_NO) -> tuple[str, str]:
"""기계 시간당 식이 읽는 유가(MO) · 조종원 노임(LB) 키."""
w = _live()
row = w.numbers["EQ"][no]
fuel = w.numbers["MO"][f"{row['값']['주연료종류']}:전국평균"]["키"]
crew = w.numbers["LB"][str(row["값"]["조종원직종"])]["키"]
return fuel, crew
# ── 수식 길 줄 쌓기 ───────────────────────────────────────────────────
@pytest.mark.parametrize("what", ["가격", "유가", "노임"])
def test_stack_m01_machine(world, monkeypatch, what):
"""품셈 기계(굴착기 1.0㎥) 줄 쌓기 → 저장 → 기계 가격 · 유가 · 조종원 노임 바뀜 →
금액 그대로 + 단가새값 → [단가 갱신] → 반영."""
me = world["personal"]
key = _sk(me)
_stack(me, key, [("기계", MACHINE, "묶음")])
was = _sum(me, key)
assert was > 0
fuel, crew = _fuel_and_crew()
moved = {"가격": DIG, "유가": fuel, "노임": crew}[what]
_move(monkeypatch, moved)
now = _at(me, path.read, key)
assert now["셈"]["계"] == was and now["사본"]["단가새값"] is True
got = _price(me, key)
assert got["바뀐"] == [moved] and got["후"] > was
def test_stack_swapped_machine(world, monkeypatch):
"""바꿔 끼운 기계 — 기계 입력을 다른 굴착기로 바꿔 저장 → 그 기계 값이 사본에."""
me = world["personal"]
key = _sk(me)
_stack(me, key, [("기계", MACHINE, "묶음")])
got = _at(me, path.read, key)
doc = copy.deepcopy(got["문서"])
for v in doc["변수"]:
if (v.get("연결") or {}).get("키") == DIG_LOGIC:
v["연결"]["입력"] = {"기계": f"'{DIG2_NO}'"}
_at(me, path.save, key, {"문서": doc, "판": got["판"]})
was = _sum(me, key)
other = _live().numbers["EQ"][DIG2_NO]["키"]
_move(monkeypatch, other)
assert _sum(me, key) == was
assert _price(me, key)["바뀐"] == [other]
@pytest.mark.parametrize("kind", ["인력", "재료"])
def test_stack_resource(world, monkeypatch, kind):
"""자원 줄 더하기(노임 · 자재 요소) — 저장 뒤 값 바뀌어도 그대로 · 갱신 뒤 반영."""
me = world["personal"]
res = MAN if kind == "인력" else next(iter(_live().index["MT"]))
key = _sk(me)
part = "노무비" if kind == "인력" else "재료비"
_stack(me, key, [("자원", {"갈래": "요소", "키": res}, part)])
was = _sum(me, key)
assert was > 0
_move(monkeypatch, res)
assert _sum(me, key) == was
assert _price(me, key)["바뀐"] == [res]
def test_stack_logic_ref(world, monkeypatch):
"""단가산출 참조(M01 단가산출을 부름) — 그 식이 읽는 노임도 사본에."""
me = world["personal"]
key = _sk(me)
_stack(me, key, [("참조", {"갈래": "단가산출", "키": PCT, "입력": {}}, "묶음")])
was = _sum(me, key)
assert was > 0
_move(monkeypatch, MAN)
assert _sum(me, key) == was
assert MAN in _price(me, key)["바뀐"]
# ── 옛 로직 길(더한줄 · 파생) ────────────────────────────────────────
OLD_ADDS = {
"로직 기계": ([{"종류": "로직", "로직": DIG_LOGIC, "입력값": {"기계": DIG_NO}, "수량": 1}],
{"기계": DIG_NO}, DIG),
"재료 요소": ([{"종류": "재료", "요소": "MT000008", "수량": 1}], {}, "MT000008"),
"인력 요소": ([{"종류": "인력", "요소": MAN, "수량": 1}], {}, MAN),
} # fmt: skip
@pytest.mark.parametrize("name", list(OLD_ADDS))
def test_old_added_lines(world, monkeypatch, name):
"""옛 로직 길 더한줄(로직 기계 · 재료 요소 · 인력 요소) — 저장 뒤 그대로 · 갱신 뒤 반영."""
me = world["personal"]
adds, given, moved = OLD_ADDS[name]
key = _sk(me, 더한줄=adds, 입력값=given)
was = _sum(me, key)
assert was > 0
assert _at(me, path.read, key)["사본"]["사본없음"] == 0
_move(monkeypatch, moved)
assert _sum(me, key) == was
got = _price(me, key)
assert moved in got["바뀐"] and got["후"] > was
# ── 가져온 식 · 층 복사 · 재생성 ──────────────────────────────────────
def test_imported_ours(world, monkeypatch):
"""가져온 식 단가기준 우리 — 이음 노임 바뀌어도 그대로 · 갱신 뒤 반영 · 엑셀 기준 = 엑셀 값."""
from M02_MasterTemplete import M02_Sangeun_Imported as imp
me = world["personal"]
man = {"종류": "인력", "이름": "보통인부", "단위": "인", "수량": "0.5", "비목": "노무비"}
row = {
"이름": "sub1임시 가져온",
"결과단위": "원/㎡",
"호표": [{**man, "단가": 165545, "이음": MAN}],
}
key = _at(me, lambda: imp.create(row, None, "sub1임시 가져온")["이름"])
assert _sum(me, key) == 82772 # 엑셀 단가 그대로
doc = _at(me, lambda: store.read("sangeun", key)["문서"])
ours = _at(me, lambda: imp.to_price(doc, "우리")["문서"])
_at(me, lambda: store.write("sangeun", key, store.read("sangeun", key)["판"], ours))
was = _sum(me, key)
_move(monkeypatch, MAN)
assert _sum(me, key) == was
doc = _at(me, lambda: store.read("sangeun", key)["문서"])
got = _at(me, skc.preview, doc, skc.PRICE)
assert [x["키"] for x in got["바뀐자원"]] == [MAN] and got["후"]["계"] > was
def test_layer_copy_keeps_copy(world, monkeypatch):
"""층 복사(개인 → 회사) — 줄 쌓기 기계 사본째 · 원본 M01 값 바뀐 뒤 복사해도 그 값."""
me, co = world["personal"], world["company"]
key = _sk(me)
_stack(me, key, [("기계", MACHINE, "묶음")])
was = _sum(me, key)
_move(monkeypatch, DIG)
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 _at(co, path.read, new["이름"])["사본"]["단가새값"] is True
def test_regen_follows_m01(world, monkeypatch):
"""[재생성] — 지금 M01 로 다시 찍음(따로 문서 버림) · 그 뒤 M01 바뀌어도 그대로."""
me = world["personal"]
key = _sk(me, 바탕={"로직": PCT}, 단위="㎡")
_stack(me, key, [("기계", MACHINE, "묶음")])
_move(monkeypatch, MAN)
판 = _at(me, path.read, key)["판"]
_at(me, path.save, key, {"할일": skc.REGEN, "판": 판})
after = _at(me, path.read, key)
assert after["사본"]["따로"] is False and after["사본"]["사본없음"] == 0
assert not after["사본"]["단가새값"]
was = after["셈"]["계"]
_move(monkeypatch, MAN) # 한 번 더(네 배)
assert _sum(me, key) == was
def test_gap_mark_then_save_fills(world, monkeypatch):
"""빈틈 있는 옛 SK — 열면 지금 값으로 셈 + 「사본없음 n」 · 일괄 고쳐 쓰지 않음 ·
[저장] 하면 지금 값으로 굳음(n = 0) · 그 뒤 M01 바뀌어도 그대로."""
me = world["personal"]
key = _sk(me)
_stack(me, key, [("기계", MACHINE, "묶음")])
file = me / "sangeun" / f"{key}.json"
raw = json.loads(file.read_text(encoding="utf-8"))
vals = raw["문서"]["수식"] if "문서" in raw else raw["수식"]
for part in ("로직", "단가"):
vals[part] = [] # 옛 저장본 흉내 — 줄 쌓기 기계를 사본에 안 박은 때
file.write_text(json.dumps(raw, ensure_ascii=False), encoding="utf-8")
before = file.read_bytes()
got = _at(me, path.read, key)
assert got["사본"]["사본없음"] > 0
was = got["셈"]["계"]
assert file.read_bytes() == before # 열기 = 안 고쳐 씀
files, live = m01.loaded()
_move(monkeypatch, DIG)
moved = _sum(me, key)
assert moved > was # 빈틈 = 지금 값으로 셈
got = _at(me, path.read, key)
_at(me, path.save, key, {"문서": got["문서"], "판": got["판"]})
now = _at(me, path.read, key)
assert now["사본"]["사본없음"] == 0 and now["셈"]["계"] == moved
monkeypatch.setattr(m01, "loaded", lambda: (files, live)) # 앞 값(절반)
assert _sum(me, key) == moved