Merge remote-tracking branch 'origin/sub_laptop_7' into main_laptop_1

This commit is contained in:
2026-10-01 20:38:48 +09:00
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 }),
+26 -3
View File
@@ -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 아님)