feat(m02-df): 36-0b 선 조건 여러 줄(그리고 · 또는) · 값 목록

- CfRule gate conds[{from · op · value}] · join 그리고/또는 · 옛 한 줄 꼴(from · op · value)도 읽음 — 옛 칸은 첫 조건과 같게 둠(makeGate)
- gateConds · makeGate · valueChoices(변수 고르기 · 재료 규격 · 표 칸 값 · 수 · 함수 결과는 null)
- 규칙 → 받는 칸 엑셀 식 IF(AND/OR(...),A1,NA()) · 선 이름표 「지반 = 토사 그리고 높이 ≥ 3 일 때만」 · 겹침 · 안 맞음은 조건 알약 묶음끼리
- 조건 값 열쇠 「상자|알약」 먼저(이름 겹침 막음) · M02_DF_Compute.py gate 같은 뜻(서버 = 화면 시험)
- 시험 test_m02_df_model 30개

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 23:27:04 +09:00
co-authored by Claude Opus 5.5
parent 2c134b440f
commit 2e99091673
5 changed files with 249 additions and 26 deletions
+75 -2
View File
@@ -126,9 +126,27 @@ export type CfGateOp = "=" | "≠" | "≥" | "≤" | ">" | "<";
/** 선 규칙(선 하나에 하나 · 없으면 그대로) — 받는 칸의 엑셀 수식으로 바뀌어 엔진 한 벌로 셈
* lookup = 보낸 표에서 조건 칸마다 맞출 알약 · 꺼낼 칸(범위 칸은 「이름·아래」 「이름·위」)
* gate = 알약 값이 조건을 통과할 때만 보냄 · scale = × ÷ +% -% · round = 끝수 */
/** 조건 통과의 조건 한 줄 */
export interface CfGateCond {
from: CfPort;
op: CfGateOp;
value: number | string;
}
/** 조건 통과 — conds 여러 줄을 join(그리고 · 또는)으로 · 옛 한 줄 꼴(from · op · value)도 읽음
* from · op · value 는 첫 조건과 같게 둠(옛 화면 코드가 그대로 돌게 · makeGate 가 맞춰 줌) — conds 가 있으면 conds 가 이김 */
export type CfGateRule = {
kind: "gate";
conds?: CfGateCond[];
join?: "그리고" | "또는";
from: CfPort;
op: CfGateOp;
value: number | string;
};
export type CfRule =
| { kind: "lookup"; conds: { col: string; from: CfPort }[]; col: string }
| { kind: "gate"; from: CfPort; op: CfGateOp; value: number | string }
| CfGateRule
| { kind: "scale"; op: "×" | "÷" | "+%" | "-%"; value: number }
| ({ kind: "round" } & CfRound);
@@ -357,10 +375,65 @@ export function portDir(box: CfBox, name: string): "in" | "out" {
export function ruleSources(rule: CfRule | undefined): CfPort[] {
if (!rule) return [];
if (rule.kind === "lookup") return rule.conds.map((c) => c.from);
if (rule.kind === "gate") return [rule.from];
if (rule.kind === "gate") return gateConds(rule).conds.map((c) => c.from);
return [];
}
/** 조건 통과의 조건 줄 · 잇는 말 — 옛 한 줄 꼴은 조건 하나 */
export function gateConds(rule: CfGateRule): { conds: CfGateCond[]; join: "그리고" | "또는" } {
const conds = rule.conds?.length
? rule.conds
: [{ from: rule.from, op: rule.op, value: rule.value }];
return { conds, join: rule.join ?? "그리고" };
}
/** 조건 통과 만들기 — 옛 칸(from · op · value)은 첫 조건과 같게 */
export function makeGate(conds: CfGateCond[], join: "그리고" | "또는" = "그리고"): CfGateRule {
const first = conds[0] ?? { from: { box: "", name: "" }, op: "=" as const, value: "" };
return {
kind: "gate",
conds: clone(conds),
join,
from: { ...first.from },
op: first.op,
value: first.value,
};
}
/** 알약이 가질 수 있는 값 목록 — 조건 값 고르기 · null = 숫자 칸(범위 검사)
* 입력 변수 고르기 = choices · 재료 알약 = 그 요소들의 규격 · 표 알약 = opts.col 칸 값들(opts.values 의 표 덩어리)
* · 함수 결과 · 다른 로직 출력 · 수 변수 = null */
export function valueChoices(
lg: CfLogic,
port: CfPort,
opts: { col?: string; values?: Record<string, Record<string, unknown>> } = {},
): (string | number)[] | null {
const box = boxOf(lg, port.box);
const inp = box?.input;
if (!box || box.kind !== "input" || !inp || inp.mode === "logic") return null;
const v = inp.variables.find((x) => x.name === port.name);
if (v) return v.type === "choice" && v.choices?.length ? [...v.choices] : null;
if (port.name === TABLE_PORT && inp.table) {
const block = opts.values?.[box.id]?.[TABLE_PORT];
if (!opts.col || !Array.isArray(block) || !Array.isArray(block[0])) return null;
const head = (block[0] as unknown[]).map(String);
const at = head.indexOf(opts.col);
if (at < 0) return null;
const seen: (string | number)[] = [];
for (const row of block.slice(1) as unknown[][]) {
const x = row[at];
if ((typeof x === "number" || typeof x === "string") && x !== "" && !seen.includes(x))
seen.push(x);
}
return seen.length ? seen : null;
}
const specs = inp.elements
.filter((e) => elementPort(e) === port.name && e.spec.trim())
.map((e) => e.spec.trim());
const uniq = [...new Set(specs)];
return uniq.length ? uniq : null;
}
/** 상자 사이 기댐 — 선 + 선 규칙의 조건 알약 + 재료 조건 찾기의 알약(→ 그 상자) */
export function depends(lg: CfLogic): { from: string; to: string }[] {
const out: { from: string; to: string }[] = [];