feat(m02-df): 끊긴 곳 무게 — 쉼(쉬는 갈래) · 문제

- CfBreak.level: 통과 안 됨 · 그 아래로 번진 빈 값 = 쉼 · 빈 받는 자리 · 표에 없는 조건 · 맞는 재료 없음 · 조건 겹침 · 안 맞음 · 오류 = 문제
- 쉬는 갈래에서 온 빈 값은 받는 상자도 쉼(셈 안 함 · 출력 빈 값 · 문제로 안 셈)
- M02_DF_Compute.py breaks 에 같은 뜻 level 한 칸
- 시험 25개

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VBToTCGk5dqDKeXCEP3vhk
This commit is contained in:
2026-10-01 20:38:21 +09:00
co-authored by Claude Opus 5.5
parent 3abbe74f58
commit 018177408b
3 changed files with 66 additions and 7 deletions
@@ -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"),
+38 -4
View File
@@ -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<string, boolean>();
/** 선 규칙에서 막힌 상자 — 그 함수는 셈하지 않고 빈 값을 넘김(갈래가 끊김) */
const blocked = new Set<string>();
/** 조건이 안 맞아 쉬는 상자 — 그 아래로 번짐 */
const resting = new Set<string>();
/** 선 하나로 들어오는 값 — 규칙이 있으면 엔진으로 */
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 }),