refactor(472-5 5-B 나눔): design_pipeline_page.ts 저장 기준(정본 지문 · savedSource · savedEdits · storedPvi · crossNow)을 design_pipeline_page_saved.ts 로 — 동작 같음(694 → 646줄)
- 시험 anchor 한 줄(test_272_detail_memory 화면 선만 바뀜 = 정본 노선 번호를 나눈 파일에서 읽음) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -26,7 +26,6 @@ import { createDesignPipeline, type DesignPipeline } from "./logic/design_pipeli
|
||||
import { publishPagePipeline } from "./design_pipeline_page_publish";
|
||||
import { SOURCE_INPUT } from "./logic/design_pipeline_types";
|
||||
export { pipelineWork } from "./logic/design_pipeline";
|
||||
import { pipelineCrossSource, type PipelineCrossSource } from "./design_pipeline_cross";
|
||||
import { createStationsStage, snapOptions, stationDiff } from "./logic/design_pipeline_stations";
|
||||
import type {
|
||||
ChangeSource,
|
||||
@@ -48,7 +47,6 @@ import type { SectionDetailResponse } from "../../B06_Section/B06_Section_Api_Fe
|
||||
import { createCrossStage } from "../../B06_Section/logic/B06_Section_Cross_Stage";
|
||||
import {
|
||||
emptyEdits,
|
||||
toAlignmentBase,
|
||||
type AlignmentEdits,
|
||||
type ProfileAlignment,
|
||||
} from "../../B05_Profile/logic/B05_Profile_UI_Profile_Alignment";
|
||||
@@ -56,7 +54,6 @@ import {
|
||||
markAlignmentSaved,
|
||||
readAlignmentDraft,
|
||||
} from "../../B05_Profile/B05_Profile_UI_Profile_Edit";
|
||||
import { readAlignment } from "../../B05_Profile/logic/B05_Profile_UI_Profile_Data";
|
||||
import {
|
||||
followPlusDraft,
|
||||
plusStationsOf,
|
||||
@@ -71,6 +68,7 @@ import {
|
||||
import { fetchStructureTypes } from "../../B05_Profile/B05_Profile_Api_Structures";
|
||||
import { forgetPageData, pagePipes, pageStructures, trackPageBusy } from "./design_page_data";
|
||||
import type { PagePipelineOptions, ProfileLink } from "./design_pipeline_page_types";
|
||||
import { createPageSaved } from "./design_pipeline_page_saved";
|
||||
import { feedFacilities } from "./design_pipeline_facilities";
|
||||
export type { PagePipelineOptions, ProfileLink } from "./design_pipeline_page_types";
|
||||
import {
|
||||
@@ -192,24 +190,12 @@ function createPagePipeline(
|
||||
let enabled: PlanType[] = [];
|
||||
let savedStructures: PlanStructure[] = [];
|
||||
let savedPipes: PlanPipe[] = [];
|
||||
/** 저장 노선 측점(열쇠) · 마지막으로 모은 횡단 입력(시설 제원 — 새 노선 상세가 씀). */
|
||||
/** 저장 노선 측점(열쇠) — 저장분을 받을 때 셈. */
|
||||
let savedStations: PipelineStation[] = [];
|
||||
let crossSource: PipelineCrossSource | null = null;
|
||||
/** 저장 정본(서버가 준 상세 · 노선 번호) — 측점 갈아끼움(swap)이 만든 새 상세는 지반 표본 배열을
|
||||
* 그대로 둠 → 정본 아님(묶음 출력 → 상세 → 다시 묶음 고리를 끊음). */
|
||||
let canon: { routeId: unknown; samples: unknown; detail: SectionDetailResponse | null } | null =
|
||||
null;
|
||||
/** 저장 기준(정본 지문 · 종단도 저장 기준 · 계획고 · PVI · 횡단 입력) — `design_pipeline_page_saved`. */
|
||||
const pageSaved = createPageSaved(projectId, link, options, () => savedStations);
|
||||
const { savedSource, canonChanged, savedEdits, storedPvi, crossNow, drafted } = pageSaved;
|
||||
let shown: SectionDetailResponse | null = null;
|
||||
/** 이 묶음이 지은 새 노선 상세들 — 횡단 보기 카드가 그것을 종단도에 그려도 저장 정본이 아님. */
|
||||
const drafted = new WeakSet<SectionDetailResponse>();
|
||||
let lastSaved: ReturnType<ProfileLink["pipelineSource"]> | null = null;
|
||||
/** 종단도가 내준 저장 기준 — 종단도가 묶음 새 노선 상세를 그리는 동안은 그 앞 저장 기준(고리 끊음 ·
|
||||
* 새 노선 상세 → 정본으로 오인 → 처음부터 다시 → 새 상세 … 349-0g). */
|
||||
const savedSource = (): ReturnType<ProfileLink["pipelineSource"]> => {
|
||||
const now = link.pipelineSource();
|
||||
if (now.detail && drafted.has(now.detail)) return lastSaved ?? { ...now, detail: null };
|
||||
return (lastSaved = now);
|
||||
};
|
||||
let ready = false;
|
||||
let started = false;
|
||||
/** 앞 종단이 노선 초안으로 셈한 것인가 — 초안을 버리면 저장 기준으로 다시 그림. */
|
||||
@@ -240,41 +226,6 @@ function createPagePipeline(
|
||||
plan ? plan.planned.map(([x, y]): Vertex => [x, y]) : null;
|
||||
const savedCurves = () =>
|
||||
(plan?.curves ?? []).map(({ start, apex, end, radius_m }) => ({ start, apex, end, radius_m }));
|
||||
/** 저장 정본이 바뀌었나(새로 받은 상세 · 새 노선) — 바뀌었으면 잡아 둠. */
|
||||
const canonChanged = (): boolean => {
|
||||
const { detail = null, routeId } = savedSource();
|
||||
const samples = detail?.longitudinal.samples;
|
||||
if (canon && canon.routeId === routeId && canon.samples === samples) return false;
|
||||
canon = { routeId, samples, detail };
|
||||
return true;
|
||||
};
|
||||
/** 저장 노선 계획고 편집(세션 초안 → 저장분) — 노선 초안을 버리면 이것으로 돌아옴. */
|
||||
const savedEdits = (): AlignmentEdits => {
|
||||
const routeId = canon?.routeId;
|
||||
const longitudinal = canon?.detail?.longitudinal;
|
||||
return (
|
||||
(typeof routeId === "number" ? readAlignmentDraft(routeId) : null) ??
|
||||
(longitudinal && readAlignment(longitudinal)?.edits) ??
|
||||
emptyEdits()
|
||||
);
|
||||
};
|
||||
const storedPvi = () => {
|
||||
const stored = canon?.detail && readAlignment(canon.detail.longitudinal);
|
||||
return stored ? toAlignmentBase(stored).basePvi : null;
|
||||
};
|
||||
/** 횡단 입력 — 부를 때마다 저장 상세 + 세션 고침 · 표준을 모음(상세 객체마다 기억하면 고침이 안 감 · 365-2). */
|
||||
const crossNow = (): PipelineCrossSource | null => {
|
||||
const routeId = canon?.routeId;
|
||||
const source = pipelineCrossSource(
|
||||
projectId,
|
||||
canon?.detail ?? null,
|
||||
savedStations,
|
||||
typeof routeId === "number" ? routeId : null,
|
||||
options.stationKeys, // 초안 자리 측점 짝(457)
|
||||
);
|
||||
if (source) crossSource = source;
|
||||
return source;
|
||||
};
|
||||
const statusOf = (pipes: unknown, structures: unknown) =>
|
||||
shapeStatus(pipes ?? savedPipes, structures ?? savedStructures, types); // 새 측점 그림 · 맥락(457)
|
||||
const groundDeps = groundStageDeps(projectId);
|
||||
@@ -395,7 +346,7 @@ function createPagePipeline(
|
||||
if (pushed && dropPending && !pipe.input("route"))
|
||||
return (lastRun as Promise<PipelineRunRecord>).then(finishDrop);
|
||||
dropPending = false;
|
||||
const routeId = canon?.routeId;
|
||||
const routeId = pageSaved.canonRouteId();
|
||||
const stations = pipe.output("ground")?.stations ?? [];
|
||||
let why: string = record.outcome; // 새 조작에 밀림 · 단계 실패 — 초안 전 상세는 그대로 둠
|
||||
if (record.outcome === "done") {
|
||||
@@ -409,7 +360,7 @@ function createPagePipeline(
|
||||
held = null;
|
||||
const detail = hold?.detail;
|
||||
if (detail) {
|
||||
canon = { routeId, samples: detail.longitudinal.samples, detail };
|
||||
pageSaved.adoptCanon(routeId, detail);
|
||||
const diff = stationDiff(detail, stations);
|
||||
if (diff) record.restoreDiff = diff;
|
||||
}
|
||||
@@ -474,7 +425,7 @@ function createPagePipeline(
|
||||
ground,
|
||||
profile,
|
||||
pipe.output("cross"),
|
||||
crossSource?.specs ?? new Map(),
|
||||
pageSaved.crossSpecs(),
|
||||
options.stationIntervalM ?? profile.policy.station_interval_m,
|
||||
statusOf(pipe.input("pipes"), pipe.input("structures")),
|
||||
)
|
||||
@@ -541,7 +492,7 @@ function createPagePipeline(
|
||||
quietGeneration = inputs.route || drafts ? -1 : pipe.generation;
|
||||
void run
|
||||
.then(() => reconcile()) // 열 때 셈하는 사이 바뀐 입력(세션 · 가게)도 맞춤
|
||||
.then(() => publisher.opened(projectId, openGeneration, canon?.routeId)); // 466
|
||||
.then(() => publisher.opened(projectId, openGeneration, pageSaved.canonRouteId())); // 466
|
||||
};
|
||||
|
||||
/** 묶음에 넘긴 입력(follow 가 고치기 전) · 마지막으로 띄운 차례 — 고리가 견줌 · 기다림. */
|
||||
@@ -563,7 +514,7 @@ function createPagePipeline(
|
||||
let seen: Promise<unknown> | null = null;
|
||||
while (seen !== lastRun) await (seen = lastRun); // 기다리는 사이 뜬 차례까지
|
||||
await pipe.settled();
|
||||
if (disposed || canon?.routeId !== savedSource().routeId) return round; // 정본 바뀜 = restart 몫
|
||||
if (disposed || pageSaved.canonRouteId() !== savedSource().routeId) return round; // 정본 바뀜 = restart 몫
|
||||
if (facilities.saving()) return (facilities.afterSave(), round); // 저장 도중 = 옛 저장분 · 끝나면 다시
|
||||
const now = inputsNow();
|
||||
const stale = (Object.entries(SOURCE_INPUT) as Array<[ChangeSource, keyof PipelineInputs]>)
|
||||
@@ -601,7 +552,7 @@ function createPagePipeline(
|
||||
stationSnap: routePlan.station_snap ?? false,
|
||||
stationSnapStep: routePlan.station_snap_step,
|
||||
});
|
||||
crossSource = null;
|
||||
pageSaved.forgetCross();
|
||||
})
|
||||
.catch(() => undefined);
|
||||
|
||||
@@ -672,7 +623,8 @@ function createPagePipeline(
|
||||
// 갈아끼움 · 같은 상세 다시 그림은 안 셈 · 보기가 심은 측점은 묶음이 입력으로 셈(386-3) · 화면 선만
|
||||
// 바뀜(비정규 측점 도착) = 상세가 그 선 대비 낡았으면 cross 한 번 더(466)
|
||||
if (canonChanged()) void loadPlan().then(start);
|
||||
else if (typeof canon?.routeId === "number") publisher.recheck(projectId, canon.routeId);
|
||||
else if (typeof pageSaved.canonRouteId() === "number")
|
||||
publisher.recheck(projectId, pageSaved.canonRouteId() as number);
|
||||
},
|
||||
earthwork() {
|
||||
const cross = crossNow()?.input ?? null;
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
/* =============================================================================
|
||||
* A00_Common/design/design_pipeline_page_saved.ts
|
||||
* 페이지 묶음의 **저장 기준** — 저장 정본 지문(노선 번호 · 지반 표본) · 종단도가 내준 저장 기준 · 저장 노선
|
||||
* 계획고 편집 · 저장 PVI · 횡단 입력(cross). `design_pipeline_page.ts` 에서 갈라냄(700줄 한계 · 472-5).
|
||||
* ========================================================================== */
|
||||
|
||||
import type { SectionDetailResponse } from "../../B06_Section/B06_Section_Api_Fetch";
|
||||
import {
|
||||
emptyEdits,
|
||||
toAlignmentBase,
|
||||
type AlignmentEdits,
|
||||
} from "../../B05_Profile/logic/B05_Profile_UI_Profile_Alignment";
|
||||
import { readAlignmentDraft } from "../../B05_Profile/B05_Profile_UI_Profile_Edit";
|
||||
import { readAlignment } from "../../B05_Profile/logic/B05_Profile_UI_Profile_Data";
|
||||
import { pipelineCrossSource, type PipelineCrossSource } from "./design_pipeline_cross";
|
||||
import type { PipelineStation } from "./logic/design_pipeline_types";
|
||||
import type { PagePipelineOptions, ProfileLink } from "./design_pipeline_page_types";
|
||||
|
||||
export function createPageSaved(
|
||||
projectId: string,
|
||||
link: ProfileLink,
|
||||
options: PagePipelineOptions,
|
||||
/** 저장 노선 측점(열쇠) — 페이지가 저장분을 받을 때 셈. */
|
||||
savedStations: () => PipelineStation[],
|
||||
) {
|
||||
/** 저장 정본(서버가 준 상세 · 노선 번호) — 측점 갈아끼움(swap)이 만든 새 상세는 지반 표본 배열을
|
||||
* 그대로 둠 → 정본 아님(묶음 출력 → 상세 → 다시 묶음 고리를 끊음). */
|
||||
let canon: { routeId: unknown; samples: unknown; detail: SectionDetailResponse | null } | null =
|
||||
null;
|
||||
/** 마지막으로 모은 횡단 입력(시설 제원 — 새 노선 상세가 씀). */
|
||||
let crossSource: PipelineCrossSource | null = null;
|
||||
/** 이 묶음이 지은 새 노선 상세들 — 횡단 보기 카드가 그것을 종단도에 그려도 저장 정본이 아님. */
|
||||
const drafted = new WeakSet<SectionDetailResponse>();
|
||||
let lastSaved: ReturnType<ProfileLink["pipelineSource"]> | null = null;
|
||||
/** 종단도가 내준 저장 기준 — 종단도가 묶음 새 노선 상세를 그리는 동안은 그 앞 저장 기준(고리 끊음 ·
|
||||
* 새 노선 상세 → 정본으로 오인 → 처음부터 다시 → 새 상세 … 349-0g). */
|
||||
const savedSource = (): ReturnType<ProfileLink["pipelineSource"]> => {
|
||||
const now = link.pipelineSource();
|
||||
if (now.detail && drafted.has(now.detail)) return lastSaved ?? { ...now, detail: null };
|
||||
return (lastSaved = now);
|
||||
};
|
||||
/** 저장 정본이 바뀌었나(새로 받은 상세 · 새 노선) — 바뀌었으면 잡아 둠. */
|
||||
const canonChanged = (): boolean => {
|
||||
const { detail = null, routeId } = savedSource();
|
||||
const samples = detail?.longitudinal.samples;
|
||||
if (canon && canon.routeId === routeId && canon.samples === samples) return false;
|
||||
canon = { routeId, samples, detail };
|
||||
return true;
|
||||
};
|
||||
/** 저장 노선 계획고 편집(세션 초안 → 저장분) — 노선 초안을 버리면 이것으로 돌아옴. */
|
||||
const savedEdits = (): AlignmentEdits => {
|
||||
const routeId = canon?.routeId;
|
||||
const longitudinal = canon?.detail?.longitudinal;
|
||||
return (
|
||||
(typeof routeId === "number" ? readAlignmentDraft(routeId) : null) ??
|
||||
(longitudinal && readAlignment(longitudinal)?.edits) ??
|
||||
emptyEdits()
|
||||
);
|
||||
};
|
||||
const storedPvi = () => {
|
||||
const stored = canon?.detail && readAlignment(canon.detail.longitudinal);
|
||||
return stored ? toAlignmentBase(stored).basePvi : null;
|
||||
};
|
||||
/** 횡단 입력 — 부를 때마다 저장 상세 + 세션 고침 · 표준을 모음(상세 객체마다 기억하면 고침이 안 감 · 365-2). */
|
||||
const crossNow = (): PipelineCrossSource | null => {
|
||||
const routeId = canon?.routeId;
|
||||
const source = pipelineCrossSource(
|
||||
projectId,
|
||||
canon?.detail ?? null,
|
||||
savedStations(),
|
||||
typeof routeId === "number" ? routeId : null,
|
||||
options.stationKeys, // 초안 자리 측점 짝(457)
|
||||
);
|
||||
if (source) crossSource = source;
|
||||
return source;
|
||||
};
|
||||
return {
|
||||
savedSource,
|
||||
canonChanged,
|
||||
savedEdits,
|
||||
storedPvi,
|
||||
crossNow,
|
||||
drafted,
|
||||
/** 저장 정본 노선 번호(정본을 아직 안 잡았으면 undefined). */
|
||||
canonRouteId: (): unknown => canon?.routeId,
|
||||
/** 초안을 버려 되돌린 상세 = 저장 정본(다시 하기도 같은 기준). */
|
||||
adoptCanon(routeId: unknown, detail: SectionDetailResponse): void {
|
||||
canon = { routeId, samples: detail.longitudinal.samples, detail };
|
||||
},
|
||||
/** 마지막 횡단 입력의 시설 제원(새 노선 상세가 씀). */
|
||||
crossSpecs: () => crossSource?.specs ?? new Map(),
|
||||
/** 저장분을 다시 받음 — 횡단 입력도 다시 모음. */
|
||||
forgetCross(): void {
|
||||
crossSource = null;
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -103,7 +103,11 @@ def test_초안_있으면_열_때_측점_냄() -> None:
|
||||
def test_화면_선만_바뀌면_낡음_다시_셈() -> None:
|
||||
src = (ROOT / "A00_Common/design/design_pipeline_page.ts").read_text(encoding="utf-8")
|
||||
restart = src[src.index(" restart() {") :]
|
||||
assert "publisher.recheck(projectId, canon.routeId)" in restart[: restart.index(" },")]
|
||||
# 정본 지문은 `design_pipeline_page_saved`(472-5 나눔) — 노선 번호를 거기서 읽음
|
||||
assert (
|
||||
"publisher.recheck(projectId, pageSaved.canonRouteId()"
|
||||
in restart[: restart.index(" },")]
|
||||
)
|
||||
store = (ROOT / "B06_Section/B06_Section_Section_Store.ts").read_text(encoding="utf-8")
|
||||
fn = store[store.index("export function recrossIfStale(") :]
|
||||
assert "recrossSaved(projectId, key, detail, routeId)" in fn[: fn.index("\n}\n")]
|
||||
|
||||
Reference in New Issue
Block a user