/* 387-4b 헬퍼 — 관 옵션 → 횡단 조정값: 상세 가게 얹기(`layPipeOptions` · B05 만 연 페이지) = B06 폼 되보내기 * (`applyPipeOptionsToCache` · 조정창 흉내 = 캐시에 같은 칸으로 씀)인지 · 옵션을 바꿔도 같은지 · 면적으로 견줌. * 짝 = `test_387_4b_pipe_options.py`. */ const fs = require("fs"); const path = require("path"); const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); const CATALOG = require("./helper_322_catalog_module.cjs"); // 구조물 표(322-1) const ROOT = path.join(__dirname, "..", ".."); const cache = new Map(); const STUBS = { "@ui/ui_template_toast": { showToast: () => undefined } }; 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, esModuleInterop: true, }, }).outputText; const box = { exports: {} }; cache.set(full, box.exports); const localRequire = (request) => { if (CATALOG.isCatalogRequest(request)) return CATALOG; if (STUBS[request]) return STUBS[request]; const alias = { "@util/": "common_util", "@ui/": "ui_template", "@config/": "config" }; for (const [prefix, dir] of Object.entries(alias)) if (request.startsWith(prefix)) return loadTs(resolve(path.join(ROOT, dir, request.slice(prefix.length)))); return loadTs(resolve(path.join(path.dirname(full), request))); }; new Function("exports", "module", "require", js)(box.exports, box, localRequire); cache.set(full, box.exports); return box.exports; } const resolve = (base) => [base, `${base}.ts`, path.join(base, "index.ts")].find( (p) => fs.existsSync(p) && fs.statSync(p).isFile(), ) ?? `${base}.ts`; function memoryStorage() { const map = new Map(); return { getItem: (k) => (map.has(k) ? map.get(k) : null), setItem: (k, v) => map.set(k, String(v)), removeItem: (k) => map.delete(k), key: (i) => [...map.keys()][i] ?? null, get length() { return map.size; }, }; } globalThis.window = { sessionStorage: memoryStorage(), localStorage: memoryStorage() }; const B06 = (f) => path.join(ROOT, "B06_Section", `${f}.ts`); const { structureAreaRows, computeStoredLayouts } = loadTs(B06("B06_Section_Structure_Layouts")); const { layPipeOptions } = loadTs(B06("B06_Section_Section_Adjusts")); const { applyPipeOptionsToCache } = loadTs(B06("B06_Section_UI_Page_Pipe_Options")); const { ZERO_ADJUST } = loadTs(B06("B06_Section_UI_Cross_Culvert_Types")); const fresh = () => JSON.parse(fs.readFileSync(path.join(__dirname, "fixtures", "b06_186_1_pipe_292.json"), "utf8")) .cross_sections; const areaOf = (sections) => { const s = sections.find((x) => x.culvert); const row = structureAreaRows(sections).find((r) => r.chainage_m === s.chainage_m); return [row?.cut_area_m2 ?? null, row?.fill_area_m2 ?? null, s.design.revet_adjust ?? null]; }; /** B06 길 — 조정창 흉내(세션 없음 · 캐시 칸에 같은 꼴로 씀 · `storeAdjust` 규칙). */ function b06(sections, options, extra = 0) { const owner = sections.find((x) => x.culvert); owner.design.extra_wall_counts = { outlet: extra, basin: 0 }; const adjustFor = (section, role) => ({ ...ZERO_ADJUST, ...(section.design.revet_adjust?.[role] ?? {}), }); const round1 = (v) => Math.round(v * 10) / 10; const ctx = { detail: () => ({ cross_sections: sections }), ford: {}, box: {}, revetOffset: { adjustFor, update: (at, role, patch) => { const section = sections.find((x) => Math.abs(x.chainage_m - at) < 0.01); const cur = adjustFor(section, role); const next = { ...cur, h: patch.h === undefined ? cur.h : patch.h === null ? null : round1(patch.h), m: patch.m === undefined ? cur.m : patch.m, }; const bucket = { ...(section.design.revet_adjust ?? {}) }; const dflt = Math.abs(next.x) < 1e-9 && next.d == null && next.h == null && next.m == null; if (dflt) delete bucket[role]; else bucket[role] = next; section.design.revet_adjust = bucket; }, }, soilTiers: () => { const probe = { ...owner, design: { ...owner.design } }; return extra ? (computeStoredLayouts(probe, sections)?.culvert?.extraWalls.length ?? extra) : 0; }, overrideOptions: () => undefined, refreshCard: () => undefined, }; applyPipeOptionsToCache(ctx, owner.chainage_m, options, true); return areaOf(sections); } /** 상세 가게 길(B05 만 연 페이지) — 관 초안 · 저장 관의 폼 칸 그대로. */ function store(sections, options, extra = 0) { const owner = sections.find((x) => x.culvert); owner.design.extra_wall_counts = { outlet: extra, basin: 0 }; layPipeOptions({ cross_sections: sections }, [{ chainage_m: owner.chainage_m, options }]); return areaOf(sections); } const at = fresh().find((x) => x.culvert).chainage_m; const first = { inlet_revet_height_m: 1.5, inlet_revet_form: "돌쌓기(찰)", outlet_revet_height_m: 1.5, outlet_revet_form: "돌쌓기(찰)", }; const changed = { ...first, outlet_revet_height_m: 2.4, outlet_revet_form: "옹벽" }; const out = { at, none: areaOf(fresh()) }; for (const [name, options] of Object.entries({ first, changed })) { out[name] = { b06: b06(fresh(), options), store: store(fresh(), options) }; } // 흙막이 단(soil_add) — 유출 다단 두 단 · 높이 · 형태 const tiers = { ...first, soil_add_1_height: 1.2, soil_add_1_form: "돌쌓기(찰)", soil_add_2_height: 0.05, }; out.tiers = { b06: b06(fresh(), tiers, 2), store: store(fresh(), tiers, 2) }; process.stdout.write(JSON.stringify(out));