Files
Aislo/resources/tester/helper_b05_station_roundtrip.cjs
T
eomsangdonandClaude Opus 5.5 08e493f132 feat(B05·B06·B07): 소수 측점 횡단 — 관 · 구조물 자리마다 따로 · 이름 소수 2자리 · 화면 표기만 정수
PLAN 20장 소수 측점 횡단(sub6). 사용자 확정: 관 · 구조물이 놓인 소수 측점(예 119.8)에 그 자리 횡단을
따로 둠 · 백엔드 · 파일 · 저장값은 소수 그대로 · 화면 표기만 「측점 + 거리」의 거리를 정수로(5+20).
- 이름 규칙 한 벌(B05_Profile_Engine_Sections): cross_key · cross_filename · parse_cross_key ·
  CROSS_KEY_PATTERN — 정수 m 는 옛 이름 그대로(cross_00120m) · 소수 측점은 cross_00119.80m.
  find_cross_file = 읽기용(새 이름이 없으면 옛 정수 m 이름) · prune_stale_cross_files 도 이것으로
  가려 옛 프로젝트의 정수 이름 파일을 지우지 않음
- generate_sections: 정수 m 격자 붙임을 풂 — 같은 이름이 되는 0.005m 안 겹침만 하나로
- B06: 찾기 · 패치 입력(Router_Design 둘) · 샘플 넓힘(SampleExtend)이 find_cross_file ·
  DB 설계 찾기 · 병합(get_cross_section_design · merge_…_by_round)은 ROUND(chainage_m)=정수 →
  0.005m 안 매칭
- B07: 도면 id · 장 id(cross_{key}m · cross_s{key}m) 정규식 · 측점 키(cross_station — 정수는 int
  그대로라 옛 id · 수량표 키 안 바뀜) · 측점은 파일 속 값으로(옛 정수 이름 파일도) · 횡단 파일 찾기
- 화면 표기 한 벌 common_util/common_util_station.ts(stationLabel · stationPartsLabel · 순수 함수) —
  common_util_svg 가 다시 내보내고 B05 formatStation 을 대신함(B05 목록 · 표시 · 폼 범위 · 계획노선
  편집 · 비정규 측점 라벨 · B06 카드 · 종단 · 유토) · 입력 칸 · 저장값은 소수 그대로
- B05_Profile_UI_Structures_Panel 700줄 → 690(순수 도우미 셋을 Panel_Types 로)
- M02_Table_Fill 관연장 찾기는 이미 측점 값(정확 먼저 · 0.5m 안)이라 그대로
시험: test_b05_station_roundtrip(표기 5+20 · 옛 정수 이름 찾기 · 안 지움 · 119.8 과 120 따로) ·
test_b05_extra_stations_supplier(새 규칙) · test_b06_patch_station(119.8 한 건) ·
test_b05_b06_save_roundtrip(보낸 관 측점마다 소수 이름 횡단 파일 있음) 등 고친 대상 108 통과 ·
test_b05_station_merge 통과 · 타입검사 통과.
ORCA(IP 5181 · 사본 프로젝트 sub6_검증 — 끝나고 DB 행 · 폴더 지움 · 원본 안 씀): 노출형 횡단수로를
5+19.8 에 놓고 저장 → structures.json 119.8 · 종단 측점 119.8(irregular) + 120 · 파일
cross_00119.80m.json 과 cross_00120m.json · B05 목록 「5+20」 · B06 카드 「5+20 · 119.8m」 열림.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VVM3xZ1aUPcUTuW2WZSnwg
2026-09-29 11:54:51 +09:00

117 lines
4.3 KiB
JavaScript

