Files
Aislo/resources/tester/helper_216_1_b05_station_panel.cjs
T
eomsangdonandClaude Opus 5.5 59f2d4463a fix(B05): 종단 측점선 고름 → 좌측 구조물 폼 = B06 카드 고름과 같은 규칙 · 같은 함수(216-1)
- 까닭: syncIrregularSelection 이 일반 측점이면 폼을 안 건드리고 끝남(옛 폼 남음 · 덮는 옹벽 안 열림) · 비정규 측점도 관 전용 selectPipeByChainage 만 불러 A군 수동 구조물 안 열림
- 측점 고름 포트 openAtStation = 폼 selectAt(resolveSelection) + coversAtStation(노면공 뺌) — B06 showAtChainage 와 한 규칙
- 고름 한 칸은 폼 동기화 뒤에 그 측점으로(폼 알림이 구조물 id 를 덮어 적지 않게 · 188-1)
- coversAtStation 을 design_selection 한 곳에 두고 B06 coverable 도 그것으로
- 새 시험 test_216_1_b05_station_panel

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JywHxQuWPk6UYXTebyrBoU
2026-10-10 15:31:59 +09:00

194 lines
7.4 KiB
JavaScript

/* 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 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;
// 비정규 측점 모듈은 설정 별칭(@config)을 끌어오므로 쓰는 두 함수만 같은 꼴로 흉내
const STUBS = {
"./B05_Profile_UI_IrregularStations": {
irregularStationId: (id) => `irregular:${id}`,
isPipeStation: (s) => s.origin === "pipe",
},
};
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) =>
STUBS[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 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));