- 옮김: B04 2 · B05 23 · B06 11 · A00_Common/design 13 (9,299 줄) · 이미 logic/ 에 있던 16 파일 그대로 - 안 옮김: 묶음 입구(package.json build:*) · 파이썬 · 순수지만 계산 아닌 8 (보기 결선 · 콜백 · API 타입 · 갈아끼움 알림판) - import 266 줄(142 파일) · 시험 · 주석 · 짝 표시 문자열 경로 고침 · B05 · B06 CLAUDE.md 짝 줄 logic/ - 시험 도우미 새 경로: 가짜 require 열쇠 · B05()/B06() 부름 · 형제 import 자리 · tsc 묶어 돌리는 시험 · 정규식 · rglob - design_view_cross 줄 한도 시험 685 → 688 (긴 import 경로 줄바꿈 3줄) - 확인: typecheck 0 · 서버 묶음 넷(corridor · server-calc · b07-extent · b07-note) 다시 지어 코드 같음(//#region 경로 주석만 다름) · 관련 시험 270 파일 — 남은 실패 19 는 옮기기 전과 같음 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014utrjfoW831Vc5wJUd13kL
176 lines
7.1 KiB
JavaScript
176 lines
7.1 KiB
JavaScript
/* 215-1 종단 [자유지정] 단위검증 헬퍼 — `design_profile_free.ts` 를 그 자리에서 트랜스파일해
|
|
* 범위 · 시작 이음 · 세로선 붙음 · 높이 변환 · 미리 보기 · 기울기 판정 · 종단곡선 · 끝 이음 ·
|
|
* Esc · Backspace · 적용 편집을 Node 로 돌린다.
|
|
*/
|
|
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();
|
|
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) => {
|
|
if (request.startsWith(".")) return loadTs(path.join(path.dirname(full), `${request}.ts`));
|
|
return require(request);
|
|
};
|
|
new Function("exports", "module", "require", js)(box.exports, box, localRequire);
|
|
cache.set(full, box.exports);
|
|
return box.exports;
|
|
}
|
|
|
|
const free = loadTs(path.join(ROOT, "A00_Common", "design", "logic", "design_profile_free.ts"));
|
|
const align = loadTs(path.join(ROOT, "B05_Profile", "logic", "B05_Profile_UI_Profile_Alignment.ts"));
|
|
|
|
// 전체 측점 폴리라인(20m 간격 · 0~200m) — 계획고 = 지반고, 마디마다 기울기가 다르다.
|
|
const stations = Array.from({ length: 11 }, (_, i) => i * 20);
|
|
const ground = stations.map((s, i) => 100 + 0.3 * i + (i % 2 ? 0.4 : 0));
|
|
const base = {
|
|
policy: {
|
|
station_interval_m: 20,
|
|
curve_radius_ratio: 1,
|
|
curve_tangent_max_ratio: 0.5,
|
|
curve_skip_legal_exception: false,
|
|
default_curve_length_m: 20,
|
|
default_curve_radius_m: 300,
|
|
balance_tolerance_percent: 10,
|
|
edit_step_m: 0.1,
|
|
grade_violation_policy: "warn",
|
|
max_grade_pct: 14,
|
|
curve_skip_delta_pct: 0,
|
|
paved: true,
|
|
},
|
|
onlyExplicitCurves: true,
|
|
basePvi: stations.map((s, i) => ({ chainage_m: s, elevation_m: ground[i] })),
|
|
chainage: stations,
|
|
ground,
|
|
stations: stations.map((s) => ({ station_id: `S${s}`, chainage_m: s })),
|
|
};
|
|
|
|
function harness() {
|
|
let edits = align.emptyEdits();
|
|
const log = { applied: [], closed: 0, changed: 0 };
|
|
const draw = free.createFreeDraw({
|
|
base: () => base,
|
|
alignment: () => align.buildAlignment(base, edits),
|
|
edits: () => edits,
|
|
apply: (next) => {
|
|
log.applied.push(next);
|
|
edits = next;
|
|
},
|
|
close: () => (log.closed += 1),
|
|
changed: () => (log.changed += 1),
|
|
});
|
|
return { draw, log, edits: () => edits };
|
|
}
|
|
|
|
const out = {};
|
|
const z0 = (s) => ground[stations.indexOf(s)];
|
|
|
|
// ① 높이 변환 — 세로 창(기준 + 지금 변환) 왕복 · 변환 글 읽기.
|
|
const view = { center: 100, pxPerM: 10, zero: 150, scale: 1.2, offset: -20 };
|
|
out.yAt103_2 = free.chartYAtElevation(view, 103.2);
|
|
out.zBack = free.elevationAtChartY(view, out.yAt103_2);
|
|
out.parsed = free.parseWindowTransform("translate(0 -20) scale(1 1.2)");
|
|
out.parsedNone = free.parseWindowTransform(null);
|
|
|
|
// ② 세로선 붙음 — 가장 가까운 측점 · 범위 안만.
|
|
out.snap79 = free.snapStation(stations, 79);
|
|
out.snapOutside = free.snapStation(stations, 190, [40, 160]);
|
|
|
|
// ③ 범위 → 시작 이음 → 점 → 미리 보기 → 끝 이음.
|
|
{
|
|
const { draw, log, edits } = harness();
|
|
draw.click(41, 0, stations); // 범위 첫 세로선(40)
|
|
out.pendingEdges = draw.marks().edges;
|
|
draw.click(158, 0, stations); // 범위 끝(160)
|
|
out.rangePhase = draw.phase();
|
|
out.band = draw.marks().band;
|
|
draw.click(100, 105, stations); // 범위 가운데 — 시작 아님
|
|
out.stillArmed = draw.phase();
|
|
draw.click(43, 999, stations); // 시작 세로선 근처 — 그 측점 계획고에 이어 시작
|
|
out.startPhase = draw.phase();
|
|
out.startDot = draw.marks().dots[0];
|
|
draw.click(79, 103.456, stations); // 80 에 붙고 높이는 클릭 높이(0.01 단위)
|
|
out.point1 = draw.marks().dots[1];
|
|
draw.click(61, 90, stations); // 뒤로 가는 클릭 — 무시
|
|
out.dotsAfterBack = draw.marks().dots.length;
|
|
draw.hover({ chainageM: 121, elevationM: 101.2, stations });
|
|
out.preview = draw.marks().segments.map((s) => ({
|
|
to: s.to,
|
|
grade: Number(s.grade_pct.toFixed(4)),
|
|
over: s.over,
|
|
preview: s.preview,
|
|
}));
|
|
draw.hover({ chainageM: 121, elevationM: 80, stations }); // 기울기 넘음
|
|
const steep = draw.marks().segments.at(-1);
|
|
out.steep = { grade: Number(steep.grade_pct.toFixed(2)), over: steep.over };
|
|
draw.click(121, 101.2, stations);
|
|
draw.hover({ chainageM: 157, elevationM: 50, stations }); // 끝 세로선 → 그 계획고로 이음
|
|
out.finishPreview = draw.marks().segments.at(-1).to;
|
|
out.hint = draw.hint();
|
|
draw.click(157, 50, stations); // 끝 세로선 근처 = 끝
|
|
out.appliedCount = log.applied.length;
|
|
out.closed = log.closed;
|
|
out.afterPhase = draw.phase();
|
|
const next = edits();
|
|
out.offsetKeys = Object.keys(next.station_offsets).sort();
|
|
out.radiiKeys = Object.keys(next.curve_radii).sort();
|
|
const built = align.buildAlignment(base, next);
|
|
const pvi = (s) => built.pvi.find((n) => Math.abs(n.chainage_m - s) < 1e-6).elevation_m;
|
|
out.pvi = { 40: pvi(40), 60: pvi(60), 80: pvi(80), 100: pvi(100), 120: pvi(120), 140: pvi(140), 160: pvi(160) };
|
|
out.ground = { 40: z0(40), 160: z0(160) };
|
|
const g1 = (103.46 - z0(40)) / 40;
|
|
const g2 = (101.2 - 103.46) / 40;
|
|
out.expectR80 = Number((20 / Math.abs(g2 - g1)).toFixed(6));
|
|
out.r80 = next.curve_radii["80.000"];
|
|
out.curve80L = built.curves.find((c) => Math.abs(c.chainage_m - 80) < 1e-6)?.l_m ?? null;
|
|
out.outsideUntouched = !("20.000" in next.station_offsets) && !("180.000" in next.station_offsets);
|
|
}
|
|
|
|
// ④ Enter = 끝(마지막 점 → 끝 세로선) · 끝 세로선에서 거꾸로 시작.
|
|
{
|
|
const { draw, log, edits } = harness();
|
|
draw.click(40, 0, stations);
|
|
draw.click(160, 0, stations);
|
|
draw.click(162, 0, stations); // 끝 쪽 세로선에서 시작
|
|
out.reverseAnchor = draw.marks().dots[0].chainage_m;
|
|
draw.click(100, 104, stations);
|
|
out.enter = draw.key("Enter");
|
|
out.enterApplied = log.applied.length;
|
|
const built = align.buildAlignment(base, edits());
|
|
const pvi = (s) => built.pvi.find((n) => Math.abs(n.chainage_m - s) < 1e-6).elevation_m;
|
|
out.enterPvi = { 40: pvi(40), 60: pvi(60), 100: pvi(100), 140: pvi(140), 160: pvi(160) };
|
|
}
|
|
|
|
// ⑤ Esc = 취소(편집 없음) · Backspace = 마지막 점 빼기.
|
|
{
|
|
const { draw, log } = harness();
|
|
draw.click(40, 0, stations);
|
|
draw.click(160, 0, stations);
|
|
draw.click(40, 0, stations);
|
|
draw.click(80, 102, stations);
|
|
draw.click(120, 103, stations);
|
|
draw.key("Backspace");
|
|
out.afterBackspace = draw.marks().dots.map((d) => d.chainage_m);
|
|
draw.key("Backspace");
|
|
draw.key("Backspace");
|
|
out.backspaceToArmed = draw.phase();
|
|
draw.click(40, 0, stations);
|
|
draw.click(80, 102, stations);
|
|
out.escape = draw.key("Escape");
|
|
out.escApplied = log.applied.length;
|
|
out.escClosed = log.closed;
|
|
out.escPhase = draw.phase();
|
|
out.escDots = draw.marks().dots.length;
|
|
out.enterOutsideDrawing = draw.key("Enter");
|
|
}
|
|
|
|
process.stdout.write(JSON.stringify(out));
|