feat(M01): 흐름 로직 줄 = 옛 식 칸(입력 · 중간 · 호표 · 결과 · 덧줄) 지움 · 읽는 곳은 흐름의 설계 때 정할 것 · 출력에서(사용자 결정)

- master_formula.inputs_of · FLOW_INPUTS · OLD_FIELDS · mf.run 이 흐름 줄이면 스스로 서버 갈래를 깖(스크립트 · 시험도)
- check_master 틀 검사 · 시험입력 · 로직(…) 입력 검사 · master_auto 자동값 · 프로젝트 빈 입력 · 연결 · 다시 적용 값이 흐름 입력으로
- 프로젝트 단가 묶음(collect)이 흐름의 요소 · 표 · 다른 로직 · 기계 시간당 경비 로직을 모음
- 옛 엔진 갈래 결과 = 옛 꼴(돈 출력 줄 · 값들 · 알림) · 조합 · 프로젝트에서 안 준 입력은 빈 입력 알림(예시값 아님)
- /text 흐름 로직 = 출력 상자 줄 · GET /logic 은 보기용으로 설계 때 정할 것을 입력 꼴로 얹음(파일은 그대로)
- 찾을 값이 빈 조건 찾기는 그 값만 비움(옛 셈처럼)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KmELpdXFDRZHhfbGuRhSmL
This commit is contained in:
2026-10-02 08:55:36 +09:00
co-authored by Claude Opus 5.5
parent 8fed757fc1
commit 28da700d47
13 changed files with 275 additions and 33 deletions
+67 -13
View File
@@ -167,8 +167,10 @@ def _value(v: Any, words: set | frozenset = frozenset()) -> Any:
return store._dec(v)
def run(flow: dict, key: str, inputs: dict) -> dict:
"""흐름 셈 — 입력 = 「설계 때 정할 것」 이름 → 값."""
def run(flow: dict, key: str, inputs: dict, strict: bool = False) -> dict:
"""흐름 셈 — 입력 = 「설계 때 정할 것」 이름 → 값 · strict(조합 · 프로젝트) = 안 준 입력은
예시값
대신 빈 값 + 알림 「빈 입력」(옛 엔진처럼 그 값을 쓰는 곳만 비움)."""
active = _ACTIVE.get()
if key and key in active: # 옛 셈을 건너 다시 돌아온 흐름(흐름 → 옛 로직 → 흐름)
raise store.cm.mf.FormulaError(f"로직이 서로를 부름 — {' → '.join([*active, key])}")
@@ -183,7 +185,12 @@ def run(flow: dict, key: str, inputs: dict) -> dict:
given = {k: _value(v, words) for k, v in (inputs or {}).items()}
flow = {**flow, "key": key}
values = dc._design_values(flow, given)
return dc.run(flow, values, load, flow.get("userElements") or [], [key])
blank = [d for d in dc.design_inputs(flow) if strict and given.get(d["port"]) is None]
for d in blank:
values.setdefault(d["box"], {})[d["port"]] = None
got = dc.run(flow, values, load, flow.get("userElements") or [], [key])
got["notes"] = [*(f"입력 「{d['port']}」 빈 입력" for d in blank), *got.get("notes", [])]
return got
finally:
_ACTIVE.reset(token)
@@ -195,20 +202,65 @@ def _engine(master, ref: str, row: dict, given: dict) -> dict:
"""
token = dv.SOURCE.set(master)
try:
got = run(row[FLOW], ref, given)
got = run(row[FLOW], ref, given, strict=True)
finally:
dv.SOURCE.reset(token)
if got["errors"]:
raise store.cm.mf.FormulaError(f"「{ref}」 흐름 셈 — {' · '.join(got['errors'])}")
cost = got["cost"]
kinds = {(b.get("output") or {}).get("cost") for b in row[FLOW].get("boxes") or []
if b.get("kind") == "output"} # fmt: skip
if kinds and kinds <= {"값"} and len(cost["값들"]) == 1: # 돈 아닌 로직 = 결과 하나
return {"결과": next(iter(cost["값들"].values())), "중간": {}}
return {"줄": [], **{k: cost[k] for k in dc.COSTS}, "계": cost["계"], "중간": {}}
cost, notes = got["cost"], got.get("notes") or []
outs = [b for b in row[FLOW].get("boxes") or [] if b.get("kind") == "output"]
extra = {"중간": {}, "값들": cost["값들"], **({"알림": notes} if notes else {})}
if outs and all((b.get("output") or {}).get("cost") in (None, "값") for b in outs):
first = got["outputs"].get(outs[0]["id"]) # 돈 아닌 로직 = 첫 출력이 결과 · 나머지는 값들
return {"결과": first, **extra}
lines = [] # 옛 「줄」 꼴 — 돈 출력 상자마다 한 줄(수량 1 · 단가 = 금액)
for b in outs:
kind, value = (b.get("output") or {}).get("cost"), got["outputs"].get(b["id"])
if kind in dc.COSTS:
unit = (b.get("output") or {}).get("unit") or ""
lines.append({"이름": b.get("name") or "", "단위": unit,
"수량": Decimal(1), "단가": value, "금액": value, "비목": {kind: value},
**({} if value is not None else {"까닭": "값 없음"})}) # fmt: skip
sums = {k: cost[k] for k in dc.COSTS}
return {"줄": lines, **sums, "계": cost["계"], **extra}
def _inputs(row: dict) -> list[dict]:
"""흐름 로직 입력 = 「설계 때 정할 것」 → 옛 입력 꼴(이름 · 단위 · 고르기 · 범위 · 예시)."""
out = []
for d in dc.design_inputs(row[FLOW]):
v = d.get("variable") or {}
one = {"이름": d["port"], "단위": None if d.get("unit") in (None, "-") else d["unit"]}
one |= {"고르기": v["choices"]} if v.get("choices") else {}
one |= {"범위": v["range"]} if v.get("range") else {}
out.append(one | {"예시": v.get("example")})
return out
store.cm.mf.FLOW_RUN = _engine # 옛 엔진을 부르는 곳이 모두 흐름 로직을 흐름 셈으로(한 곳 갈래)
store.cm.mf.FLOW_INPUTS = (
_inputs # 입력을 읽는 곳(시험입력 · 자동값 · 프로젝트 입력 · 연결)도 흐름에서
)
def text(key: str, inputs: dict, row: dict | None = None) -> dict | None:
"""읽는 식 줄(`/text` 꼴) — 흐름 로직이면 출력 상자를 비목 묶음 줄로 · 아니면 None(옛 글)."""
got = calc(key, inputs, row)
if got is None or not got.get("flow") or "outputs" not in got:
return got
groups: dict[str, list] = {}
for o in got["outputs"]:
value = o["value"]
shown = f"{value:,}" if isinstance(value, (int, Decimal)) else str(value)
line = {"이름": o["name"], "글": shown, "이름글": o["name"], "금액": value, "조각": []}
groups.setdefault(o["cost"] or "값", []).append(line)
out = [{"비목": k, "줄": v} for k, v in groups.items()]
return {
"ok": got["ok"],
"flow": True,
"groups": out,
**({"reason": got["reason"]} if "reason" in got else {}),
}
def auto(key: str) -> dict | None:
@@ -294,9 +346,10 @@ def _redo(row: dict, key: str) -> list[str]:
def _bare(row: dict) -> dict:
"""새 흐름 로직 — 옛 식 칸은 빈 것으로(파일 틀 검사) · 출처 없으면 「자체」."""
old = {"입력": [], "중간": [], "호표": [], "끝수": None}
return {**old, **row, "출처": row.get("출처") or "자체"}
"""흐름 로직 줄 — 옛 식 칸(입력 · 중간 · 호표 · 결과 · 덧줄)은 지움(흐름이 정본)
· 끝수 · 시험입력 · 메타 칸은 둠 · 출처 없으면 「자체」."""
kept = {k: v for k, v in row.items() if k not in store.cm.mf.OLD_FIELDS}
return {"끝수": None, **kept, "출처": row.get("출처") or "자체"}
def save(batch: list[dict]) -> dict:
@@ -313,6 +366,7 @@ def save(batch: list[dict]) -> dict:
raise store.StoreError(400, f"새 흐름 로직은 「{make.OWN}」 에만 더함")
row = make._clean(_bare(row), str(row.get("소유") or make.OWNERS[0]))
ch.update(op="add", key=None)
row = _bare(row) if not new else row
ch["row"] = row
name = row.get("이름") or ch.get("key") or "새 로직"
warns += [f"「{name}」 {w}" for w in _redo(row, str(ch.get("key") or ""))]