Files
Aislo/resources/tester/helper_445_save_patch.cjs
T
eomsangdonandClaude Opus 5.5 de0141b8fc refactor(472-6 6-B 한 벌): 노선 바뀜 넘김 규칙 · 세션 고침 얹기 · [저장] patch 바탕을 순수 TS 로 떼어 냄 — 동작 같음
- logic/B06_Section_Route_Carry.ts = 측점 열쇠 목록 · 측점 열쇠 칸 · 누가거리 칸 넘김(Station_Keys 몸통) · Station_Keys = 세션 겉
- B06_Section_Route_Carry_Patch.ts = 조정창 · 옮긴 고침 · 관 옵션 얹기 · crossPatchesFromCache · 서버용 routeCarryPatches(옛 carryAfterRouteSave 차례) · Section_Adjusts · Section_Store = 겉 · 내줌
- 칸 목록 USER_TOUCHED_KEYS · STRUCTURE_ADJUST_KEYS = logic/B06_Section_Design_Keys.ts · PIPE_MATCH_M = common_util_pipe_match.ts(옛 자리는 내줌)
- 시험 헬퍼 · 글 단언 시험 = 옮긴 자리로(목록 한 벌 자리만 바뀜)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 11:33:28 +09:00

199 lines
7.7 KiB
JavaScript

