feat(m02-df): 36-2 옛 로직 → 흐름도 변환 · 표 찾기 옛 find 와 같은 뜻
- M02_DF_Convert.convert(row, ctx={key, file, master}) → {flow, notes} — 옛 파서 나무 → 함수 상자 시트(칸 주소 · 결과 하나) · 표 찾기 lookup · 로직 줄 비목 셋 · 덧줄 비목 합 · 줄.이름 · 결과 = 값 출력 · 자동 배치
- {입력} 낀 요소(EQ:{기계} · LB:{직종} · MO:경유:{지역} · 재료 검색어) = 조건 찾기 field 「키」(Model CfMaterialRule · Api slot 타입) · 자리 여럿은 글 잇는 함수
- 표 찾기: 빈 조건 칸 = 무엇이든 · 꺼낼 칸 빈 줄 건너뜀(Rule · Compute) · 함수 칸으로 가는 실패는 #N/A 를 넣어 넘김 · 결과가 오류일 때만 끊긴 곳(Eval · Compute 같음)
- 전수 대조 test_m02_df_convert(사본 폴더 1,472 로직 · 시험입력 92.9% · 기본 입력 94.3%) → tmp/convert_report_36.json
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VBToTCGk5dqDKeXCEP3vhk
This commit is contained in:
@@ -35,6 +35,10 @@ export type CfValues = Record<string, Record<string, CfValue>>;
|
||||
|
||||
type Scalar = number | string | boolean | null;
|
||||
|
||||
/** 함수 상자 칸으로 가는 표 찾기 실패 값 — 칸에 NA() 를 넣음(엑셀과 같게 · IF 가 안 고른 갈래면 무시) */
|
||||
const NA_VALUE = "#N/A";
|
||||
const isErrorValue = (v: unknown): boolean => typeof v === "string" && /^#[A-Z/0!?]+$/.test(v);
|
||||
|
||||
/** 끊긴 곳 하나 — 상자 · 알약 · 선 위에 표시할 것 */
|
||||
export interface CfBreak {
|
||||
kind:
|
||||
@@ -228,7 +232,11 @@ function runFunc(
|
||||
): Scalar {
|
||||
const func = box.func!;
|
||||
const sheet: Sheet = structuredClone(func.sheet);
|
||||
for (const i of func.inputs) put(sheet.칸, i.cell, arrive[i.port] ?? given[i.port] ?? null);
|
||||
for (const i of func.inputs) {
|
||||
const v = arrive[i.port] ?? given[i.port] ?? null;
|
||||
if (v === NA_VALUE) sheet.칸[i.cell.toUpperCase()] = { 식: "NA()" };
|
||||
else put(sheet.칸, i.cell, v);
|
||||
}
|
||||
for (const v of func.variables)
|
||||
if (v.cell) put(sheet.칸, v.cell, v.name in given ? given[v.name] : v.example);
|
||||
const values = recalcWorkbook({ 종류: "통합문서", 판: 1, 열: "DF", 서식: [{}], 시트: [sheet] });
|
||||
@@ -262,6 +270,8 @@ export function evalLogic(
|
||||
const blocked = new Set<string>();
|
||||
/** 조건이 안 맞아 쉬는 상자 — 그 아래로 번짐 */
|
||||
const resting = new Set<string>();
|
||||
/** 함수 상자로 가는 표 찾기 실패 — 그 함수 결과가 오류일 때만 끊긴 곳(엑셀과 같게) */
|
||||
const late = new Map<string, CfBreak[]>();
|
||||
/** 선 하나로 들어오는 값 — 규칙이 있으면 엔진으로 */
|
||||
const through = (l: CfLink): CfValue => {
|
||||
const v = valueOf(l.from.box, l.from.name);
|
||||
@@ -295,7 +305,22 @@ export function evalLogic(
|
||||
? "ManyRows"
|
||||
: "RuleError";
|
||||
const text = tl(key, fill);
|
||||
found.push({ kind, level: "문제", box: l.to.box, port: l.to.name, link: l.id, text });
|
||||
const brk: CfBreak = {
|
||||
kind,
|
||||
level: "문제",
|
||||
box: l.to.box,
|
||||
port: l.to.name,
|
||||
link: l.id,
|
||||
text,
|
||||
};
|
||||
if (
|
||||
l.rule.kind === "lookup" &&
|
||||
lg.boxes.find((b) => b.id === l.to.box)?.kind === "function"
|
||||
) {
|
||||
late.set(l.to.box, [...(late.get(l.to.box) ?? []), brk]);
|
||||
return NA_VALUE;
|
||||
}
|
||||
found.push(brk);
|
||||
errors.push(text);
|
||||
}
|
||||
if (res.why) blocked.add(l.to.box);
|
||||
@@ -354,9 +379,13 @@ export function evalLogic(
|
||||
} else if (b.kind === "input") ports[b.id] = otherLogic(b, arrive(b), given, opts, errors);
|
||||
else if (b.kind === "function") {
|
||||
const got = arrive(b);
|
||||
ports[b.id] = {
|
||||
[b.func!.output.name]: blocked.has(b.id) ? null : runFunc(b, got, given, errors),
|
||||
};
|
||||
const out = blocked.has(b.id) ? null : runFunc(b, got, given, errors);
|
||||
ports[b.id] = { [b.func!.output.name]: out };
|
||||
if (out === null || isErrorValue(out))
|
||||
for (const brk of late.get(b.id) ?? []) {
|
||||
found.push(brk);
|
||||
errors.push(brk.text);
|
||||
}
|
||||
} else {
|
||||
const v = arrive(b)[OUTPUT_PORT] ?? null;
|
||||
let scalar: Scalar = Array.isArray(v) ? null : v;
|
||||
|
||||
Reference in New Issue
Block a user