/* 요구 280-3 원인 3 헬퍼 — 노선 초안 동안 초안 측점 맞춤(`withDraftStations` · `withDraftShapes`)이 상세의 틀을 앎. * old = 옛 노선 틀 상세(저장 정본 · 새로고침 첫 화면) → 새 틀 초안 대신 저장분으로 · new = 묶음이 갈아끼운 새 틀 → * 저장분(옛 틀) 종류와 같은 m 로 견주지 않음 · null = 옛 동작(대조). 진짜 TS: Drafts · 측점 계획 · 형상 얹기 · 표. * 결과 = 표준출력 JSON. 짝 = `test_280_3_route_undo_facilities.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 CATALOG = require("./helper_322_catalog_module.cjs"); const S = { savedPipes: [], savedStructures: [], draftPipes: null, draftStructures: null, types: [], }; const STUBS = { design_page_data: { forgetPageData: () => {}, pagePipes: async () => ({ pipe_points: S.savedPipes }), pageStructures: async () => ({ structures: S.savedStructures }), }, B05_Profile_Api_Structures: { fetchStructureTypes: async () => S.types, readPendingStructures: () => S.draftStructures, }, B05_Profile_Api_Pipes_Draft: { readPendingPipes: () => S.draftPipes }, B05_Profile_Api_PlusStations: { readPlusDraft: () => null }, B06_Section_Station_Keys: { useStationKeys: () => {}, useStationOwners: () => {} }, ui_template_elements: { showToast: () => {} }, }; const cache = new Map(); function resolveTs(base) { for (const c of [`${base}.ts`, base]) if (fs.existsSync(c) && fs.statSync(c).isFile()) return c; throw new Error(`없음: ${base}`); } function load(full) { if (cache.has(full)) return cache.get(full).exports; const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, }).outputText; const box = { exports: {} }; cache.set(full, box); const req = (r0) => { if (CATALOG.isCatalogRequest(r0)) return CATALOG; const r = r0.replace(/\.js$/, ""); const stub = STUBS[r.split("/").pop()]; if (stub) return stub; if (r.endsWith(".json")) return JSON.parse(fs.readFileSync(path.join(path.dirname(full), r), "utf8")); if (r.startsWith("@util/")) return load(resolveTs(path.join(ROOT, "common_util", r.slice(6)))); if (r.startsWith("@ui/")) return STUBS[r.split("/").pop()] ?? {}; if (r.startsWith(".")) return load(resolveTs(path.join(path.dirname(full), r))); return require(r); }; new Function("exports", "module", "require", js)(box.exports, box, req); return box.exports; } const P = (...a) => path.join(ROOT, ...a); const D = load(P("B06_Section", "B06_Section_Section_Drafts.ts")); const def = JSON.parse( fs.readFileSync(P("resources", "master_template", "구조물정의.json"), "utf8"), ); S.types = def.types.map((t) => ({ type_id: t.type_id, group: t.group, placement: t.placement, managed_by: t.managed_by, name: t.name, options: (t.options || []).map((o) => ({ key: o.key, label: o.label, default: o.default })), })); const sec = (m, kind, extra = {}) => ({ station_id: `station_${String(Math.round(m * 1000)).padStart(12, "0")}`, chainage_m: m, label: "", kind, center_x: m, center_y: 0, center_z: 100, frame: { left_xy: [0, 1] }, samples: [{ offset_m: 0, elevation_m: 100, valid: true }], design: { design_elevation_m: 100 }, ...extra, }); const detailOf = (marks, len = 300) => { const grid = []; for (let m = 0; m <= len; m += 20) grid.push(m); const all = [...new Set([...grid, ...marks.map((x) => x.m)])].sort((a, b) => a - b); const cross = all.map((m, i) => { const mk = marks.find((x) => x.m === m); const kind = i === 0 ? "bp" : i === all.length - 1 ? "ep" : mk ? "irregular" : "regular"; return sec(m, kind, mk ? { structure: mk.title, ...(m % 20 === 0 ? { grid: true } : {}) } : {}); }); return { longitudinal: { length_m: len, stations: [] }, cross_sections: cross }; }; const show = (detail) => detail.cross_sections .filter((s) => s.kind === "irregular" || s.structure || s.preview) .map( (s) => `${s.chainage_m}:${s.kind}${s.grid ? "/grid" : ""}${s.preview ? "/preview" : ""}=${s.structure ?? "-"}[${Object.keys( s, ) .filter( (k) => ![ "station_id", "chainage_m", "label", "kind", "center_x", "center_y", "center_z", "frame", "samples", "design", "structure", "grid", "preview", ].includes(k), ) .join(",")}]`, ); const ud = (id, m) => ({ structure_id: id, type_id: "cross_underdrain", placement: "point", chainage_m: m, start_m: null, end_m: null, options: {}, }); const rw = (id, s, a, e) => ({ structure_id: id, type_id: "retaining_wall", placement: "interval", chainage_m: a, start_m: s, end_m: e, options: {}, }); /** 상세 하나를 맞춘 뒤 — 숨김(빠진 측점) · 구조물 없는 사본 · 측점마다 카드 이름. */ async function run(saved, draft, marks, frame) { S.savedStructures = saved; S.draftStructures = draft; const detail = detailOf(marks); const before = detail.cross_sections.map((s) => s.chainage_m); await D.withDraftStations(detail, "p", 1, frame); await D.withDraftShapes(detail, "p", false, frame); const after = detail.cross_sections.map((s) => s.chainage_m); return { hidden: before.filter((m) => !after.includes(m)), preview: detail.cross_sections.filter((s) => s.preview).map((s) => s.chainage_m), names: Object.fromEntries( detail.cross_sections.filter((s) => s.structure).map((s) => [s.chainage_m, s.structure]), ), }; } (async () => { const out = {}; // 옛 틀 상세(저장 정본 · 새로고침) + 새 틀 초안(+20 m) — 저장분 자리 그대로여야 함 const oldFrame = [ud("A", 100), rw("B", 153, 160, 167)]; const oldMarks = [ { m: 100, title: "맹암거" }, { m: 153, title: "옹벽" }, { m: 160, title: "옹벽" }, { m: 167, title: "옹벽" }, ]; const shifted = [ud("A", 120), rw("B", 173, 180, 187)]; for (const frame of ["old", null]) out[`old_${frame}`] = await run(oldFrame, shifted, oldMarks, frame); // 새 틀 상세(갈아끼움 뒤) — 옛 틀 B 중심 120 자리에 새 틀 A 가 옴(종류 다름) · 사본 · 숨김 없어야 함 const savedB = [ud("A", 100), rw("B", 113, 120, 127)]; const draftB = [ud("A", 120), rw("B", 133, 140, 147)]; const newMarks = [ { m: 120, title: "맹암거" }, { m: 133, title: "옹벽" }, { m: 140, title: "옹벽" }, { m: 147, title: "옹벽" }, ]; for (const frame of ["new", null]) out[`new_${frame}`] = await run(savedB, draftB, newMarks, frame); process.stdout.write(JSON.stringify(out)); })().catch((error) => { process.stderr.write(String(error && error.stack ? error.stack : error)); process.exit(1); });