Files
Aislo/resources/tester/helper_322_3_flow.cjs
T
eomsangdonandClaude Opus 5.5 d9fec937aa fix(B05·B06 한 페이지): 시설 옮김 = 횡단 편집 열쇠째 · [저장] 중 먹이기 멈춤 · 새 시설 측점 미리보기 걷음 · 되돌리기 뒤 다시 견줌 (415-1 · 415-2)
- H1: 관 · 구조물 밖 측점이 움직인 때만 「노선 바뀜」 넘김 규칙 · 시설만 옮기면 열쇠째 옮김
  (사면 쉬프트 · 소단 깊이 · 단면 유형 · 관 측점 고침 그대로)
- H1 덧: 초안 없을 때도 저장분 계획으로 열쇠 주인(관 = 저장 id · 계산 묶음 열쇠와 같음 · 첫 옮김도 열쇠째)
  · 묶음이 세운 열쇠 목록을 같은 측점이면 주인 규칙으로 다시 세지 않음 · 갈아끼운 옛 상세 맞춤 끝냄
- H2: [저장] 도중 관 · 구조물 먹이기 · 맞춤 고리 쉼 · 끝나면 저장 목록 다시 읽고 맞춤(design_pipeline_facilities)
- M4: 새 시설 측점 서버 미리보기(preview-stations) 걷음 · 새 자리 = 계산 묶음 갈아끼움 · 카드 이름만 얹음
  · 종류 바뀐 자리 = 구조물 없는 사본
- 415-2: 되돌리기 · 다시하기(세션만 되씀) 뒤 관 · 구조물을 묶음 입력과 견줘 다시 넣음
- 시험: test_415_page_split 새로 · 미리보기 전제 시험(273-2 · 298 · 317 · 318 · 322-0 · 322-3)을 새 규칙으로

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TteK8mfYZetk5P3bEZkyVZ
2026-10-18 13:14:59 +09:00

261 lines
10 KiB
JavaScript

