Files
Aislo/resources/tester/helper_322_3_flow.cjs
T
eomsangdonandClaude Opus 5.5 cb4bab7bf2 feat(구조물표): 323-5 고름 한 칸 — 관 세션 id(옮겨도 그대로 · 저장 안 함) · 고름 강조 한 규칙(pickLights · 카드 형상 · 3D 같은 함수) · 그린 것마다 신원표(data-structure-id + data-part) · 같은 자리 거리 = PIPE_MATCH_M 하나 · 322-0 이 깬 옛 시험 도움 파일 · 줄 수 고침
- `design_pipe_ids.ts` — 장부(세션 pipe-ids) · pipe-s{차례} · 자리 → id(찾는 거리 안 가장 가까운 것) · followMovedPipe 가 id 를 새 자리로(고름 id 그대로 · 순환 import 없게 design_selection 에서 옮김) · 설계 페이지가 프로젝트를 정함(usePipeIds) · 자리 이름 만들기 열 곳 · 되읽기 둘 걷음 · 측점 계획 주인 = 세션 id(withPipeIds)
- `design_pick_match.ts` — pickLights(① id ② 같은 자리 ③ 고른 부분) · paintPicked(카드 통째 형상 · 부분 · 관 계열 · 포장층은 제 손잡이) · tagShape — 관 세트 벽 · 집수정 · 관 · 세월교 측벽 · 몸체 · BOX 끝 · C군 벽 · 물넘이 콘크리트에 신원표 · 형상 판정면은 통째 형상 · 콘크리트만
- 3D — 부재 맞춤 = pickLights(옛 0.01 걷음) · C군 벽 솔리드 = 구조물 id(덮는 측점 어디서 골라도 한 번 스윕한 벽이 켜짐)
- 322-0 이 깬 것 고침 — 노면공 흐름 도움 파일(165 · 231-1) 정의 판 확인 흉내 · design_view_profile 700줄(322-0 이 704)
- 시험: test_323_5_pick(세션 id · 옮김 · 강조 규칙 · 칠하기) · 옛 시험 도움 파일 흉내 · 기대 글 고침

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

242 lines
9.1 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);
await drafts.withDraftShapes(detail, "P"); // 그림 자료 = 형상 얹기 한 벌(323-4)
return summary(detail);
}
(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);
});