Files
Aislo/resources/tester/test_m01_logic_flow_project.py
T
eomsangdonandClaude Opus 5.5 3336902c17 feat(M01): 흐름 로직 입력 약속 하나 — 저장 때 「입력」 은 흐름의 설계 때 정할 것으로 다시 씀 · 지우는 것은 계산 칸만 · 시험판 길 지움
- 흐름으로 저장 = 계산 칸(중간 · 호표 · 결과 · 덧줄)만 지우고 「입력」(이름 · 설명 · 단위 · 고르기 · 범위)은 흐름에서 같은 꼴로 · 조합 · 구조물 · 미리 보기 · 다시 적용은 「입력」 을 그대로 읽음(따로 갈래 없음)
- 엔진 갈래: 안 준 입력 = 멈춤 · 빈 값으로 준 입력 · 단가 없는 품목 · 표에 없는 조건 = 그 값만 비움(알림) · 결과 꼴 = 돈 출력 줄 · 값들 · 알림
- 프로젝트에서 고른 자재 = 흐름 재료 자리 이름으로(고름 · 다시 적용 때 살림)
- 시험판 길 지움: /list · /get · /save · /delete · /user-elements(서버) · cfList · cfGet · cfSave · cfDelete · cfUserElement*(Api) · 빈 _data 폴더
- 시험: 조합에 담은 로직을 흐름으로 바꿔도 입력 목록 · 로직별 결과 · 비목 합 · 단가 묶음 셈이 같음

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KmELpdXFDRZHhfbGuRhSmL
2026-10-02 09:57:30 +09:00

97 lines
4.1 KiB
Python

# ruff: noqa: E501
"""흐름 로직이 일위대가 조합 · 프로젝트 길에서도 그대로(PLAN 36 · 사용자 원칙 「조합은 로직을 가져가 더하기만」 · 사본 폴더).
못박는 것 — 조합에 담은 로직을 모두 흐름으로 저장(계산 칸 지움 · 「입력」 은 흐름에서 같은 꼴로)한 뒤
- 로직마다 입력 목록(이름 · 단위 · 고르기 · 범위) = 바꾸기 전
- 조합 셈(`master_combo.run`) 로직별 결과 · 비목 합 = 바꾸기 전
- 프로젝트 단가 묶음(`M02_Item_Prices.collect`)만으로 셈 = 바꾸기 전(흐름이 쓰는 요소 · 표 · 다른 로직이 담김)
- 빈 값으로 준 입력은 「빈 입력」 알림(예시값으로 안 넘어감) · 안 준 입력은 멈춤
"""
from __future__ import annotations
import shutil
from decimal import Decimal
from pathlib import Path
import pytest
from M01_MasterData import M01_MasterData_Store as store
from M01_MasterData import M01_MasterData_Store_Flow as sflow
from M02_MasterTemplete import M02_Item_Prices as prices
from M02_MasterTemplete.DataFlow import M02_DF_Convert as cv
REAL = store.FOLDER
KEYS = ("GF000185", "GF000219") # 레미콘 검색어 · 펌프차 기계 · 인력 / 표 찾기 · 인력
COMBO = {
"키": "UA999999",
"이름": "시험 조합",
"담은로직": [{"로직": k, "메모": None} for k in KEYS],
}
def _specs(row: dict) -> list:
return [
(s["이름"], s.get("단위"), s.get("고르기"), s.get("범위")) for s in row.get("입력") or []
]
def _round(v):
return round(Decimal(str(v)), 6) if v is not None else None
def _combo(folder: Path) -> tuple[list, dict]:
mf, mcb = store.cm.mf, store.cm.mcb
given = {k: dict(store.cm.시험입력(store.logic(k)["logic"])) for k in KEYS}
got = mcb.run(mf.Master(store.cm.load(folder=folder)), COMBO, given)
lines = [
(x["로직"], x["ok"], {k: _round(x.get(k)) for k in ("노무비", "재료비", "경비", "계")})
for x in got["줄"]
]
return lines, {k: _round(got.get(k)) for k in ("노무비", "재료비", "경비", "계")}
def _bundle_sums() -> dict:
mf = store.cm.mf
snapshot = prices.load_prices(prices.to_json(prices.collect(list(KEYS))))
out = {}
for k in KEYS:
got = mf.run(snapshot, k, dict(store.cm.시험입력(store.logic(k)["logic"])))
out[k] = {n: _round(got[n]) for n in ("노무비", "재료비", "경비", "계")}
return out
def test_조합에_담은_로직을_흐름으로_바꿔도_같음(tmp_path: Path, monkeypatch) -> None:
for path in REAL.glob("*.json"):
shutil.copy(path, tmp_path / path.name)
monkeypatch.setattr(store, "FOLDER", tmp_path)
specs = {k: _specs(store.logic(k)["logic"]) for k in KEYS}
lines, sums = _combo(tmp_path)
assert all(ok for _, ok, _ in lines) and sums["계"] > 0
bundle = _bundle_sums()
for key in KEYS:
got = store.logic(key)
flow = cv.convert(got["logic"], {"key": key, "master": store.loaded()[1]})["flow"]
flow.pop("key", None)
change = {"op": "edit", "key": key, "row": {**got["logic"], "흐름": flow}}
sflow.save(
[store._dec({"file": got["file"], "version": got["version"], "changes": [change]})]
)
for key in KEYS:
row = store.logic(key)["logic"]
assert not set(row) & set(store.cm.mf.CALC_FIELDS) and row["흐름"] # 계산 칸만 지움
assert _specs(row) == specs[key] # 입력 약속 그대로
assert _combo(tmp_path) == (lines, sums)
assert "_빠진" not in prices.collect(list(KEYS))
assert _bundle_sums() == bundle
mf = store.cm.mf
whole = mf.Master(store.cm.load(folder=tmp_path))
given = dict(store.cm.시험입력(store.logic(KEYS[0])["logic"]))
first = next(iter(given))
blank = mf.run(
whole, KEYS[0], {**given, first: None}
) # 빈 값으로 준 입력 = 그 값을 쓰는 곳만 비움
assert any(f"「{first}」 빈 입력" in n for n in blank.get("알림") or [])
with pytest.raises(mf.FormulaError, match="없음"): # 안 준 입력 = 멈춤(엔진과 같음)
mf.run(whole, KEYS[0], {k: v for k, v in given.items() if k != first})