/* 구조물 측점 왕복 단위검증 헬퍼 — `B05_Profile_UI_Structures_Fields.ts`(측점 칸)와
* `B05_Profile_UI_Profile_Edit.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 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) =>
request.startsWith(".")
? loadTs(path.join(path.dirname(full), `${request}.ts`))
: require(request);
new Function("exports", "module", "require", js)(box.exports, box, localRequire);
cache.set(full, box.exports);
return box.exports;
}
// 측점 칸이 쓰는 만큼만 흉내 낸 DOM
function fakeElement() {
const listeners = {};
const classes = new Set();
return {
value: "",
children: [],
style: {},
dataset: {},
classList: {
add: (c) => classes.add(c),
remove: (c) => classes.delete(c),
toggle: (c, on) => (on ? classes.add(c) : classes.delete(c)),
contains: (c) => classes.has(c),
},
setAttribute() {},
append(...kids) {
this.children.push(...kids);
},
appendChild(kid) {
this.children.push(kid);
return kid;
},
addEventListener: (type, fn) => (listeners[type] = listeners[type] || []).push(fn),
fire: (type) => (listeners[type] || []).forEach((fn) => fn({ type })),
};
}
global.document = { createElement: () => fakeElement(), createTextNode: () => fakeElement() };
const { stationFields } = loadTs(
path.join(ROOT, "B05_Profile", "B05_Profile_UI_Structures_Fields.ts"),
);
const { createProfileEditStore } = loadTs(
path.join(ROOT, "B05_Profile", "B05_Profile_UI_Profile_Edit.ts"),
);
const INTERVAL = 20;
const out = {};
const fields = stationFields("기준", () => INTERVAL);
// ① 실어 둔 값은 칸을 안 고치면 그대로(표시는 6 + 3.4)
fields.write(123.37, INTERVAL);
out.shown = [fields.station.value, fields.remainder.value];
out.untouched = fields.read(INTERVAL, true);
fields.write(123.3721, INTERVAL);
out.untouchedRaw = fields.read(INTERVAL, true);
// 사용자가 놓은 119.8 — 다시 열면 5 + 19.8 로 보이고 그대로 읽힘
fields.write(119.8, INTERVAL);
out.r119 = [fields.station.value, fields.remainder.value, fields.read(INTERVAL, true)];
fields.write(123.3721, INTERVAL);
// 사용자가 잔여를 고치면 친 값으로
fields.remainder.value = "5.2";
fields.remainder.fire("change");
out.edited = fields.read(INTERVAL, true);
// 같은 글로 되돌려 치면 — 여전히 실어 둔 값(칸 글이 쓴 그대로)
fields.write(123.37, INTERVAL);
fields.remainder.value = "3.4";
out.retypedSame = fields.read(INTERVAL, true);
// 넘침 정규화(1 + 30 → 2 + 10)는 친 값으로
fields.station.value = "1";
fields.remainder.value = "30";
fields.remainder.fire("change");
out.normalized = [fields.station.value, fields.remainder.value, fields.read(INTERVAL, true)];
// 비운 폼은 읽기 null
fields.write(null, INTERVAL);
out.cleared = fields.read(INTERVAL, false);
// ② 계획고 편집 — 측점이 0.005m 안으로 옮겨 오면 편집을 옮기고, 아니면 지움
const saved = { station_offsets: { 123.372: 0.5, "200.000": 0.3 }, curve_radii: { 123.372: 40 } };
const store = createProfileEditStore(null, saved, () => {});
store.resetStation(123.372, 123.37);
out.moved = store.edits();
store.resetStation(200);
out.deleted = store.edits();
// 화면 표기 한 벌 — 거리는 정수로 보이기만(PLAN 20장 · 119.8 → 5+20)
const { stationLabel, stationPartsLabel } = loadTs(
path.join(ROOT, "common_util", "common_util_station.ts"),
);
out.labels = [
stationLabel(119.8, INTERVAL),
stationLabel(120, INTERVAL),
stationLabel(84.3, INTERVAL),
stationLabel(123.37, INTERVAL),
stationPartsLabel(5, 19.8),
];
process.stdout.write(JSON.stringify(out));