/* 측점 · 구조물 고름 한 곳 단위검증 헬퍼(PLAN 24-2d) — TS 를 그 자리에서 트랜스파일해 Node 로 돌린다. * 결과는 표준출력에 JSON 한 벌. 화면 그릇은 쓰지 않는다(세션 저장소와 즉시 실행 프레임만 흉내). * * ⚠ 확장자가 `.cjs` 인 까닭은 `helper_b05_structures_form_summary.cjs` 머리와 같다. */ const fs = require("fs"); const path = require("path"); const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); const ROOT = path.join(__dirname, "..", ".."); const cache = new Map(); const session = new Map(); const fakeStorage = { getItem: (k) => session.get(k) ?? null, setItem: (k, v) => session.set(k, String(v)), removeItem: (k) => session.delete(k), }; globalThis.window = { sessionStorage: fakeStorage, localStorage: fakeStorage }; globalThis.sessionStorage = fakeStorage; globalThis.localStorage = fakeStorage; globalThis.requestAnimationFrame = (fn) => fn(); function loadTs(file) { const full = path.resolve(file); if (cache.has(full)) return cache.get(full); 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.exports); const localRequire = (request) => request.startsWith(".") ? loadTs(path.join(path.dirname(full), `${request}.ts`)) : require(request); new Function("exports", "module", "require", js)(box.exports, box, localRequire); cache.set(full, box.exports); return box.exports; } const sel = loadTs(path.join(ROOT, "A00_Common", "design", "design_selection.ts")); const pickPage = loadTs(path.join(ROOT, "B06_Section", "B06_Section_UI_Page_Structure_Pick.ts")); const pickSession = loadTs( path.join(ROOT, "B05_Profile", "B05_Profile_UI_Structure_Pick_Session.ts"), ); const out = { matchM: sel.PIPE_MATCH_M }; const P = "proj-1"; // ── 풀기: 관 → 구조물 순 · 한 거리 ─────────────────────────────────────── // 옹벽(구간 200~210) · 독립 기슭막이(구간 80~90) · BOX/세월교/배수관은 관 정본 const structures = [ { structure_id: "wall", placement: "interval", start_m: 200, end_m: 210, chainage_m: 205 }, { structure_id: "revet", placement: "interval", start_m: 80, end_m: 90, chainage_m: 85 }, ]; const pipes = [{ chainage_m: 173.09 }, { chainage_m: 205.2 }, { chainage_m: 300 }]; const at = (chainageM, structureId = null) => sel.resolveSelection({ chainageM, structureId }, pipes, structures); out.card = { // 횡단 카드 측점이 관 측점에서 0.05 ~ 0.51 m 어긋나도 그 관을 찾는다(옛 B05 폼은 0.05 라 놓쳤다) pipeNear: at(173.4), pipeFar: at(173.09 + 0.6), wallStart: at(200), wallMiddle: at(207), wallEdge: at(210.4), revet: at(85), none: at(250), cleared: at(null), // 같은 측점에 관과 구조물이 겹치면 관이 먼저(횡단도에 그려지는 것) pipeBeatsStructure: at(205.0), // 구조물 id 가 주어지고 목록에 있으면 측점보다 id 가 이긴다 · 없는 id 는 측점으로 되돌아간다 byId: at(300, "revet"), staleId: at(200, "gone"), }; // ── 세션 값 + 알림 ───────────────────────────────────────────────────── const heard = []; const off = sel.subscribeSelection(P, (s, from) => heard.push({ ...s, from })); sel.selectDesign(P, { chainageM: 205, structureId: "wall" }, { partKey: "own", from: "b06" }); out.store1 = sel.readSelection(P); sel.selectDesign(P, { chainageM: 205, structureId: "wall" }, { from: "b06" }); out.sameQuiet = heard.length; // 같은 값 · 부재키 생략 → 알리지 않음 sel.selectDesign(P, { chainageM: 173.09, structureId: null }, { from: "b05" }); out.keyKept = sel.readSelection(P)?.partKey; // 부재키를 안 주면 지킨다(옛 규칙) sel.selectDesign(P, sel.NO_SELECTION, { from: "b05" }); out.cleared = sel.readSelection(P); out.heardFrom = heard.map((h) => [h.chainageM, h.structureId, h.from]); off(); sel.selectDesign(P, { chainageM: 1, structureId: null }); out.afterOff = heard.length; out.noProject = sel.readSelection(null); // 알림 도중 다시 낸 값은 적기만 한다(되울림 방지) let echoes = 0; const off2 = sel.subscribeSelection(P, () => { echoes += 1; sel.selectDesign(P, { chainageM: 2, structureId: null }); }); sel.selectDesign(P, { chainageM: 3, structureId: null }); out.echoes = echoes; out.lastWritten = sel.readSelection(P)?.chainageM; off2(); sel.selectDesign(P, sel.NO_SELECTION); // ── 넘김값 읽기 ──────────────────────────────────────────────────────── sel.selectDesign(P, { chainageM: 205, structureId: "wall" }, { partKey: "right" }); out.handoff = pickSession.readStructurePick(P); sel.selectDesign(P, sel.NO_SELECTION); out.handoffNone = pickSession.readStructurePick(P); // ── B06 진입 때 카드 찾기 ────────────────────────────────────────────── const cards = [80, 173.09, 200, 205.2, 220].map((c) => ({ chainage_m: c, station_id: `s${c}`, ford: null, box: null, })); const detail = { cross_sections: cards }; const run = (handoff, list) => { const calls = []; calls.applied = pickPage.applyStructurePick( handoff, detail, { focusStation: (id) => calls.push(["focus", id]), refreshCard: (c) => calls.push(["refresh", c]), revetSelect: (c, k) => calls.push(["revet", c, k]), fordSelect: (c, k) => calls.push(["ford", c, k]), boxSelect: (c, k) => calls.push(["box", c, k]), }, list, ); return calls; }; const midWall = { at: 207, structureId: "wall" }; // 구간 한가운데 — 카드 위치가 아님 out.retry = { // 구조물 목록이 아직 안 실렸으면 카드를 못 찾고 false → 부르는 쪽이 나중에 다시 부른다 before: run({ at: 260 }, []).applied, // 목록이 실린 뒤 같은 넘김값은 카드를 찾는다 after: run(midWall, structures).applied, notFound: run({ at: 260 }, structures).applied, emptyHandoff: run(null, structures).applied, }; out.apply = { pipeOffsetCard: run({ at: 173.4 }, structures), wallByAnchor: run({ at: 205, structureId: "wall" }, structures), wallNoId: run({ at: 207 }, structures), revetCardFirst: run({ at: 85, structureId: "revet", key: "own" }, structures), unknown: run({ at: 260 }, structures), none: run(null, structures), }; // ── B05 진입 때 되살리기 — 구조물 id 가 있을 때만 구조물로 연다 ───────────── const restoreRun = (pickValue) => { sel.selectDesign(P, sel.NO_SELECTION); if (pickValue) sel.selectDesign(P, pickValue.selection, { partKey: pickValue.key }); const calls = []; let opened = false; const section = { hasSelection: () => opened, selectAt: (s) => { calls.push(["selectAt", s.chainageM, s.structureId]); opened = true; }, }; const controls = { select: (p) => calls.push(["3d", p.chainageM, p.key ?? null]) }; const done = pickSession.restoreStructurePick(controls, P, section, (c) => calls.push(["station", c]), ); return { calls, done }; }; out.restore = { pipeOnly: restoreRun({ selection: { chainageM: 85.05, structureId: null } }), structure: restoreRun({ selection: { chainageM: 205, structureId: "wall" }, key: "own" }), nothing: restoreRun(null), }; sel.selectDesign(P, sel.NO_SELECTION); console.log(JSON.stringify(out));