/* PLAN 322-3 카드 목록 흐름 헬퍼 — 진짜 `B06_Section_Section_Drafts.ts`(withDraftStations) + 측점 계획 · * 구조물 표 · 관 세트 산식을 그 자리에서 트랜스파일해 Node 로 돌림(서버 · 세션만 흉내) · 결과 = 표준출력 JSON. * 흐름 일곱 = 놓기 · 일반 측점에 놓기(219) · 옮기기 · 같은 군 바꿈 · 정본 넘는 바꿈 · 지우기 · 저장 뒤 다시 열기. * 짝 = `test_322_3_flow.py`. */ const fs = require("fs"); const path = require("path"); const ROOT = path.join(__dirname, "..", ".."); const ts = require(path.join(ROOT, "config", "node_modules", "typescript")); const DEF = JSON.parse( fs.readFileSync(path.join(ROOT, "resources", "master_template", "구조물정의.json"), "utf8"), ); const TYPES = DEF.types.map((t) => ({ managed_by: null, enabled: true, ...t })); const state = { savedPipes: [], savedStructures: [], draftPipes: null, draftStructures: null }; const memory = new Map(); const store = { getItem: (k) => (memory.has(k) ? memory.get(k) : null), setItem: (k, v) => memory.set(k, String(v)), removeItem: (k) => memory.delete(k), key: (i) => [...memory.keys()][i] ?? null, get length() { return memory.size; }, }; globalThis.window = { sessionStorage: store, localStorage: store, addEventListener: () => {} }; globalThis.sessionStorage = store; globalThis.localStorage = store; const file = (...parts) => path.resolve(ROOT, ...parts); const STUBS = new Map([ [ file("B04_PreProcess", "B04_PreProcess_Api_Fetch.ts"), { fetchDetailPipePoints: async () => ({ pipe_points: state.savedPipes }) }, ], [ file("B05_Profile", "B05_Profile_Api_Structures.ts"), { fetchStructures: async () => ({ structures: state.savedStructures }), fetchStructureTypes: async () => TYPES, readPendingStructures: () => state.draftStructures, }, ], [ file("B05_Profile", "B05_Profile_Api_Pipes_Draft.ts"), { readPendingPipes: () => state.draftPipes }, ], [ file("B06_Section", "B06_Section_Api_Fetch.ts"), { previewStructureSections: async (_p, chainages) => chainages.map((at) => ({ chainage_m: at, kind: "irregular", station_id: `p${at}` })), }, ], ]); const cache = new Map(); function load(target) { const full = path.resolve(target); if (STUBS.has(full)) return STUBS.get(full); if (cache.has(full)) return cache.get(full); if (full.endsWith(".json")) return JSON.parse(fs.readFileSync(full, "utf8")); const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020, esModuleInterop: true, }, }).outputText; const box = { exports: {} }; cache.set(full, box.exports); const need = (spec) => { if (spec.startsWith("@ui/")) return { showToast: () => {} }; const alias = spec.startsWith("@util/") ? path.join(ROOT, "common_util", spec.slice(6)) : null; const base = alias ?? path.join(path.dirname(full), spec); const hit = [`${base}.ts`, base.replace(/\.js$/, ".ts"), base].find( (p) => (STUBS.has(path.resolve(p)) || fs.existsSync(p)) && !fs.statSync(p, { throwIfNoEntry: false })?.isDirectory(), ); if (!hit) throw new Error(`모름: ${spec} (${full})`); return load(hit); }; new Function("exports", "module", "require", js)(box.exports, box, need); return box.exports; } load(file("common_util", "common_util_structure_catalog.ts")).useCatalogDefinition(DEF); const drafts = load(file("B06_Section", "B06_Section_Section_Drafts.ts")); const grid = () => Array.from({ length: 11 }, (_v, i) => ({ chainage_m: i * 20, kind: "regular", station_id: `g${i * 20}`, })); const irregular = (at, structure, extra = {}) => ({ chainage_m: at, kind: "irregular", station_id: `i${at}`, structure, ...extra, }); const wall = (type_id, id = "w1") => ({ structure_id: id, type_id, placement: "interval", start_m: 93, chainage_m: 101, end_m: 113, options: { height_m: 3 }, }); const summary = (detail) => detail.cross_sections.map((s) => [ s.chainage_m, s.structure ?? null, !!s.preview, ["culvert", "ford", "box"].filter((k) => s[k]), ]); let route = 0; async function run(setup, detail) { Object.assign(state, { savedPipes: [], savedStructures: [], draftPipes: null, draftStructures: null, ...setup, }); drafts.forgetDraftStations("P"); route += 1; await drafts.withDraftStations(detail, "P", route); await drafts.withDraftShapes(detail, "P"); // 그림 자료 = 형상 얹기 한 벌(323-4) return summary(detail); } (async () => { const out = {}; out.place = await run({ draftStructures: [wall("retaining_wall")] }, { cross_sections: grid() }); out.gridPlace = await run( { draftStructures: [ { structure_id: "u1", type_id: "cross_underdrain", placement: "point", chainage_m: 100, options: {}, }, ], draftPipes: [{ chainage_m: 120, options: { pipe_diameter_mm: "800" } }], }, { cross_sections: grid() }, ); const pipeSet = { culvert: { type: "pipe" } }; out.move = await run( { savedPipes: [{ chainage_m: 61, options: { pipe_diameter_mm: "1000" } }], draftPipes: [{ chainage_m: 75, options: { pipe_diameter_mm: "1000" } }], }, { cross_sections: [...grid(), irregular(61, "파형강관 D1000", pipeSet)] }, ); const walls = () => [irregular(93, "옹벽"), irregular(101, "옹벽"), irregular(113, "옹벽")]; out.sameGroup = await run( { savedStructures: [wall("retaining_wall")], draftStructures: [wall("soil_guard")] }, { cross_sections: [...grid(), ...walls()] }, ); out.crossOwner = await run( { savedPipes: [{ chainage_m: 101, options: { pipe_diameter_mm: "1000" } }], draftPipes: [], draftStructures: [ { structure_id: "u2", type_id: "cross_underdrain", placement: "point", chainage_m: 101, options: {}, }, ], }, { cross_sections: [...grid(), irregular(101, "파형강관 D1000", pipeSet)] }, ); const kept = { cross_sections: [...grid(), ...walls()] }; out.remove = await run({ savedStructures: [wall("retaining_wall")], draftStructures: [] }, kept); // 저장 뒤 다시 열기 — 초안 없음 = 서버 목록 그대로(빼 둔 옛 자리 돌아옴) out.reopen = await run({ savedStructures: [wall("retaining_wall")] }, kept); // 관을 옮겨도 고름 유지(322-3) — 고른 관이면 새 자리 · id = 관 세션 id 그대로(323-5) · 다른 고름은 그대로 const sel = load(file("A00_Common", "design", "design_selection.ts")); const pipeIds = load(file("A00_Common", "design", "design_pipe_ids.ts")); sel.selectDesign("P", { chainageM: 61, structureId: pipeIds.pipeIdAt(61, "P") }); pipeIds.followMovedPipe("P", 61, 75); const moved = sel.readSelection("P"); sel.selectDesign("P", { chainageM: 101, structureId: "w1" }); pipeIds.followMovedPipe("P", 101, 120); out.follow = [moved, sel.readSelection("P")]; // 옛 소수 관(검증_sub1 견본) — B06 을 바로 열어도 B05 와 같은 정수 m 초안(`snapPlan` · 요구 167) const snap = load(file("B05_Profile", "B05_Profile_UI_Structures_Snap.ts")); const DEC = [ 85.05, 173.09, 258.12, 352.14, 440.24, 533.98, 619.71, 720.44, 804.18, 899.91, 982.64, ]; const ford = new Set([173.09, 352.14]); const decPipes = DEC.map((at) => ({ chainage_m: at, facility: ford.has(at) ? "ford_bridge" : "pipe", options: { pipe_diameter_mm: "800" }, })); const plan = snap.snapPlan([], decPipes); out.snapTo = [plan.pipes.map((move) => move.to), plan.skipped]; const snapped = decPipes.map((pipe) => ({ ...pipe, chainage_m: plan.pipes.find((move) => move.from === pipe.chainage_m)?.to ?? pipe.chainage_m, })); const gridTo = (end) => Array.from({ length: end / 20 + 1 }, (_v, i) => ({ chainage_m: i * 20, kind: "regular", station_id: `g${i * 20}`, })); const titleAt = (at) => (ford.has(at) ? "세월교" : "파형강관 D800"); const own = [85, 173, 258, 352, 534, 804, 983]; const onGrid = [440, 620, 720, 900]; // 서버가 정수 자리로 심은 저장분(격자 자리는 구조물 측점 하나 · grid) — 새 미리 뜬 카드 0 const savedInt = [ ...gridTo(1000).filter((s) => !onGrid.includes(s.chainage_m)), ...onGrid.map((at) => irregular(at, "파형강관 D800", { grid: true })), ...own.map((at) => irregular(at, at === 173 || at === 352 ? "세월교" : "파형강관 D800")), ]; out.snapSavedInt = await run( { savedPipes: decPipes, draftPipes: snapped, draftStructures: [] }, { cross_sections: savedInt }, ); // 소수 자리로 심긴 저장분(검증_sub1 지금) = B05 를 먼저 연 것과 같음 — 소수 카드는 빼 두고 정수 자리 미리 뜸 const savedDec = [...gridTo(1000), ...DEC.map((at) => irregular(at, titleAt(at)))]; out.snapSavedDec = await run( { savedPipes: decPipes, draftPipes: snapped }, { cross_sections: savedDec }, ); process.stdout.write(JSON.stringify(out)); })().catch((error) => { process.stderr.write(String(error?.stack ?? error)); process.exit(1); });