/* PLAN 322-3 카드 목록 흐름 헬퍼 — 진짜 `B06_Section_Section_Drafts.ts`(withDraftStations) + 측점 계획 ·
* 구조물 표 · 관 세트 산식을 그 자리에서 트랜스파일해 Node 로 돌림(서버 · 세션만 흉내) · 결과 = 표준출력 JSON.
* 흐름 일곱 = 놓기 · 일반 측점에 놓기(219) · 옮기기 · 같은 군 바꿈 · 정본 넘는 바꿈 · 지우기 · 저장 뒤 다시 열기.
* 짝 = `test_322_3_flow.py`.
*/
const fs = require("fs");
const path = require("path");
const ROOT = path.join(__dirname, "..", "..");
const ts = require(path.join(ROOT, "config", "node_modules", "typescript"));
const DEF = JSON.parse(
fs.readFileSync(path.join(ROOT, "resources", "master_template", "구조물정의.json"), "utf8"),
);
const TYPES = DEF.types.map((t) => ({ managed_by: null, enabled: true, ...t }));
const state = { savedPipes: [], savedStructures: [], draftPipes: null, draftStructures: null };
const memory = new Map();
const store = {
getItem: (k) => (memory.has(k) ? memory.get(k) : null),
setItem: (k, v) => memory.set(k, String(v)),
removeItem: (k) => memory.delete(k),
key: (i) => [...memory.keys()][i] ?? null,
get length() {
return memory.size;
},
};
globalThis.window = { sessionStorage: store, localStorage: store, addEventListener: () => {} };
globalThis.sessionStorage = store;
globalThis.localStorage = store;
const file = (...parts) => path.resolve(ROOT, ...parts);
const STUBS = new Map([
[
file("B04_PreProcess", "B04_PreProcess_Api_Fetch.ts"),
{ fetchDetailPipePoints: async () => ({ pipe_points: state.savedPipes }) },
],
[
file("B05_Profile", "B05_Profile_Api_Structures.ts"),
{
fetchStructures: async () => ({ structures: state.savedStructures }),
fetchStructureTypes: async () => TYPES,
readPendingStructures: () => state.draftStructures,
},
],
[
file("B05_Profile", "B05_Profile_Api_Pipes_Draft.ts"),
{ readPendingPipes: () => state.draftPipes },
],
[
file("B06_Section", "B06_Section_Api_Fetch.ts"),
{
previewStructureSections: async (_p, chainages) =>
chainages.map((at) => ({ chainage_m: at, kind: "irregular", station_id: `p${at}` })),
},
],
]);
const cache = new Map();
function load(target) {
const full = path.resolve(target);
if (STUBS.has(full)) return STUBS.get(full);
if (cache.has(full)) return cache.get(full);
if (full.endsWith(".json")) return JSON.parse(fs.readFileSync(full, "utf8"));
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 need = (spec) => {
if (spec.startsWith("@ui/")) return { showToast: () => {} };
const alias = spec.startsWith("@util/") ? path.join(ROOT, "common_util", spec.slice(6)) : null;
const base = alias ?? path.join(path.dirname(full), spec);
const hit = [`${base}.ts`, base.replace(/\.js$/, ".ts"), base].find(
(p) =>
(STUBS.has(path.resolve(p)) || fs.existsSync(p)) &&
!fs.statSync(p, { throwIfNoEntry: false })?.isDirectory(),
);
if (!hit) throw new Error(`모름: ${spec} (${full})`);
return load(hit);
};
new Function("exports", "module", "require", js)(box.exports, box, need);
return box.exports;
}
load(file("common_util", "common_util_structure_catalog.ts")).useCatalogDefinition(DEF);
const drafts = load(file("B06_Section", "B06_Section_Section_Drafts.ts"));
const grid = () =>
Array.from({ length: 11 }, (_v, i) => ({
chainage_m: i * 20,
kind: "regular",
station_id: `g${i * 20}`,
}));
const irregular = (at, structure, extra = {}) => ({
chainage_m: at,
kind: "irregular",
station_id: `i${at}`,
structure,
...extra,
});
const wall = (type_id, id = "w1") => ({
structure_id: id,
type_id,
placement: "interval",
start_m: 93,
chainage_m: 101,
end_m: 113,
options: { height_m: 3 },
});
const summary = (detail) =>
detail.cross_sections.map((s) => [
s.chainage_m,
s.structure ?? null,
!!s.preview,
["culvert", "ford", "box"].filter((k) => s[k]),
]);
let route = 0;
async function run(setup, detail) {
Object.assign(state, {
savedPipes: [],
savedStructures: [],
draftPipes: null,
draftStructures: null,
...setup,
});
drafts.forgetDraftStations("P");
route += 1;
await drafts.withDraftStations(detail, "P", route);
swapIn(detail);
await drafts.withDraftStations(detail, "P", route); // 갈아끼움 뒤 다시 그림 = 다시 맞춤
await drafts.withDraftShapes(detail, "P"); // 그림 자료 = 형상 얹기 한 벌(323-4)
return summary(detail);
}
/** 415 M4 — 새 자리 = 계산 묶음 측점 갈아끼움 흉내(측점 계획 자리 중 상세에 없는 것 · 기본 설계 · 서버 안 물음). */
function swapIn(detail) {
const plan = load(file("common_util", "common_util_structure_station.ts"));
const { registryDefaults } = load(file("common_util", "common_util_culvert_sets.ts"));
const planned = plan.planStations(
state.draftPipes ?? state.savedPipes,
state.draftStructures ?? state.savedStructures,
TYPES,
registryDefaults(TYPES),
);
const near = (a, b) => Math.abs(a - b) < plan.SAME_STATION_M;
for (const { chainage_m: at } of planned)
if (detail.cross_sections.every((s) => !near(s.chainage_m, at)))
detail.cross_sections.push({ chainage_m: at, kind: "irregular", station_id: `p${at}` });
detail.cross_sections.sort((a, b) => a.chainage_m - b.chainage_m);
}
(async () => {
const out = {};
out.place = await run({ draftStructures: [wall("retaining_wall")] }, { cross_sections: grid() });
out.gridPlace = await run(
{
draftStructures: [
{
structure_id: "u1",
type_id: "cross_underdrain",
placement: "point",
chainage_m: 100,
options: {},
},
],
draftPipes: [{ chainage_m: 120, options: { pipe_diameter_mm: "800" } }],
},
{ cross_sections: grid() },
);
const pipeSet = { culvert: { type: "pipe" } };
out.move = await run(
{
savedPipes: [{ chainage_m: 61, options: { pipe_diameter_mm: "1000" } }],
draftPipes: [{ chainage_m: 75, options: { pipe_diameter_mm: "1000" } }],
},
{ cross_sections: [...grid(), irregular(61, "파형강관 D1000", pipeSet)] },
);
const walls = () => [irregular(93, "옹벽"), irregular(101, "옹벽"), irregular(113, "옹벽")];
out.sameGroup = await run(
{ savedStructures: [wall("retaining_wall")], draftStructures: [wall("soil_guard")] },
{ cross_sections: [...grid(), ...walls()] },
);
out.crossOwner = await run(
{
savedPipes: [{ chainage_m: 101, options: { pipe_diameter_mm: "1000" } }],
draftPipes: [],
draftStructures: [
{
structure_id: "u2",
type_id: "cross_underdrain",
placement: "point",
chainage_m: 101,
options: {},
},
],
},
{ cross_sections: [...grid(), irregular(101, "파형강관 D1000", pipeSet)] },
);
const kept = { cross_sections: [...grid(), ...walls()] };
out.remove = await run({ savedStructures: [wall("retaining_wall")], draftStructures: [] }, kept);
// 저장 뒤 다시 열기 — 초안 없음 = 서버 목록 그대로(빼 둔 옛 자리 돌아옴)
out.reopen = await run({ savedStructures: [wall("retaining_wall")] }, kept);
// 관을 옮겨도 고름 유지(322-3) — 고른 관이면 새 자리 · id = 관 세션 id 그대로(323-5) · 다른 고름은 그대로
const sel = load(file("A00_Common", "design", "design_selection.ts"));
const pipeIds = load(file("A00_Common", "design", "design_pipe_ids.ts"));
sel.selectDesign("P", { chainageM: 61, structureId: pipeIds.pipeIdAt(61, "P") });
pipeIds.followMovedPipe("P", 61, 75);
const moved = sel.readSelection("P");
sel.selectDesign("P", { chainageM: 101, structureId: "w1" });
pipeIds.followMovedPipe("P", 101, 120);
out.follow = [moved, sel.readSelection("P")];
// 옛 소수 관(검증_sub1 견본) — B06 을 바로 열어도 B05 와 같은 정수 m 초안(`snapPlan` · 요구 167)
const snap = load(file("B05_Profile", "B05_Profile_UI_Structures_Snap.ts"));
const DEC = [
85.05, 173.09, 258.12, 352.14, 440.24, 533.98, 619.71, 720.44, 804.18, 899.91, 982.64,
];
const ford = new Set([173.09, 352.14]);
const decPipes = DEC.map((at) => ({
chainage_m: at,
facility: ford.has(at) ? "ford_bridge" : "pipe",
options: { pipe_diameter_mm: "800" },
}));
const plan = snap.snapPlan([], decPipes);
out.snapTo = [plan.pipes.map((move) => move.to), plan.skipped];
const snapped = decPipes.map((pipe) => ({
...pipe,
chainage_m: plan.pipes.find((move) => move.from === pipe.chainage_m)?.to ?? pipe.chainage_m,
}));
const gridTo = (end) =>
Array.from({ length: end / 20 + 1 }, (_v, i) => ({
chainage_m: i * 20,
kind: "regular",
station_id: `g${i * 20}`,
}));
const titleAt = (at) => (ford.has(at) ? "세월교" : "파형강관 D800");
const own = [85, 173, 258, 352, 534, 804, 983];
const onGrid = [440, 620, 720, 900];
// 서버가 정수 자리로 심은 저장분(격자 자리는 구조물 측점 하나 · grid) — 새 미리 뜬 카드 0
const savedInt = [
...gridTo(1000).filter((s) => !onGrid.includes(s.chainage_m)),
...onGrid.map((at) => irregular(at, "파형강관 D800", { grid: true })),
...own.map((at) => irregular(at, at === 173 || at === 352 ? "세월교" : "파형강관 D800")),
];
out.snapSavedInt = await run(
{ savedPipes: decPipes, draftPipes: snapped, draftStructures: [] },
{ cross_sections: savedInt },
);
// 소수 자리로 심긴 저장분(검증_sub1 지금) = B05 를 먼저 연 것과 같음 — 소수 카드는 빼 두고 정수 자리 미리 뜸
const savedDec = [...gridTo(1000), ...DEC.map((at) => irregular(at, titleAt(at)))];
out.snapSavedDec = await run(
{ savedPipes: decPipes, draftPipes: snapped },
{ cross_sections: savedDec },
);
process.stdout.write(JSON.stringify(out));
})().catch((error) => {
process.stderr.write(String(error?.stack ?? error));
process.exit(1);
});