From 018177408bddf41f737ea6db0f04f6ca5e2d7933 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Thu, 1 Oct 2026 20:38:21 +0900 Subject: [PATCH 1/3] =?UTF-8?q?feat(m02-df):=20=EB=81=8A=EA=B8=B4=20?= =?UTF-8?q?=EA=B3=B3=20=EB=AC=B4=EA=B2=8C=20=E2=80=94=20=EC=89=BC(?= =?UTF-8?q?=EC=89=AC=EB=8A=94=20=EA=B0=88=EB=9E=98)=20=C2=B7=20=EB=AC=B8?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CfBreak.level: 통과 안 됨 · 그 아래로 번진 빈 값 = 쉼 · 빈 받는 자리 · 표에 없는 조건 · 맞는 재료 없음 · 조건 겹침 · 안 맞음 · 오류 = 문제 - 쉬는 갈래에서 온 빈 값은 받는 상자도 쉼(셈 안 함 · 출력 빈 값 · 문제로 안 셈) - M02_DF_Compute.py breaks 에 같은 뜻 level 한 칸 - 시험 25개 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VBToTCGk5dqDKeXCEP3vhk --- M02_MasterTemplete/DataFlow/M02_DF_Compute.py | 2 + M02_MasterTemplete/DataFlow/M02_DF_Eval.ts | 42 +++++++++++++++++-- resources/tester/test_m02_df_model.py | 29 +++++++++++-- 3 files changed, 66 insertions(+), 7 deletions(-) diff --git a/M02_MasterTemplete/DataFlow/M02_DF_Compute.py b/M02_MasterTemplete/DataFlow/M02_DF_Compute.py index cc7d9c1ed..d447e8eda 100644 --- a/M02_MasterTemplete/DataFlow/M02_DF_Compute.py +++ b/M02_MasterTemplete/DataFlow/M02_DF_Compute.py @@ -470,6 +470,8 @@ def run(lg: dict, values: dict, load: Loader, local: list[dict], stack: list | N breaks.append( { "kind": kind, + # 무게 — 통과 안 됨 = 쉼(조건이 안 맞아 쉬는 갈래) · 나머지 = 문제(Eval CfBreak.level 과 같음) + "level": "쉼" if kind == "통과 안 됨" else "문제", "box": to.get("box"), "port": to.get("name"), "link": ln.get("id"), diff --git a/M02_MasterTemplete/DataFlow/M02_DF_Eval.ts b/M02_MasterTemplete/DataFlow/M02_DF_Eval.ts index 9b6380065..aba6e5f26 100644 --- a/M02_MasterTemplete/DataFlow/M02_DF_Eval.ts +++ b/M02_MasterTemplete/DataFlow/M02_DF_Eval.ts @@ -43,6 +43,8 @@ export interface CfBreak { | "조건 겹침" | "조건 안 맞음" | "오류"; + /** 무게 — 「쉼」 = 조건이 안 맞아 쉬는 갈래(그 아래로 번진 빈 값 포함) · 「문제」 = 고쳐야 할 끊김 */ + level: "문제" | "쉼"; box: string; port?: string; link?: string; @@ -112,6 +114,11 @@ const TEXT = { "「{from}」 → 「{box}」 선 — 표에 맞는 줄이 여럿", "Line 「{from}」 → 「{box}」 — several table rows match", ], + /** {from} {box} */ + Rest: [ + "「{from}」 → 「{box}」 — 앞 조건이 안 맞아 쉬는 갈래", + "「{from}」 → 「{box}」 — resting branch (an earlier condition doesn't match)", + ], RuleError: [ "「{from}」 → 「{box}」 선 규칙을 셀 수 없음", "Line 「{from}」 → 「{box}」 rule can't be worked out", @@ -251,9 +258,26 @@ export function evalLogic( const passed = new Map(); /** 선 규칙에서 막힌 상자 — 그 함수는 셈하지 않고 빈 값을 넘김(갈래가 끊김) */ const blocked = new Set(); + /** 조건이 안 맞아 쉬는 상자 — 그 아래로 번짐 */ + const resting = new Set(); /** 선 하나로 들어오는 값 — 규칙이 있으면 엔진으로 */ 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( @@ -262,15 +286,17 @@ export function evalLogic( const res = applyRule(l.rule, v, conds); if (l.rule.kind === "gate") passed.set(l.id, res.why === null); const fill = { from: name(l.from.box), box: name(l.to.box), rule: ruleLabel(l.rule) }; - if (res.why === "통과 안 됨") + if (res.why === "통과 안 됨") { + resting.add(l.to.box); found.push({ kind: "통과 안 됨", + level: "쉼", box: l.to.box, port: l.to.name, link: l.id, text: tl("NotPassed", fill), }); - else if (res.why) { + } else if (res.why) { const kind = res.why === "표에 없는 조건" ? "표에 없는 조건" : "오류"; const key = res.why === "표에 없는 조건" @@ -279,7 +305,7 @@ export function evalLogic( ? "ManyRows" : "RuleError"; const text = tl(key, fill); - found.push({ kind, box: l.to.box, port: l.to.name, link: l.id, text }); + found.push({ kind, level: "문제", box: l.to.box, port: l.to.name, link: l.id, text }); errors.push(text); } if (res.why) blocked.add(l.to.box); @@ -303,7 +329,13 @@ export function evalLogic( for (const e of b.input?.elements ?? []) if (e.mode === "rule" && ports[b.id][elementPort(e)] === null) { const text = tl("NoMaterial", { box: b.name, pill: elementPort(e) }); - found.push({ kind: "맞는 재료 없음", box: b.id, port: elementPort(e), text }); + found.push({ + kind: "맞는 재료 없음", + level: "문제", + box: b.id, + port: elementPort(e), + text, + }); errors.push(text); } } else if (b.kind === "input") ports[b.id] = otherLogic(b, arrive(b), given, opts, errors); @@ -383,6 +415,7 @@ function gates( const text = tl(n > 1 ? "Overlap" : "NoneMatch", fill); found.push({ kind: n > 1 ? "조건 겹침" : "조건 안 맞음", + level: "문제", box: g.from.box, port: g.from.name, text, @@ -402,6 +435,7 @@ export function breaks(lg: CfLogic, values: CfValues = {}, opts: CfEvalOptions = if (!lg.links.some((l) => l.to.box === b.id && l.to.name === p.name)) out.push({ kind: "빈 받는 자리", + level: "문제", box: b.id, port: p.name, text: tl("EmptyIn", { box: b.name, pill: p.name }), diff --git a/resources/tester/test_m02_df_model.py b/resources/tester/test_m02_df_model.py index 449a25a04..fe429974c 100644 --- a/resources/tester/test_m02_df_model.py +++ b/resources/tester/test_m02_df_model.py @@ -450,16 +450,17 @@ def test_나_조건_통과로_둘로_가르기_표에서_찾기() -> None: a_out, a_breaks, b_out, b_breaks, far = got # 토사: 0.5 × 100000 × 1.1 · 암 줄은 통과 안 됨 · 높이 4.5 → 증가율 40 assert a_out == {"b6": 55000, "b7": None, "b8": 40} - assert a_breaks == [["통과 안 됨", "l2"]] + assert a_breaks == [["통과 안 됨", "l2"], ["통과 안 됨", "l5"]] # 암 갈래 + 그 아래로 번진 쉼 assert b_out == { "b6": None, "b7": 50000, "b8": 30, } # 암 50,000 → 천 원 버림 그대로 · 높이 3.5 → 30 - assert b_breaks == ["통과 안 됨"] + assert b_breaks == ["통과 안 됨", "통과 안 됨"] # 토사 갈래 + 번진 쉼 assert far == [ ["통과 안 됨", "「입력」 → 「함수 2」 선이 조건(지반 = 암 일 때만)을 통과하지 않음"], ["표에 없는 조건", "「입력 2」 → 「함수 3」 선 — 표에 맞는 줄이 없음"], + ["통과 안 됨", "「함수 2」 → 「출력 2」 — 앞 조건이 안 맞아 쉬는 갈래"], ] @@ -471,7 +472,7 @@ def test_나_조건_선_겹침_안맞음() -> None: ) overlap, none = got assert overlap == [["조건 겹침", "「입력」 의 「지반」 에서 나간 조건 선이 둘 이상 통과함"]] - assert none == ["통과 안 됨", "통과 안 됨", "조건 안 맞음"] + assert none == ["통과 안 됨", "통과 안 됨", "통과 안 됨", "통과 안 됨", "조건 안 맞음"] def test_나_규칙_글과_엑셀_수식() -> None: @@ -620,3 +621,25 @@ def test_표_범위칸_숫자하나는_같음() -> None: ) # 4 = 「4」 줄만(같음) · 4 는 「4~6」(초과) 에 안 듦 · 빈 아래 끝은 열림 assert got == [[0.5, None], [0.6, None], [0.4, None], [None, "표에 없는 조건"], [0.6, None]] + + +def test_쉬는_갈래와_문제_무게() -> None: + got = _run( + SPLIT + "const a = E.breaks(lg, V('토사'));\n" + "const b = E.breaks(lg, V('자갈', 9));\n" + "out([a.map(x => [x.kind, x.level, x.box, x.text]), b.filter(x => x.level === '문제').map(x => x.kind)," + " E.evalLogic(lg, V('토사')).outputs]);" + ) + a, b_problems, outs = got + assert a == [ + [ + "통과 안 됨", + "쉼", + "b4", + "「입력」 → 「함수 2」 선이 조건(지반 = 암 일 때만)을 통과하지 않음", + ], + ["통과 안 됨", "쉼", "b7", "「함수 2」 → 「출력 2」 — 앞 조건이 안 맞아 쉬는 갈래"], + ] + # 하나도 안 맞음 · 표에 없는 조건만 문제 — 쉬는 갈래 넷은 문제로 안 셈 + assert b_problems == ["표에 없는 조건", "조건 안 맞음"] + assert outs["b7"] is None # 쉬는 갈래 끝 출력은 빈 값(0 아님) From 8a9f23c6e3b9bef8503ad1152a6affc7feddc0e9 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Thu, 1 Oct 2026 20:39:25 +0900 Subject: [PATCH 2/3] =?UTF-8?q?refactor(M02):=20=ED=9D=90=EB=A6=84?= =?UTF-8?q?=EB=8F=84=20=EB=8B=A4=EC=9D=B4=EC=96=B4=EA=B7=B8=EB=9E=A8=20?= =?UTF-8?q?=EC=89=BC=20=EA=B0=80=EB=A5=B4=EA=B8=B0=EB=A5=BC=20Eval=20level?= =?UTF-8?q?=20=EA=B7=B8=EB=8C=80=EB=A1=9C(=EA=B0=80=EC=A0=95=20=EA=B1=B7?= =?UTF-8?q?=EC=9D=8C=20=C2=B7=20PLAN=2035-9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 시험 test_m02_df_diagram_parts.py 4 통과 · DataFlow 전부 검사 초록 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Ly2oHAaTDYRjcniAmHR81z --- .../DataFlow/M02_DF_Diagram_Parts.ts | 5 ++--- resources/tester/test_m02_df_diagram_parts.py | 20 +++++++++++++++---- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/M02_MasterTemplete/DataFlow/M02_DF_Diagram_Parts.ts b/M02_MasterTemplete/DataFlow/M02_DF_Diagram_Parts.ts index 6ee7fdf61..6c03d9b21 100644 --- a/M02_MasterTemplete/DataFlow/M02_DF_Diagram_Parts.ts +++ b/M02_MasterTemplete/DataFlow/M02_DF_Diagram_Parts.ts @@ -284,9 +284,8 @@ export function depInTrace(dep: DepLine, steps: CfStep[]): boolean { return src && dst; } -/** 끊긴 곳 갈래 — 쉼(조건이 안 맞아 이번엔 안 지나감 · 문제 아님) · 문제 — level 이 없으면 「통과 안 됨」 을 쉼으로 */ -export const isRest = (k: CfBreak): boolean => - ((k as CfBreak & { level?: string }).level ?? (k.kind === "통과 안 됨" ? "쉼" : "문제")) === "쉼"; +/** 끊긴 곳 갈래 — 쉼(조건이 안 맞아 이번엔 안 지나감 · 문제 아님) · 문제 — Eval 의 level 그대로 */ +export const isRest = (k: CfBreak): boolean => k.level === "쉼"; /* ── 끌기 손잡이 ─────────────────────────────────────────────────────── */ diff --git a/resources/tester/test_m02_df_diagram_parts.py b/resources/tester/test_m02_df_diagram_parts.py index 372581c53..ef01ab2f1 100644 --- a/resources/tester/test_m02_df_diagram_parts.py +++ b/resources/tester/test_m02_df_diagram_parts.py @@ -203,10 +203,22 @@ def test_쉼_가르기(): { "op": "rest", "breaks": [ - {"kind": "통과 안 됨", "box": "f", "link": "l2", "text": "지반이 암이 아님"}, - {"kind": "빈 받는 자리", "box": "f", "port": "품", "text": "비어 있음"}, - {"kind": "통과 안 됨", "box": "f", "level": "문제", "text": "level 이 먼저"}, - {"kind": "조건 겹침", "box": "f", "level": "쉼", "text": "level 이 먼저"}, + { + "kind": "통과 안 됨", + "level": "쉼", + "box": "f", + "link": "l2", + "text": "지반이 암이 아님", + }, + { + "kind": "빈 받는 자리", + "level": "문제", + "box": "f", + "port": "품", + "text": "비어 있음", + }, + {"kind": "통과 안 됨", "level": "문제", "box": "f", "text": "level 이 정함"}, + {"kind": "조건 겹침", "level": "쉼", "box": "f", "text": "level 이 정함"}, ], } ] From aaa36625e8285d155108283f35c6e89cc9c53d92 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Thu, 1 Oct 2026 20:39:31 +0900 Subject: [PATCH 3/3] =?UTF-8?q?feat(m02-df):=20=EB=81=8A=EA=B8=B4=20?= =?UTF-8?q?=EA=B3=B3=EC=9D=80=20=EB=AC=B8=EC=A0=9C=EB=A7=8C=20=C2=B7=20?= =?UTF-8?q?=EC=89=AC=EB=8A=94=20=EA=B0=88=EB=9E=98=EB=8A=94=20=EB=94=B0?= =?UTF-8?q?=EB=A1=9C=20=ED=9A=8C=EC=83=89=20=EC=A4=84(level)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01EauA2ATmqNRbeV48s9CcBr --- M02_MasterTemplete/DataFlow/M02_DF_Verify.css | 12 ++++ M02_MasterTemplete/DataFlow/M02_DF_Verify.ts | 63 ++++++++++++------- 2 files changed, 52 insertions(+), 23 deletions(-) diff --git a/M02_MasterTemplete/DataFlow/M02_DF_Verify.css b/M02_MasterTemplete/DataFlow/M02_DF_Verify.css index 7843ee96f..7f5dd2c53 100644 --- a/M02_MasterTemplete/DataFlow/M02_DF_Verify.css +++ b/M02_MasterTemplete/DataFlow/M02_DF_Verify.css @@ -116,3 +116,15 @@ color: var(--color-danger); font-size: var(--text-caption); } + +/* 쉬는 갈래 — 문제가 아니라 회색으로 */ +.m02-dfver__rest { + padding: var(--spacing-8) var(--spacing-12); + border-left: 4px solid var(--color-border); + background: var(--color-surface); + color: var(--color-text-muted); +} + +.m02-dfver__rest .ui-btn { + color: var(--color-text-muted); +} diff --git a/M02_MasterTemplete/DataFlow/M02_DF_Verify.ts b/M02_MasterTemplete/DataFlow/M02_DF_Verify.ts index 9c1e422bd..b19f747bb 100644 --- a/M02_MasterTemplete/DataFlow/M02_DF_Verify.ts +++ b/M02_MasterTemplete/DataFlow/M02_DF_Verify.ts @@ -24,6 +24,11 @@ import "./M02_DF_Verify.css"; const TEXT = { Breaks: ["끊긴 곳", "Broken spots"], BreaksHint: ["누르면 그 상자 · 선을 고름", "Click to select the box or line"], + Rest: ["쉬는 갈래", "Resting branches"], + RestHint: [ + "조건이 안 맞아 쉬는 곳 — 누르면 그 선을 고름", + "Idle because a condition is not met — click to select the line", + ], Design: ["설계 때 정할 것", "To decide at design time"], DesignHint: ["여기서 바꾸면 그 상자도 바뀜", "Changing here changes the box too"], Vars: ["변수 예시값", "Variable samples"], @@ -333,33 +338,45 @@ export function renderVerify( /* 끊긴 곳 */ const spots = breaks(lg, opts.values ?? {}, { logics: opts.logics }); - const spotBlock = spots.length - ? [ - el("div", { - className: "m02-dfver__block m02-dfver__breaks", + const spotList = (list: typeof spots, hint: TextKey): HTMLElement[] => [ + el("ul", { + className: "m02-dfver__list", + children: list.map((s) => + el("li", { children: [ - el("strong", { text: tv("Breaks") }), - el("ul", { - className: "m02-dfver__list", - children: spots.map((s) => - el("li", { - children: [ - createButton({ - label: s.text, - variant: "ghost", - size: "sm", - disabled: !s.box || !on.select, - onClick: () => on.select?.({ box: s.box, port: s.port, link: s.link }), - }), - ], - }), - ), + createButton({ + label: s.text, + variant: "ghost", + size: "sm", + disabled: !s.box || !on.select, + onClick: () => on.select?.({ box: s.box, port: s.port, link: s.link }), }), - el("p", { className: "m02-dfver__muted", text: tv("BreaksHint") }), ], }), - ] - : []; + ), + }), + el("p", { className: "m02-dfver__muted", text: tv(hint) }), + ]; + const problems = spots.filter((s) => s.level !== "쉼"); + const resting = spots.filter((s) => s.level === "쉼"); + const spotBlock = [ + ...(problems.length + ? [ + el("div", { + className: "m02-dfver__block m02-dfver__breaks", + children: [el("strong", { text: tv("Breaks") }), ...spotList(problems, "BreaksHint")], + }), + ] + : []), + ...(resting.length + ? [ + el("div", { + className: "m02-dfver__block m02-dfver__rest", + children: [el("strong", { text: tv("Rest") }), ...spotList(resting, "RestHint")], + }), + ] + : []), + ]; /* 설계 때 정할 것 — Model `designInputs`(변수 · 재료 자리 · 다른 로직) + 재료 조건 자리 */ const designs = designInputs(lg);