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
@@ -146,7 +146,7 @@ def _element_value(
slot = {"field": rule.get("field"), "op": rule.get("op"), "scope": el.get("scope"),
"ref": el.get("ref"), "unit": el.get("unit")} # fmt: skip
got = dv.group_find(group, slot, ports.get(src.get("box"), {}).get(src.get("name")))
if not got["ok"] and el.get("default"):
if not got["ok"] and not got.get("blank") and el.get("default"):
errors.append(f"「{box.get('name')} · {el.get('name')}」 {got['reason']} — 기본으로 셈")
got = dv.one_value({"group": group, "ref": el["default"], "unit": el.get("unit")})
elif given is not None:
+15 -3
View File
@@ -431,7 +431,11 @@ def _filled(template: str, value: Any) -> str:
def _search(slot: dict, value: Any) -> dict:
"""조건 찾기 field 검색어 — 틀(요소 ref 묶음)의 {자리} 를 알약 값으로 채워 옛 찾기 그대로."""
if value in (None, ""):
return {"ok": False, "reason": "찾을 값이 비어 있음 — 이어진 알약을 확인해 주세요"}
return {
"ok": False,
"blank": True,
"reason": "찾을 값이 비어 있음 — 이어진 알약을 확인해 주세요",
}
cond = _pick(slot.get("ref")) # 요소 ref = 옛 검색어 묶음(규격 · 지역 칸도 그대로)
if cond is None: # 묶음이 없으면 범위(구분 · 상세구분) + 채운 검색어
scope = slot.get("scope") or {}
@@ -468,7 +472,11 @@ def slot_price(slot: dict, value: Any = None, pick: str | None = None) -> dict:
if field == "검색어":
return _search(slot, value)
if value in (None, ""):
return {"ok": False, "reason": "찾을 값이 비어 있음 — 이어진 알약을 확인해 주세요"}
return {
"ok": False,
"blank": True,
"reason": "찾을 값이 비어 있음 — 이어진 알약을 확인해 주세요",
}
want = _squash(value)
sub, detail = str(scope.get("구분") or ""), str(scope.get("상세구분") or "")
rows = _found(sub, detail, scope.get("찾는말") or "")
@@ -520,7 +528,11 @@ def group_find(group: str, slot: dict, value: Any) -> dict:
· field 가 없으면 키(원문번호) 또는 이름이 같은 줄 · 하나면 그 값(`one_value`).
"""
if value in (None, ""):
return {"ok": False, "reason": "찾을 값이 비어 있음 — 이어진 알약을 확인해 주세요"}
return {
"ok": False,
"blank": True,
"reason": "찾을 값이 비어 있음 — 이어진 알약을 확인해 주세요",
}
field, op, want = slot.get("field"), slot.get("op") or "같음", _squash(value)
words = str((slot.get("scope") or {}).get("찾는말") or "").split()
+1 -1
View File
@@ -211,7 +211,7 @@ def write_file(
def _kept_values(values: Any, labor: dict[str, Any]) -> dict[str, Any]:
"""같은 로직 · 같은 입력 이름만 살림."""
names = {
str(row.get("키")): {spec["이름"] for spec in row.get("입력", [])}
str(row.get("키")): {spec["이름"] for spec in prices.mf.inputs_of(row)}
for row in labor.get("로직", [])
}
out: dict[str, Any] = {}
+48 -1
View File
@@ -125,6 +125,50 @@ class _Collector:
for key in mf.candidates(self.whole, one, None, None):
self.add(key)
def flow(self, row: dict) -> None:
"""흐름 입력 상자 — 요소(고정 · 기본 · 검색어 묶음 · 조건 찾기 후보) · 표 · 다른 로직 ·
기계 시간당 경비 로직."""
from M02_MasterTemplete.DataFlow import M02_DF_Values as dv # 흐름 값 찾기와 같은 뜻
for box in row["흐름"].get("boxes") or []:
inp = (box.get("input") or {}) if box.get("kind") == "input" else {}
if inp.get("mode") == "logic":
self.logic(str((inp.get("logic") or {}).get("key") or ""))
if inp.get("table"):
self.add(str(inp["table"].get("key") or ""))
for el in inp.get("elements") or []:
ref, unit = str(el.get("ref") or ""), el.get("unit")
for one in (ref, str(el.get("default") or "")):
cond = dv._pick(one)
if cond is not None:
self.pick(row, cond, unit)
elif one and not one.startswith("UE-"):
self.element(one)
if el.get("mode") != "rule":
continue
if inp.get("group") in dv.GROUP_RULE: # 인력 · 기계 · 유가전력 찾기 = 그 그룹 전부
self.keys.update(str(r.get("키")) for r in dv._group_rows(inp["group"]))
elif dv._pick(ref) is None: # 재료 범위 찾기 — 범위(구분 · 상세구분) 후보
scope = el.get("scope") or {}
self.pick(
row, {k: scope[k] for k in ("구분", "상세구분") if scope.get(k)}, unit
)
def element(self, ref: str) -> None:
"""요소 키 · 「EQ키#칸」(시간당 경비면 그 기계경비 로직까지) · 「ID:원문번호{…}」 틀."""
from M02_MasterTemplete.DataFlow import M02_DF_Values as dv
base, _, field = ref.partition(dv.MACHINE_SEP)
if "{" in base: # 「EQ:{기계}」 같은 틀 — 그 테이블 전부
self.keys.update(self.whole.index.get(mk.id_of(base) or "", {}))
return
self.add(base)
if field == dv.HOURLY:
code = str((self.whole.index.get(base[:2], {}).get(base) or {}).get("원문번호") or "")
for fit in dv._machine_family():
if code in [str(c) for c in dv._codes(fit)]:
self.logic(str(fit.get("키")))
def logic(self, key: str) -> None:
if key in self.logics:
return
@@ -135,6 +179,9 @@ class _Collector:
return
self.logics.add(key)
self.keys.add(key)
if isinstance(row.get("흐름"), dict): # 흐름 로직 — 옛 칸 대신 흐름이 쓰는 것을 모음
self.flow(row)
return
for text in _texts(row):
try:
node = mf.parse(text)
@@ -201,7 +248,7 @@ def missing_inputs(snapshot: Any, labor: dict[str, Any], values: dict[str, Any])
except mf.FormulaError:
continue
mine = values.get(key) or {}
for spec in row.get("입력", []):
for spec in mf.inputs_of(row):
got = mine.get(spec["이름"])
if not isinstance(got, dict) or got.get("값") in (None, ""):
out.append({"로직": key, "입력": spec["이름"]})
@@ -84,7 +84,7 @@ def inputs(key: str) -> dict[str, Any]:
for logic in logics:
auto = m01.mau.values(whole, logic)
specs = []
for spec in logic.get("입력") or []:
for spec in m01.cm.mf.inputs_of(logic):
one = {k: spec[k] for k in _SPEC if k in spec}
one.setdefault("설명", None)
one.setdefault("단위", None)
@@ -126,7 +126,9 @@ def write_file(column: str, doc: dict[str, Any]) -> None:
def _known(key: str) -> dict[str, set[str]]:
"""{로직 키: {입력 이름}} — 그 항목 조합이 지금 담은 것."""
_, logics, _ = combo_logics(_item(key).get("일위대가"))
return {str(lg.get("키")): {str(s.get("이름")) for s in lg.get("입력") or []} for lg in logics}
return {
str(lg.get("키")): {str(s.get("이름")) for s in m01.cm.mf.inputs_of(lg)} for lg in logics
}
def read_links(key: str) -> dict[str, Any]:
@@ -204,7 +206,7 @@ def pills(key: str, place: str) -> dict[str, Any]:
if row.get("자리") != place or row.get("사라짐"):
continue
logic = by_key[str(row["로직"])]
spec = next(s for s in logic.get("입력") or [] if s.get("이름") == row["입력"])
spec = next(s for s in m01.cm.mf.inputs_of(logic) if s.get("이름") == row["입력"])
short = combo_store._TAIL.sub("", str(logic.get("이름") or "")) or row["로직"]
linked.append(
{