From 1dfe6bfefb12adaf46d4c065299a0271dfafb5dd Mon Sep 17 00:00:00 2001 From: umsangdon Date: Thu, 1 Oct 2026 21:00:14 +0900 Subject: [PATCH] =?UTF-8?q?fix(m02-df):=20=EB=A7=8C=EB=82=98=EB=8A=94=20?= =?UTF-8?q?=EC=83=81=EC=9E=90=EB=8A=94=20=EC=82=B4=EC=95=84=20=EC=9E=88?= =?UTF-8?q?=EB=8A=94=20=EC=84=A0=EC=9D=B4=20=EC=9E=88=EC=9C=BC=EB=A9=B4=20?= =?UTF-8?q?=EC=85=88=20=E2=80=94=20=EC=84=9C=EB=B2=84=20=3D=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 받는 상자는 들어오는 선이 모두 쉬는 갈래일 때만 쉼 · 하나라도 살아 있으면 셈(쉬는 갈래 값은 빈 칸) · 쉬는 선은 쉼 줄만 - 조건 선 바로 뒤 상자는 그대로 막음 · M02_DF_Compute.py 도 같은 뜻(resting · arrive) - 시험: 지반 견본 토사 → 1㎥ 경비 2,000 · 암 → 브레이커 갈래 값 · 쉼 줄 · 문제 0 · 서버 = 화면(26개) Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VBToTCGk5dqDKeXCEP3vhk --- M02_MasterTemplete/DataFlow/M02_DF_Compute.py | 37 ++++++++++++++-- M02_MasterTemplete/DataFlow/M02_DF_Eval.ts | 44 +++++++++++-------- resources/tester/test_m02_df_model.py | 37 ++++++++++++++++ 3 files changed, 96 insertions(+), 22 deletions(-) diff --git a/M02_MasterTemplete/DataFlow/M02_DF_Compute.py b/M02_MasterTemplete/DataFlow/M02_DF_Compute.py index d447e8eda..3abcdc73b 100644 --- a/M02_MasterTemplete/DataFlow/M02_DF_Compute.py +++ b/M02_MasterTemplete/DataFlow/M02_DF_Compute.py @@ -440,6 +440,9 @@ def _sheet_of(box: dict, arrived: dict, given: dict) -> dict: return sheet +REST = "앞 조건이 안 맞아 쉬는 갈래" + + def run(lg: dict, values: dict, load: Loader, local: list[dict], stack: list | None = None) -> dict: """셈 — {ports, outputs, cost, errors, breaks, filled}(filled = 받는 칸에 값만 되돌려 넣음).""" ctx = {"load": load, "local": local, "stack": stack or [lg.get("key") or ""]} @@ -448,6 +451,7 @@ def run(lg: dict, values: dict, load: Loader, local: list[dict], stack: list | N name = lambda i: boxes.get(i, {}).get("name") or i # noqa: E731 errors, breaks, ports, arrived, outputs = [], [], {}, {}, {} blocked: set[str] = set() + resting: set[str] = set() # 조건이 안 맞아 쉬는 상자 — Eval 과 같은 뜻 cost: dict[str, Any] = {**dict.fromkeys(COSTS, Decimal(0)), "값들": {}} def through(ln: dict) -> Any: @@ -464,13 +468,15 @@ def run(lg: dict, values: dict, load: Loader, local: list[dict], stack: list | N got, why = apply_rule(rule, v, conds) if why: blocked.add(ln["to"].get("box")) + if why == "통과 안 됨": + resting.add(ln["to"].get("box")) text = f"「{name(src.get('box'))}」 → 「{name(ln['to'].get('box'))}」 {why}" kind = "오류" if why in ("표에 여럿 맞음", "오류") else why to = ln["to"] breaks.append( { "kind": kind, - # 무게 — 통과 안 됨 = 쉼(조건이 안 맞아 쉬는 갈래) · 나머지 = 문제(Eval CfBreak.level 과 같음) + # 무게 — 통과 안 됨 = 쉼 · 나머지 = 문제(Eval CfBreak.level 과 같음) "level": "쉼" if kind == "통과 안 됨" else "문제", "box": to.get("box"), "port": to.get("name"), @@ -483,9 +489,32 @@ def run(lg: dict, values: dict, load: Loader, local: list[dict], stack: list | N return None if why else got def arrive(box: dict) -> dict: - got = { - ln["to"].get("name"): through(ln) for ln in links if ln["to"].get("box") == box["id"] - } + """받는 값 — 쉬는 갈래 빈 값은 「쉼」 줄만 · 선이 모두 쉬면 상자도 쉼(Eval 과 같음).""" + ins = [ln for ln in links if ln["to"].get("box") == box["id"]] + got, rest = {}, 0 + for ln in ins: + src, to = ln.get("from") or {}, ln["to"] + if ( + src.get("box") in resting + and ports.get(src.get("box"), {}).get(src.get("name")) is None + ): + rest += 1 + got[to.get("name")] = None + breaks.append( + { + "kind": "통과 안 됨", + "level": "쉼", + "box": to.get("box"), + "port": to.get("name"), + "link": ln.get("id"), + "text": f"「{name(src.get('box'))}」 → 「{name(to.get('box'))}」 — {REST}", + } + ) + continue + got[to.get("name")] = through(ln) + if ins and rest == len(ins): + resting.add(box["id"]) + blocked.add(box["id"]) arrived[box["id"]] = got return got diff --git a/M02_MasterTemplete/DataFlow/M02_DF_Eval.ts b/M02_MasterTemplete/DataFlow/M02_DF_Eval.ts index aba6e5f26..a35d22cf9 100644 --- a/M02_MasterTemplete/DataFlow/M02_DF_Eval.ts +++ b/M02_MasterTemplete/DataFlow/M02_DF_Eval.ts @@ -263,21 +263,6 @@ export function evalLogic( /** 선 하나로 들어오는 값 — 규칙이 있으면 엔진으로 */ const through = (l: CfLink): CfValue => { const v = valueOf(l.from.box, l.from.name); - // 쉬는 갈래에서 온 빈 값 — 받는 상자도 쉼(셈하지 않음 · 문제로 안 셈) - if (v === null && resting.has(l.from.box)) { - resting.add(l.to.box); - blocked.add(l.to.box); - const text = tl("Rest", { from: name(l.from.box), box: name(l.to.box) }); - found.push({ - kind: "통과 안 됨", - level: "쉼", - box: l.to.box, - port: l.to.name, - link: l.id, - text, - }); - return null; - } if (!l.rule) return v; if (v === null && (l.rule.kind === "scale" || l.rule.kind === "round")) return null; const conds = Object.fromEntries( @@ -311,10 +296,33 @@ export function evalLogic( if (res.why) blocked.add(l.to.box); return res.why ? null : res.value; }; + /** 받는 값 — 쉬는 갈래에서 온 빈 값은 「쉼」 줄만 남김 · 들어오는 선이 모두 쉬는 갈래면 받는 상자도 쉼 + * (하나라도 살아 있으면 셈 · 쉬는 갈래 값은 빈 칸) · 조건 선 바로 뒤 상자는 through 가 막음 */ const arrive = (box: CfBox): Record => { - const got = Object.fromEntries( - lg.links.filter((l) => l.to.box === box.id).map((l) => [l.to.name, through(l)]), - ); + const ins = lg.links.filter((l) => l.to.box === box.id); + const got: Record = {}; + let rest = 0; + for (const l of ins) { + if (resting.has(l.from.box) && valueOf(l.from.box, l.from.name) === null) { + rest++; + got[l.to.name] = null; + const text = tl("Rest", { from: name(l.from.box), box: name(l.to.box) }); + found.push({ + kind: "통과 안 됨", + level: "쉼", + box: l.to.box, + port: l.to.name, + link: l.id, + text, + }); + continue; + } + got[l.to.name] = through(l); + } + if (ins.length && rest === ins.length) { + resting.add(box.id); + blocked.add(box.id); + } arrived[box.id] = got; return got; }; diff --git a/resources/tester/test_m02_df_model.py b/resources/tester/test_m02_df_model.py index fe429974c..4cfbee4d8 100644 --- a/resources/tester/test_m02_df_model.py +++ b/resources/tester/test_m02_df_model.py @@ -643,3 +643,40 @@ def test_쉬는_갈래와_문제_무게() -> None: # 하나도 안 맞음 · 표에 없는 조건만 문제 — 쉬는 갈래 넷은 문제로 안 셈 assert b_problems == ["표에 없는 조건", "조건 안 맞음"] assert outs["b7"] is None # 쉬는 갈래 끝 출력은 빈 값(0 아님) + + +def _ground() -> dict: + return json.loads((CF / "_sample" / "ground.json").read_text(encoding="utf-8")) + + +def _server(lg: dict, values: dict) -> dict: + from M02_MasterTemplete.DataFlow import M02_DF_Compute as cp + + return cp.run(lg, values, lambda key: {}, []) + + +def test_만나는_상자는_살아_있는_선이_있으면_셈_서버와_같음() -> None: + lg = _ground() + soil = {"in-ground": {"지반": "토사"}} + rock = {"in-ground": {"지반": "암"}} + got = _run( + f"const lg = {json.dumps(lg, ensure_ascii=False)};\n" + f"const s = E.evalLogic(lg, {json.dumps(soil, ensure_ascii=False)});\n" + f"const r = E.evalLogic(lg, {json.dumps(rock, ensure_ascii=False)});\n" + "out([s.outputs, s.breaks.map(b => [b.kind, b.level, b.link]), r.outputs, r.breaks.map(b => [b.kind, b.level, b.link])," + " s.ports['fn-sum'], r.ports['fn-sum']]);" + ) + s_out, s_breaks, r_out, r_breaks, s_sum, r_sum = got + assert s_out == {"out-exp": 2000} # 굴삭기 갈래만 · 브레이커 갈래는 쉼(빈 칸) + assert s_breaks == [["통과 안 됨", "쉼", "l2"], ["통과 안 됨", "쉼", "l6"]] + assert [b for b in s_breaks if b[1] == "문제"] == [] + assert r_breaks == [["통과 안 됨", "쉼", "l1"], ["통과 안 됨", "쉼", "l5"]] + assert r_out["out-exp"] == r_sum["1㎥ 경비"] and r_out["out-exp"] != 2000 # 브레이커 갈래 값 + assert s_sum == {"1㎥ 경비": 2000} + # 서버(Compute) = 화면(Eval) + for values, screen_out, screen_breaks in ((soil, s_out, s_breaks), (rock, r_out, r_breaks)): + srv = _server(lg, values) + assert {k: float(v) for k, v in srv["outputs"].items()} == { + k: float(v) for k, v in screen_out.items() + } + assert [[b["kind"], b["level"], b["link"]] for b in srv["breaks"]] == screen_breaks