/* PLAN 445 헬퍼 — [저장] 횡단 patch 바탕 · 세션 얹음 · 되돌림 · 옮긴 측점 입력.
* 조정창(`createStationControls`)으로 고친 뒤 횡단 보기 [저장] 본문(`buildCrossPatches` → `cachedCrossPatches`)을
* 그대로 만들어 내놓음 — 시험이 서버 병합(`_apply_section_edits` → `merge_cross_section_design_batches`)에 넣어
* DB 설계를 견줌. TS 는 그 자리에서 트랜스파일 · 화면 · 서버 창구는 가짜. 짝 = `test_445_save_patch.py`.
* node helper_445_save_patch.cjs <job.json>
*/
const fs = require("fs");
const path = require("path");
const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript"));
const ROOT = path.join(__dirname, "..", "..");
const B06 = (name) => path.join(ROOT, "B06_Section", `${name}.ts`);
const memory = () => {
const m = new Map();
return {
getItem: (k) => (m.has(k) ? m.get(k) : null),
setItem: (k, v) => m.set(k, String(v)),
removeItem: (k) => m.delete(k),
key: (i) => [...m.keys()][i] ?? null,
get length() {
return m.size;
},
clear: () => m.clear(),
};
};
globalThis.sessionStorage = memory();
globalThis.localStorage = memory();
globalThis.window = globalThis;
const anything = () =>
new Proxy({}, { get: (_target, key) => (key === "__esModule" ? false : () => undefined) });
function makeLoader(real, stubs = {}) {
const cache = new Map();
const realSet = new Set(real.map((file) => path.resolve(file)));
const load = (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 need = (spec) => {
if (spec in stubs) return stubs[spec];
let target = null;
if (spec.startsWith("@util/")) target = path.join(ROOT, "common_util", `${spec.slice(6)}.ts`);
else if (spec.startsWith(".")) target = path.join(path.dirname(full), `${spec}.ts`);
if (target && realSet.has(path.resolve(target))) return load(target);
return anything();
};
new Function("exports", "module", "require", js)(box.exports, box, need);
return box.exports;
};
return load;
}
const bodyStub = () => ({
control: { selectedFor: () => false, select: () => undefined },
byChainage: () => new Map(),
load: () => undefined,
});
const load = makeLoader(
[
B06("B06_Section_Section_Store"),
B06("B06_Section_Section_Canon"),
B06("B06_Section_Section_Adjusts"),
B06("B06_Section_Cross_Refresh"),
B06("B06_Section_Api_Culvert_Options"),
B06("B06_Section_UI_Page_Station_Controls"),
B06("B06_Section_UI_Page_Link_Session"),
B06("B06_Section_UI_Cross_Culvert_Types"),
B06("logic/B06_Section_Station_Swap"),
B06("logic/B06_Section_UI_Page_Patches"),
B06("logic/B06_Section_Design_Keys"), // 칸 목록 · 얹기 · patch 꼴 한 벌(472-6)
B06("logic/B06_Section_Route_Carry"),
B06("B06_Section_Route_Carry_Patch"),
path.join(ROOT, "common_util", "common_util_pipe_match.ts"),
path.join(ROOT, "A00_Common", "b_page_state.ts"),
path.join(ROOT, "A00_Common", "design", "design_selection.ts"),
],
{
"./B06_Section_Api_Fetch": {
fetchSectionDetail: async () => job.detail,
saveSections: async () => ({}),
},
"./B06_Section_UI_Page_Ford_Controls": {
createBodyControls: () => ({ ford: bodyStub(), box: bodyStub() }),
},
"./B06_Section_UI_Page_Span_Control": {
createSpanControl: () => ({
control: {},
spanKeyOf: (chainageM, wall) => `${chainageM.toFixed(2)}:${wall}`,
}),
},
"./B06_Section_Section_Drafts": {
withDraftStations: async () => undefined,
withDraftShapes: async () => undefined,
forgetDraftStations: () => undefined,
supersedeDraftStations: () => undefined,
},
"./B06_Section_Structure_Layouts": {
applyStructureAreaRows: () => undefined,
structureAreaRows: () => [],
computeStoredLayouts: () => null,
},
"../B05_Profile/B05_Profile_Api_PlusStations": {
loadSavedPlusStations: async () => undefined,
plusStationsOf: () => [],
},
},
);
const job = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
const P = "p445";
const R = 7;
const { stateKey } = load(path.join(ROOT, "A00_Common", "b_page_state.ts"));
const store = load(B06("B06_Section_Section_Store"));
const { buildCrossPatches } = load(B06("logic/B06_Section_UI_Page_Patches"));
const { createStationControls } = load(B06("B06_Section_UI_Page_Station_Controls"));
/** 새 탭 = 빈 세션 · 서버 상세(정본) 받아 캐시에 · 조정창 하나. */
async function freshTab(clearSession = true) {
if (clearSession) globalThis.sessionStorage.clear();
store.invalidateSectionDetail(P, R);
const detail = await store.loadSectionDetail(P, R);
const controls = createStationControls({
sessionKey: (kind) => stateKey(kind, P, R),
refreshCard: () => undefined,
detail: () => detail,
crossHalfWidth: () => undefined,
sampledHalfWidth: () => 20,
ensureSampledWidth: async () => true,
projectId: () => P,
});
controls.load();
return { detail, controls };
}
/** 횡단 보기 [저장] 본문 — 세션 고침(`buildCrossPatches`) → 바탕 위에 얹음(`cachedCrossPatches`). */
function savePatches(controls) {
const overlay = buildCrossPatches({
rockOffsets: new Map(),
cutSlopeRatios: new Map(),
stationWidths: controls.widths,
inletStructures: controls.inletStructures,
basinAdjustments: controls.basinAdjustments,
revetAdjusts: controls.revetAdjustsByChainage(),
extraCounts: controls.extraCountsByChainage(),
extraSpans: controls.extraSpansByChainage(),
fordAdjusts: new Map(),
boxAdjusts: new Map(),
linkFlags: controls.linkFlagsByChainage(),
});
return { overlay, patches: store.cachedCrossPatches(P, R, overlay) };
}
(async () => {
const at = job.at;
const out = {};
// H3 — 유출만 고침 → 유입 · 집수 쪽 단 수 남음
{
const { controls } = await freshTab();
controls.revetOffset.update(at, "outlet", { x: 3 });
out.h3 = savePatches(controls);
}
// M2 — 새 탭 ↺(유입) · 추가 벽 − · 빈 전체 폭 · 집수정 내공 2.1(M1)
{
const { controls } = await freshTab();
controls.revetOffset.reset(at, "inlet");
controls.extraWalls.setCount(at, 0, "outlet");
controls.applyGlobalWidth(undefined, [at]);
controls.inletStructure.updateAdjust(at, { innerWidthM: 2.1, innerHeightM: 0.5 });
out.m2 = savePatches(controls);
out.m2.countShown = controls.extraWalls.countFor({ chainage_m: at, design: {} }, "outlet");
// 저장 전 새로고침(세션은 남고 상세는 정본) — 되돌림이 살아 있어야 함
const again = await freshTab(false);
out.m2reload = savePatches(again.controls);
out.m2reload.cachedInlet = again.detail.cross_sections[0].design.revet_adjust?.inlet ?? null;
out.m2reload.cachedCounts = again.detail.cross_sections[0].design.extra_wall_counts ?? null;
}
// H2 — 정본 행이 없는 자리(옮긴 관)의 캐시 설계 = 지반 등 입력까지 실음
{
globalThis.sessionStorage.clear();
store.invalidateSectionDetail(P, R);
await store.loadSectionDetail(P, R); // 정본 = job.detail(at 한 측점)
const moved = await store.loadSectionDetail(P, R);
moved.cross_sections.push({ ...job.moved });
out.h2 = store.cachedCrossPatches(P, R, []);
moved.cross_sections.pop();
}
process.stdout.write(JSON.stringify(out));
})().catch((error) => {
process.stderr.write(String(error?.stack ?? error));
process.exit(1);
});