diff --git a/M02_MasterTemplete/DataFlow/M02_DF_InputPanel_Build.ts b/M02_MasterTemplete/DataFlow/M02_DF_InputPanel_Build.ts index 541fd358c..3125278d4 100644 --- a/M02_MasterTemplete/DataFlow/M02_DF_InputPanel_Build.ts +++ b/M02_MasterTemplete/DataFlow/M02_DF_InputPanel_Build.ts @@ -236,19 +236,28 @@ export function checkMaterial(el: CfElementRef, others: CfElementRef[]): string return null; } -/** 다른 로직에서 읽어 둘 것 — 내보내는 알약 = 출력 상자마다 · 받는 알약 = 설계 때 정할 것(변수 · 재료 자리) */ +/** 다른 로직에서 읽어 둘 것 — 내보내는 알약 = 출력 상자마다 · 받는 알약 = 설계 때 정할 것(변수 · 재료 자리 · 이어지지 않은 다른 로직 받는 알약) + * examples = 받는 알약마다 그 로직 자기 예시값(선이 없을 때 값 차례의 끝) */ export function logicRefOf(key: string, other: CfLogic): CfLogicRef { const outputs = other.boxes .filter((b) => b.kind === "output") .map((b) => ({ name: b.name, unit: b.output?.unit || b.unit || "-" })); const designs: { name: string; unit: string }[] = []; + const examples: Record = {}; + const addDesign = (name: string, unit: string, example?: number | string | null): void => { + designs.push({ name, unit: unit || "-" }); + if (example !== null && example !== undefined && example !== "") examples[name] = example; + }; for (const b of other.boxes) { - if (b.kind !== "input" || !b.input) continue; - if (b.input.mode === "variable") - for (const v of b.input.variables) designs.push({ name: v.name, unit: v.unit || "-" }); - for (const e of b.input.elements) - if (e.mode === "choice" || e.mode === "rule") - designs.push({ name: e.slot || e.name, unit: e.unit || "-" }); + for (const v of b.input?.variables ?? b.func?.variables ?? []) + addDesign(v.name, v.unit, v.example); + if (b.input?.mode === "group") + for (const e of b.input.elements) + if (e.mode === "choice" || e.mode === "rule") addDesign(e.slot || e.name, e.unit); + if (b.input?.mode === "logic") + for (const d of b.input.logic?.designs ?? []) + if (!other.links.some((l) => l.to.box === b.id && l.to.name === d.name)) + addDesign(d.name, d.unit, b.input.logic?.examples?.[d.name]); } const seen = new Set(); return { @@ -256,9 +265,93 @@ export function logicRefOf(key: string, other: CfLogic): CfLogicRef { name: other.name, outputs: outputs.filter((o) => !seen.has(o.name) && !!seen.add(o.name)), designs: designs.filter((d) => (seen.has(`d:${d.name}`) ? false : !!seen.add(`d:${d.name}`))), + ...(Object.keys(examples).length ? { examples } : {}), }; } +/* ── 마스터 로직 고르기 — 목록 · 거름 · 쪽 · 순환 막음(36-6) ── */ + +/** 마스터 로직 한 줄 — M01 `fetchLogics` 의 줄(자기 시험 로직 DF-… 도 같은 꼴로 합침) */ +export interface MasterLogicBrief { + file: string; + 구분: string; + 상세구분: string; + 키: string; + 원문번호: string; + 이름: string; + 결과단위: string; + blocked?: boolean; + reasons?: string[]; +} + +/** 찾기 — 띄어쓰기로 나눈 말을 모두 포함(이름 · 원문번호 · 키 · 구분 · 상세구분 · 띄어쓰기는 가리지 않음) · 원문(구분) · 장(상세구분) 거름 */ +export function filterLogics( + items: MasterLogicBrief[], + q: { text: string; book?: string; chapter?: string }, +): MasterLogicBrief[] { + const words = q.text.split(/\s+/).map(squash).filter(Boolean); + return items.filter((i) => { + if (q.book && i.구분 !== q.book) return false; + if (q.chapter && i.상세구분 !== q.chapter) return false; + const hay = squash(`${i.이름} ${i.원문번호} ${i.키} ${i.구분} ${i.상세구분}`); + return words.every((w) => hay.includes(w)); + }); +} + +/** 원문(구분) 목록 · 장(상세구분) 목록 — 나온 차례대로 */ +export function logicBooks(items: MasterLogicBrief[]): string[] { + return [...new Set(items.map((i) => i.구분).filter(Boolean))]; +} +export function logicChapters(items: MasterLogicBrief[], book: string): string[] { + return [ + ...new Set( + items + .filter((i) => !book || i.구분 === book) + .map((i) => i.상세구분) + .filter(Boolean), + ), + ]; +} + +/** 옛 로직을 흐름도로 바꿔 읽은 것은 출력 상자가 비목별이라 노무비 · 재료비 · 경비 · 계 알약 넷으로 보임 */ +export const COST_OUTPUTS = ["노무비", "재료비", "경비", "계"] as const; + +/** 마스터 로직 고른 것 → 다른 로직 입력 값 — 흐름이면 출력 상자 · 변환이면 비용 넷 */ +export function masterRef( + key: string, + flow: CfLogic, + converted: boolean, + resultUnit: string, +): CfLogicRef { + const ref = logicRefOf(key, flow); + return converted + ? { ...ref, outputs: COST_OUTPUTS.map((name) => ({ name, unit: resultUnit || "-" })) } + : ref; +} + +/** 이 로직이 자기 자신을 부르거나 돌고 도나 — `load` 가 로직을 주면 입력 상자의 「다른 로직」 을 따라가 봄(깊이 8) */ +export async function wouldCycle( + selfKey: string | null, + startKey: string, + load: (key: string) => Promise, + depth = 8, +): Promise { + if (!selfKey) return false; + const seen = new Set(); + const walk = async (key: string, left: number): Promise => { + if (key === selfKey) return true; + if (seen.has(key) || left <= 0) return false; + seen.add(key); + const lg = await load(key).catch(() => null); + for (const b of lg?.boxes ?? []) { + const next = b.input?.mode === "logic" ? b.input.logic?.key : undefined; + if (next && (await walk(next, left - 1))) return true; + } + return false; + }; + return walk(startKey, depth); +} + /** 다른 로직 입력 넣기 · 빼기(null) — 빼면 변수 상태로 돌아가고 이 상자의 알약에 닿은 선은 모두 지움 */ export function setLogicInput(lg: CfLogic, id: string, ref: CfLogicRef | null): CfLogic { return {