/* PLAN 216-1 단위검증 헬퍼 — B05 종단 측점선 고름 → 좌측 구조물 폼이 B06 카드 고름과 같게. * 화면 TS(`B05_Profile_UI_Selection` · `design_selection`)를 그 자리에서 트랜스파일해 Node 로 돌림 · * 결과 = 표준출력 JSON 한 벌. 짝 = `test_216_1_b05_station_panel.py`. * B05 포트 배선은 `design_view_profile.ts` 와 같은 꼴 · B06 은 `showAtChainage` 와 같은 꼴(resolveSelection + coverable). */ const fs = require("fs"); const path = require("path"); const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const ROOT = path.join(__dirname, "..", ".."); 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; // 비정규 측점 모듈은 설정 별칭(@config)을 끌어오므로 쓰는 두 함수만 같은 꼴로 흉내 const STUBS = { "./B05_Profile_UI_IrregularStations": { irregularStationId: (id) => `irregular:${id}`, isPipeStation: (s) => s.origin === "pipe", }, }; const { loadTs } = makeLoader({ root: ROOT, requireFor: (full) => (request) => STUBS[request] ?? (request.startsWith(".") ? loadTs(path.join(path.dirname(full), `${request}.ts`)) : require(request)), }); const sel = loadTs(path.join(ROOT, "A00_Common", "design", "design_selection.ts")); const sync = loadTs(path.join(ROOT, "B05_Profile", "B05_Profile_UI_Selection.ts")); const P = "proj-216"; // 측점 = 일반(s0~s200) + 관 투영(irregular:p1 · 33.4) + A군 수동 횡단수로(irregular:x1 · 140) const cards = [0, 20, 40, 60, 80, 100, 120, 160, 180, 200].map((c) => ({ station_id: `s${c}`, chainage_m: c, })); const irregular = [ { id: "p1", chainage_m: 33.4, origin: "pipe", structure: "배관" }, { id: "x1", chainage_m: 140, structure: "노출형 횡단수로" }, ]; const pipes = [{ chainage_m: 33.4 }]; const types = [ { type_id: "retaining_wall", group: "C" }, { type_id: "cross_channel", group: "A" }, { type_id: "pavement", group: "G" }, ]; const structures = [ { structure_id: "wall", type_id: "retaining_wall", start_m: 60, end_m: 80, chainage_m: 60 }, { structure_id: "chan", type_id: "cross_channel", chainage_m: 140 }, { structure_id: "pave", type_id: "pavement", start_m: 150, end_m: 190, chainage_m: 150 }, ]; const groupOf = (typeId) => types.find((t) => t.type_id === typeId)?.group; /** 좌측 폼 흉내 — B05 `_Structures_Panel` 의 selectPipeByChainage · selectById · selectAt 꼴 그대로. */ function makeForm(onSelect) { const form = { pipe: null, structure: null }; const panel = { form, selectPipeByChainage(c) { if (c === null) { form.pipe = null; return; } const hit = pipes.find((p) => Math.abs(p.chainage_m - c) < sel.PIPE_MATCH_M); if (!hit) return; form.pipe = hit.chainage_m; form.structure = null; onSelect?.(null); }, selectById(id) { if (id === null) { form.pipe = null; form.structure = null; return; } form.pipe = null; form.structure = id; onSelect?.(structures.find((s) => s.structure_id === id) ?? null); }, selectAt(selection, coverable) { const hit = sel.resolveSelection(selection, pipes, structures, coverable); if (hit.pipeChainageM !== null) panel.selectPipeByChainage(hit.pipeChainageM); else panel.selectById(hit.structureId); return hit; }, }; return panel; } const formOf = (panel) => panel.form.pipe !== null ? `pipe-${panel.form.pipe.toFixed(2)}` : panel.form.structure; // ── B05 종단 보기 — design_view_profile 의 포트 배선 그대로 ───────────────────── let syncing = false; const rememberPick = (c, sid) => sel.selectDesign(P, c === null ? sel.NO_SELECTION : { chainageM: c, structureId: sid }, { from: "b05", }); // 폼이 구조물을 열면 view 의 onStructureSelect 가 그 구조물을 고름 한 칸에 적음(동기화 중이면 그 뒤 return) const b05Panel = makeForm((structure) => { if (structure) rememberPick(structure.chainage_m, structure.structure_id); }); const sb = sync.createSelectionSync({ stations: () => irregular, isSyncing: () => syncing, setSyncing: (v) => { syncing = v; }, selectMarker: () => undefined, selectGraph: () => undefined, selectSidebar: (c) => b05Panel.selectPipeByChainage(c), openAtStation: (c) => b05Panel.selectAt({ chainageM: c, structureId: null }, (entry) => sel.coversAtStation(groupOf(entry.type_id)), ), selectBasin: () => undefined, regularChainage: (id) => cards.find((c) => c.station_id === id)?.chainage_m, remember: (c) => rememberPick(c, null), }); // ── B06 카드 고름 — showAtChainage 와 같은 꼴(카드 = 관 투영은 가까운 카드) ────────── const b06Panel = makeForm(); const b06Coverable = (entry) => sel.coversAtStation(groupOf(entry.type_id)); const b06Card = (chainageM) => { const hit = sel.resolveSelection( { chainageM, structureId: null }, pipes, structures, b06Coverable, ); if (hit.pipeChainageM !== null) b06Panel.selectPipeByChainage(hit.pipeChainageM); else b06Panel.selectById(hit.structureId); }; const chainageOf = (id) => irregular.find((s) => `irregular:${s.id}` === id)?.chainage_m ?? cards.find((c) => c.station_id === id)?.chainage_m; /** 차례대로 고름 — 매 걸음 B05 폼 · B06 폼 · 고름 한 칸. */ function run(ids) { b05Panel.form.pipe = b05Panel.form.structure = null; b06Panel.form.pipe = b06Panel.form.structure = null; sel.selectDesign(P, sel.NO_SELECTION); return ids.map((id) => { sb.syncIrregularSelection(id); const at = id === null ? null : chainageOf(id); if (at !== undefined) b06Card(at); // 모르는 측점은 B06 카드가 없음 return { id, b05: formOf(b05Panel), b06: formOf(b06Panel), pick: sel.readSelection(P) }; }); } const out = { pipe: run(["irregular:p1"]), // 관 폼이 열린 채 일반 측점 = 폼 비움(새 배치 준비) — 옛 B05 는 관 폼이 남았음 pipeThenPlain: run(["irregular:p1", "s100"]), // 옹벽 범위 안 일반 측점 = 옹벽 폼 · 고름 한 칸은 그 측점(구조물 id 없음) wallStation: run(["s20", "s60"]), wallMiddle: run(["s80"]), // A군 수동 횡단수로 측점 = 그 구조물 폼 channel: run(["irregular:x1"]), // 노면공만 덮는 측점 = 노면공 안 엶(189-1 · 152-5) — 연 폼은 풂 pave: run(["s60", "s160"]), // 같은 측점 다시 = 풂(null) release: run(["s60", null]), // 모르는 측점 = 폼 · 고름 한 칸 안 건드림 unknown: run(["s60", "ghost"]), covers: { G: sel.coversAtStation("G"), C: sel.coversAtStation("C"), none: sel.coversAtStation(undefined), }, }; process.stdout.write(JSON.stringify(out));