/* 73-4 세월교 · 박스 흙막이 횡단 기하 헬퍼 — 화면 TS(`B06_Section_Structure_Layouts` · `_Cross_Body_Guard`)를 * 그 자리에서 트랜스파일해 Node 로 돌림. 입력 = 픽스처(검증_sub7 상세 일부) · 결과 = 표준출력 JSON 한 벌. * 짝 = `test_73_4_body_guard.py`. */ const fs = require("fs"); const path = require("path"); const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const ROOT = path.join(__dirname, "..", ".."); // 화면 전용 모듈(토스트 · 글) — 기하 계산엔 안 쓰임 const STUBS = { "@ui/ui_template_toast": { showToast: () => undefined }, }; const { loadTs } = makeLoader({ root: ROOT, interop: true, requireFor: (full) => (request) => { 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))); }, }); const resolve = (base) => [base, `${base}.ts`, path.join(base, "index.ts")].find( (p) => fs.existsSync(p) && fs.statSync(p).isFile(), ) ?? `${base}.ts`; const B06 = (f) => path.join(ROOT, "B06_Section", f); const { computeStoredLayouts, structureAreaRows, trimOfLayouts } = loadTs( B06("logic/B06_Section_Structure_Layouts.ts"), ); const { bodyGuardBase } = loadTs(B06("logic/B06_Section_Cross_Body_Guard.ts")); const fixture = JSON.parse( fs.readFileSync(path.join(__dirname, "fixtures", "b06_73_4_body_guard_sections.json"), "utf8"), ); const sections = fixture.cross_sections; const at = (c) => sections.find((s) => Math.abs(s.chainage_m - c) < 0.6); const round = (v) => Math.round(v * 1000) / 1000; /** 벽마다 배면 ↔ 본체 끝 자리 차이(m) · 벽 윗면 ↔ 본체 윗면 표고 차이(79-7 — 벽 배면이 본체 끝에 붙음). */ function fronts(section) { const layouts = computeStoredLayouts(section, sections); const base = bodyGuardBase(section, section.samples); const walls = layouts.culvert?.walls ?? []; return { walls: walls.map((w) => { const end = base.ends[w.role]; return { role: w.role, form: w.form, dOffset: round(w.bottomBack.offset - end.offset), dElevation: round(Math.max(...w.points.map((p) => p.elevation)) - end.top), front: round(w.bottomBack.offset), }; }), bodyX: layouts.culvert?.bodyX ?? null, applied: layouts.culvert?.revetShift ? { inlet: layouts.culvert.revetShift.inlet.x, outlet: layouts.culvert.revetShift.outlet.x } : null, trimSame: JSON.stringify(trimOfLayouts(layouts)) === JSON.stringify(layouts.culvert?.designTrim), pipeLength: null, }; } const out = {}; const box = at(352.14); const ford = at(173.09); out.box = fronts(box); out.ford = fronts(ford); // 박스를 좌우로 1 m 늘리면 벽 전면도 1 m 따라 나감(연동 = 본체 기준 자리) const moved = JSON.parse(JSON.stringify(box)); moved.design.box_adjust = { left: { lengthM: 1, riseM: 0 }, right: { lengthM: 1, riseM: 0 } }; const swapped = sections.map((s) => (s === box ? moved : s)); const movedLayouts = computeStoredLayouts(moved, swapped); const base = bodyGuardBase(moved, moved.samples); out.boxMoved = movedLayouts.culvert.walls.map((w) => ({ role: w.role, dOffset: round(w.bottomBack.offset - base.ends[w.role].offset), front: round(w.bottomBack.offset), })); // 연동 옆 측점(340 · 360) — 소유 측점 벽이 섬 out.linked = [340, 360].map((c) => { const s = at(c); return { chainage: c, walls: computeStoredLayouts(s, sections)?.culvert?.walls.length ?? 0 }; }); // 면적 행 — 서버 Node 진입점과 같은 함수(`structureAreaRows`) out.areas = structureAreaRows(sections).filter((r) => [173.087, 352.14].some((c) => Math.abs(r.chainage_m - c) < 0.6), ); process.stdout.write(JSON.stringify(out));