- 「예상노선으로」를 「초기화」로 — 마지막에 적용한 초기값(처음엔 예상노선 추종)으로 편집값만 되돌림 · 즉시 재계산 없앰 - 옛 「초기화」(창을 연 상태로)는 「연 상태로」 - 로직 단추 토공 최소 · 기울기 순한 노선 · 비중 계산(예상 · 토공 · 기울기 슬라이더 · 합 100 자동 · 기본 50/25/25 · [계산]) - 누르면 서버 갈래(POST /route/initial)를 불러 받은 노드를 편집값에 싣고(되돌리기 한 걸음) 결과 캐시(routeedit-initial)에 새 초기값으로 적음 - 「현재 초기값: ○○」 · 결과 비교값(연장 · 최대/평균 기울기 · 절성토량 · 기준 초과 구간 · 이격) · 공통 제약 기준값은 서버 값만 보임(확정 표시 없으면 「후보 · 사용자 확정 대기」) - 다시 계산은 노선이 바뀐 뒤 [확인]에서만 — 그대로면 계산 없이 닫음 · [확인] 몸통에 초기값(initial) 실음 · [취소]는 이 창에서 바꾼 초기값도 되돌림 - 서버 길이 아직 없으면 알림만(고친 적 없는 노선의 [초기화]는 창을 연 노선으로) - 700줄 한도 — 노드 끼우기·빼기 배선은 _Input(bindNodeSplice), 셈은 _Initial_Model - 시험 test_b05_routeedit_initial.py (비중 합 100 · 서버 답 두 꼴 펴기 · 글 · 캐시 칸) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015sCeZz8z8YnbovsKgn7HK7
163 lines
5.1 KiB
JavaScript
163 lines
5.1 KiB
JavaScript
/* 계획노선 편집 창 **초기노선 갈래**(계획서 20) 셈 단위검증 헬퍼 —
|
|
* `B05_Profile_UI_RouteEdit_Initial_Model.ts` 를 그 자리에서 트랜스파일해 Node 로 돌린다.
|
|
*
|
|
* ⚠ 확장자가 `.cjs` 인 까닭은 `helper_b05_cut_merged_loop.cjs` 머리에 적힌 것과 같다.
|
|
* 브라우저 전용인 저장소는 가짜로 채운다 — 셈만 본다.
|
|
*/
|
|
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 memoryStorage() {
|
|
const map = new Map();
|
|
return {
|
|
getItem: (key) => (map.has(key) ? map.get(key) : null),
|
|
setItem: (key, value) => map.set(key, String(value)),
|
|
removeItem: (key) => map.delete(key),
|
|
key: (index) => [...map.keys()][index] ?? null,
|
|
get length() {
|
|
return map.size;
|
|
},
|
|
_map: map,
|
|
};
|
|
}
|
|
const session = memoryStorage();
|
|
globalThis.window = { localStorage: memoryStorage(), sessionStorage: session };
|
|
globalThis.fetch = () => Promise.resolve({ ok: false });
|
|
|
|
const STUBS = { "@ui/ui_template_palette": { themeColor: (_name, fallback) => fallback } };
|
|
|
|
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 (STUBS[request]) return STUBS[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 model = loadTs(path.join(ROOT, "B05_Profile", "B05_Profile_UI_RouteEdit_Initial_Model.ts"));
|
|
const { DEFAULT_WEIGHTS } = model;
|
|
|
|
// ① 비중 맞추기 — 하나를 옮기면 나머지 둘이 비율대로 나눠 합 100.
|
|
const moves = [
|
|
model.rebalanceWeights(DEFAULT_WEIGHTS, "expected", 70),
|
|
model.rebalanceWeights(DEFAULT_WEIGHTS, "earthwork", 40),
|
|
model.rebalanceWeights({ expected: 100, earthwork: 0, slope: 0 }, "expected", 60),
|
|
model.rebalanceWeights({ expected: 20, earthwork: 30, slope: 50 }, "slope", 100),
|
|
model.rebalanceWeights({ expected: 33, earthwork: 33, slope: 34 }, "earthwork", 33.4),
|
|
model.rebalanceWeights(DEFAULT_WEIGHTS, "slope", -5),
|
|
];
|
|
|
|
// ② 서버 답 펴기 — 노드 목록 꼴과 `/route/plan` 꼴(노드+곡선) 둘 다.
|
|
const plain = model.normalizeRouteInitial(
|
|
{
|
|
nodes: [
|
|
{ x: 0, y: 0 },
|
|
{ x: 50, y: 10, curve: true, radius_m: 30 },
|
|
{ x: 100, y: 0, curve: false },
|
|
],
|
|
metrics: {
|
|
length_m: 1234.56,
|
|
max_grade_pct: 12.34,
|
|
mean_grade_pct: 6.1,
|
|
cut_m3: 1500.4,
|
|
fill_m3: 900,
|
|
over_limit_count: 2,
|
|
offset_mean_m: 3.25,
|
|
},
|
|
criteria: { max_grade_pct: 14, min_radius_m: 12, stream_offset_m: 5 },
|
|
},
|
|
"earthwork",
|
|
null,
|
|
);
|
|
const planShape = model.normalizeRouteInitial(
|
|
{
|
|
nodes: [
|
|
{ x: 0, y: 0, radius_m: null, inner_angle_deg: null, tangent_m: null, violations: [] },
|
|
{ x: 40, y: 30, radius_m: 25, inner_angle_deg: 140, tangent_m: 9, violations: [] },
|
|
{ x: 90, y: 30, radius_m: null, inner_angle_deg: null, tangent_m: null, violations: [] },
|
|
],
|
|
curves: [
|
|
{
|
|
apex: [40, 30],
|
|
radius_m: 25,
|
|
tangent_m: 9,
|
|
inner_angle_deg: 140,
|
|
start: [33, 25],
|
|
end: [49, 30],
|
|
node_first: 1,
|
|
node_last: 1,
|
|
violations: [],
|
|
},
|
|
],
|
|
},
|
|
"weighted",
|
|
{ expected: 60, earthwork: 20, slope: 20 },
|
|
);
|
|
let tooShort = "";
|
|
try {
|
|
model.normalizeRouteInitial({ nodes: [{ x: 1, y: 1 }] }, "gentle", null);
|
|
} catch (error) {
|
|
tooShort = error.message;
|
|
}
|
|
|
|
// ③ 글 — 현재 초기값 · 비교값 · 기준값.
|
|
const labels = {
|
|
follow: model.choiceLabel("follow", null),
|
|
weighted: model.choiceLabel("weighted", { expected: 60, earthwork: 20, slope: 20 }),
|
|
metrics: model.metricsLine(plain.metrics),
|
|
metricsNone: model.metricsLine(null),
|
|
criteria: model.criteriaLine(plain.criteria, 0),
|
|
criteriaConfirmed: model.criteriaLine({ ...plain.criteria, confirmed: true }, 0),
|
|
criteriaFallback: model.criteriaLine(null, 15),
|
|
};
|
|
|
|
// ④ 캐시 왕복 — 결과 통 프로젝트 칸. 꼴이 틀린 값은 안 읽는다.
|
|
model.writeInitialChoice("p-1", plain);
|
|
const readBack = model.readInitialChoice("p-1");
|
|
const other = model.readInitialChoice("p-2");
|
|
const keys = [...session._map.keys()];
|
|
session._map.set(
|
|
keys[0],
|
|
JSON.stringify({
|
|
logic: "nope",
|
|
snapshot: {
|
|
planned: [
|
|
[0, 0],
|
|
[1, 1],
|
|
],
|
|
},
|
|
}),
|
|
);
|
|
const badLogic = model.readInitialChoice("p-1");
|
|
model.writeInitialChoice("p-1", null);
|
|
const cleared = model.readInitialChoice("p-1");
|
|
|
|
process.stdout.write(
|
|
JSON.stringify({
|
|
moves,
|
|
plain,
|
|
planShape,
|
|
tooShort,
|
|
labels,
|
|
readBack,
|
|
other,
|
|
keys,
|
|
badLogic,
|
|
cleared,
|
|
}),
|
|
);
|