- 흐름으로 저장 = 계산 칸(중간 · 호표 · 결과 · 덧줄)만 지우고 「입력」(이름 · 설명 · 단위 · 고르기 · 범위)은 흐름에서 같은 꼴로 · 조합 · 구조물 · 미리 보기 · 다시 적용은 「입력」 을 그대로 읽음(따로 갈래 없음) - 엔진 갈래: 안 준 입력 = 멈춤 · 빈 값으로 준 입력 · 단가 없는 품목 · 표에 없는 조건 = 그 값만 비움(알림) · 결과 꼴 = 돈 출력 줄 · 값들 · 알림 - 프로젝트에서 고른 자재 = 흐름 재료 자리 이름으로(고름 · 다시 적용 때 살림) - 시험판 길 지움: /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
97 lines
4.1 KiB
Python
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})
|