/* PLAN 439 헬퍼 — 저장 노선 cross 차례(`refreshSavedCross`)가 카드 단추(복합경사 · 측구 · 지반)를 늘 다시 셈: * 묶음 측점 = 상세 측점(돌려줌) · 다름 1 mm 넘게(null = 묶음 엔진 · 카드 자료는 고침) · 서버 폴백에 카드 선택 실음. * node helper_266_card_toggle.mjs <묶음.mjs> * 짝 = `test_266_card_toggle_mismatch.py` · 표준출력 JSON 한 벌. */ import { readFileSync } from "node:fs"; import { pathToFileURL } from "node:url"; const store = () => { const m = new Map(); return { getItem: (k) => (m.has(k) ? m.get(k) : null), setItem: (k, v) => m.set(k, String(v)), removeItem: (k) => m.delete(k), key: (i) => [...m.keys()][i] ?? null, get length() { return m.size; }, clear: () => m.clear(), }; }; globalThis.window = globalThis; globalThis.sessionStorage = store(); globalThis.localStorage = store(); const any = new Proxy(function () {}, { get: (_t, k) => (k === Symbol.toPrimitive ? () => 0 : any), apply: () => any, construct: () => any, }); globalThis.document = any; globalThis.requestAnimationFrame = (run) => setTimeout(run, 0); globalThis.matchMedia = () => ({ matches: false, addEventListener() {}, removeEventListener() {} }); globalThis.addEventListener = () => undefined; globalThis.removeEventListener = () => undefined; globalThis.screen = { width: 1920, height: 1080 }; globalThis.location = { href: "http://localhost/", pathname: "/", search: "", hash: "", origin: "http://localhost", }; const job = JSON.parse(readFileSync(process.argv[3], "utf8")); // 서버 폴백 창구만 받아 적음 — 그 밖 요청은 404(격자 없음 = 넓힘 안 함) const previews = []; globalThis.fetch = async (url, init) => { if (String(url).includes("/cross-design/preview")) { previews.push(JSON.parse(init.body)); return Response.json({ designs: [] }); } return new Response("{}", { status: 404 }); }; const m = await import(pathToFileURL(process.argv[2]).href); const R = 7; const EDITS = { station_offsets: {}, curve_radii: {} }; const alignment = { stations: job.sections.map((s) => ({ chainage_m: s.chainage_m, plan_elevation_m: s.plan_z })), samples: job.sections.map((s) => ({ chainage_m: s.chainage_m, elevation_m: s.plan_z })), balance: { cut_area_m2: 0, fill_area_m2: 0, net_area_m2: 0 }, }; const detailOf = (withLine) => ({ longitudinal: withLine ? { length_m: 1000, stations: [], profile_alignment: { policy: {} } } : { length_m: 1000, stations: [] }, cross_sections: job.sections.map(({ plan_z, ...section }) => ({ ...structuredClone(section), design: { ground_type: "ripping_rock", section_mode: job.mode, design_elevation_m: plan_z, }, })), }); const pick = (design) => ({ ground_type: design.ground_type, two_stage_slope: design.two_stage_slope ?? null, ditch_enabled: design.ditch_enabled ?? null, cut: design.cut_area_m2 ?? null, fill: design.fill_area_m2 ?? null, line: JSON.stringify(design.design_line ?? null), }); /** 카드 단추 줄 차례 — 처음 셈 → 복합경사 켬 → 측구 끔 → 지반 흙. `shift` = 묶음 측점 어긋남(m). */ async function toggles(projectId, shift) { m.writeState("std-cross", job.standard, projectId); m.rememberRockBoundaryDefault(projectId, job.rockDefault); const detail = detailOf(true); const target = detail.cross_sections[job.target]; const stations = detail.cross_sections.map((s, i) => ({ key: `s${i}`, chainageM: s.chainage_m + shift, })); const steps = []; const run = async (tag) => { const updated = []; let calls = 0; // 받는 쪽 = 카드 다시 그림 + 종단 · 유토 다시 그림(design_shared) — 한 걸음에 한 번 const out = await m.refreshSavedCross( { projectId, routeId: R, detail, edits: EDITS, alignment }, stations, (rows) => (calls++, updated.push(...rows)), ); steps.push({ tag, out: out ? Object.keys(out.byKey).length : null, updated, calls, ...pick(target.design), }); }; await run("open"); const choice = { ground_type: "ripping_rock", section_mode: job.mode, ditch_side: null, ditch_type: "standard", paved: false, two_stage_slope: false, ditch_choice: null, }; for (const [tag, over] of [ ["two_stage", { two_stage_slope: true }], ["ditch_off", { ditch_choice: false }], ["soil", { ground_type: "soil" }], ]) { Object.assign(choice, over); m.writeCrossDesignChoice(projectId, R, target.chainage_m, { ...choice }); await run(tag); } // 사면 이동 한 걸음(B06 ◀▶ 0.1 m · 깎기 쪽 · 441 · 요구 268-4) m.writeShoulderExtend(projectId, R, target.chainage_m, job.mode.split("_")[0], 0.1); await run("slope"); return { target: target.chainage_m, steps }; } const out = { same: await toggles("p266a", 0), mismatch: await toggles("p266b", 0.002) }; /* 선형 저장분 없음 — 옛 서버 폴백 걷음(511-B B-4) · 부름 0 */ { const projectId = "p266c"; m.writeState("std-cross", job.standard, projectId); const detail = detailOf(false); const at = detail.cross_sections[job.target].chainage_m; m.writeCrossDesignChoice(projectId, R, at, { ground_type: "soil", section_mode: job.mode, two_stage_slope: true, ditch_choice: false, }); await m.refreshSavedCross({ projectId, routeId: R, detail, edits: EDITS }, [], () => undefined); out.server = { at, calls: previews.length }; } process.stdout.write(JSON.stringify(out));