feat(365-4): 한 바퀴 정리 — 저장 노선 계획고 = 횡단 한 벌 · B06 다시 그림 한 번
- 저장 노선 cross = 다시 셈 창구 한 벌(refreshCrossDesigns · 카드 · 유토곡선 · [저장] 패치가 보는 상세 설계) — 묶음 cross 가 부르고 그 설계를 냄(detailCross) · 종단도 계획고 편집 뒤 따로 예약 안 함(pipelineOwnsCross) - B06 다시 그림 — 묶음 차례는 cross 뒤에만 · 겹친 신호(갈아끼움 · 묶음 · 초안)는 refreshDetailForStructures 한 번(최신 차례만) - 중심점 좌우 = 노선 차례가 cross 전에 밀림 → cross 한 번(계획고 맞춤 차례 · 시험으로 못박음) - stationDiff → logic/design_pipeline_stations(700줄) - 시험 test_365(저장 ▲▼ 다시 셈 1 · 상세 설계 그대로 · 중심점 좌우 cross 1) · 346-2 pills · 230-2 고침 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TteK8mfYZetk5P3bEZkyVZ
This commit is contained in:
@@ -28,9 +28,10 @@ import { createDesignPipeline, publishToStore, type DesignPipeline } from "./log
|
||||
import { SOURCE_INPUT } from "./logic/design_pipeline_types";
|
||||
export { pipelineWork } from "./logic/design_pipeline";
|
||||
import { pipelineCrossSource, type PipelineCrossSource } from "./design_pipeline_cross";
|
||||
import { createStationsStage } from "./logic/design_pipeline_stations";
|
||||
import { createStationsStage, stationDiff } from "./logic/design_pipeline_stations";
|
||||
import type {
|
||||
ChangeSource,
|
||||
CrossOutput,
|
||||
FollowPatch,
|
||||
PipelineInputs,
|
||||
PipelineOutputs,
|
||||
@@ -147,6 +148,13 @@ export interface PagePipelineOptions {
|
||||
) => (stations: PipelineStation[]) => Promise<string>;
|
||||
/** 고리가 `RECONCILE_ROUNDS` 바퀴 안에 안 맞음 — 「계산 불안정」 한 줄(365-2). */
|
||||
onUnstable?: () => void;
|
||||
/** 저장 노선 cross = 다시 셈 창구 한 벌(365-4) — 상세 설계를 고치고 그 값을 냄 · 측점이 상세와 다르면 null
|
||||
* (묶음 엔진). 주면 종단도가 계획고 편집 뒤 따로 다시 셈 안 함(`pipelineOwnsCross`). */
|
||||
savedCross?: (
|
||||
detail: SectionDetailResponse,
|
||||
routeId: number,
|
||||
stations: ReadonlyArray<PipelineStation>,
|
||||
) => Promise<CrossOutput | null>;
|
||||
}
|
||||
|
||||
interface PagePipeline {
|
||||
@@ -157,6 +165,7 @@ interface PagePipeline {
|
||||
earthwork(context: MassHaulStageContext | null): void;
|
||||
cross(): void;
|
||||
reconcile(): Promise<number>;
|
||||
ownsCross: boolean;
|
||||
detail(): SectionDetailResponse | null;
|
||||
dispose(): void;
|
||||
}
|
||||
@@ -167,22 +176,6 @@ type Facilities = Pick<DesignStoreValues, "pipes" | "structures">;
|
||||
const pages = new Map<string, PagePipeline>();
|
||||
const same = (a: unknown, b: unknown): boolean => JSON.stringify(a) === JSON.stringify(b);
|
||||
|
||||
/** 상세 측점 대 묶음 측점(1 mm) — 같으면 null · 다르면 첫 다른 자리 · 개수(묶음 기록 `restoreDiff`). */
|
||||
function stationDiff(
|
||||
detail: SectionDetailResponse,
|
||||
stations: ReadonlyArray<PipelineStation>,
|
||||
): NonNullable<PipelineRunRecord["restoreDiff"]> | null {
|
||||
const saved = detail.cross_sections.map((section) => section.chainage_m);
|
||||
const n = Math.max(saved.length, stations.length);
|
||||
for (let i = 0; i < n; i += 1) {
|
||||
const pipelineM = stations[i]?.chainageM ?? null;
|
||||
if (saved[i] !== undefined && pipelineM !== null && Math.abs(saved[i] - pipelineM) < 1e-3)
|
||||
continue;
|
||||
return { savedM: saved[i] ?? null, pipelineM, saved: saved.length, pipeline: stations.length };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** 편집기에 넘길 다리 — 그 프로젝트 묶음을 처음 부를 때 세움. */
|
||||
export function routeDraftBridge(
|
||||
projectId: string,
|
||||
@@ -208,6 +201,10 @@ export const pipelineEarthwork = (projectId: string, context: MassHaulStageConte
|
||||
/** 횡단 고침(지반 유형 · 측구 · 암 · 소단 · 표준 — B06 · 365-2) — 세션 횡단 입력을 다시 모아 cross 부터. */
|
||||
export const pipelineCross = (projectId: string): void => pages.get(projectId)?.cross();
|
||||
|
||||
/** 저장 노선 횡단 다시 셈을 묶음 cross 가 맡는가(365-4) — 종단도는 계획고 편집 뒤 따로 안 셈. */
|
||||
export const pipelineOwnsCross = (projectId: string): boolean =>
|
||||
pages.get(projectId)?.ownsCross ?? false;
|
||||
|
||||
/** 입력(세션 · 가게) 대 묶음 입력을 견줘 맞춤(365-2) — 돈 바퀴 수(0 = 같음 · -1 = 넘음 · 묶음 없음 = 0). */
|
||||
export const reconcilePagePipeline = (projectId: string): Promise<number> =>
|
||||
pages.get(projectId)?.reconcile() ?? Promise.resolve(0);
|
||||
@@ -363,18 +360,27 @@ function createPagePipeline(
|
||||
ground: createGroundStage(groundDeps),
|
||||
planline: createPlanlineStage({ savedBase: () => savedSource().base, savedLine }),
|
||||
profile: profileStage,
|
||||
// 단면 유형 기본값 = 새 지형 상단측(서버 노선 저장 규칙) — 단계 본문은 그대로 부름
|
||||
cross: (ctx) =>
|
||||
crossStage.stage({
|
||||
...ctx,
|
||||
inputs: {
|
||||
...ctx.inputs,
|
||||
cross: crossWithUphill(
|
||||
ctx.inputs.cross as CrossInput | null,
|
||||
ctx.out.ground?.stations ?? [],
|
||||
),
|
||||
},
|
||||
}),
|
||||
// 단면 유형 기본값 = 새 지형 상단측(서버 노선 저장 규칙) — 단계 본문은 그대로 부름 · 저장 노선 = 다시 셈
|
||||
// 창구 한 벌(카드 · 유토곡선이 보는 상세 설계 · 365-4)
|
||||
cross: (ctx) => {
|
||||
const engine = () =>
|
||||
crossStage.stage({
|
||||
...ctx,
|
||||
inputs: {
|
||||
...ctx.inputs,
|
||||
cross: crossWithUphill(
|
||||
ctx.inputs.cross as CrossInput | null,
|
||||
ctx.out.ground?.stations ?? [],
|
||||
),
|
||||
},
|
||||
});
|
||||
const { detail = null, routeId } = savedSource();
|
||||
const saved =
|
||||
!ctx.inputs.route && detail && typeof routeId === "number"
|
||||
? options.savedCross?.(detail, routeId, ctx.out.ground?.stations ?? [])
|
||||
: null;
|
||||
return saved ? saved.then((out) => out ?? engine()) : engine();
|
||||
},
|
||||
footprint: footprintStage,
|
||||
masshaul: createMassHaulStage(() => earthwork),
|
||||
haul: (ctx) => (ctx.inputs.route ? haulStage(ctx) : (ctx.prev ?? null)),
|
||||
@@ -671,6 +677,7 @@ function createPagePipeline(
|
||||
if (cross) void feed("cross", cross);
|
||||
},
|
||||
reconcile,
|
||||
ownsCross: Boolean(options.savedCross),
|
||||
detail: () => shown,
|
||||
dispose() {
|
||||
disposed = true;
|
||||
|
||||
@@ -353,8 +353,8 @@ export function createDesignProfile(ctx: DesignProfileContext) {
|
||||
pagePipeline.pipelineGrade(projectId, store.edits());
|
||||
// 계획고가 바뀌면 측점별 횡단 단면적도 함께 바뀐다 — 전 측점을 다시 계산해 공유
|
||||
// 캐시에 얹는다. 그래야 **횡단 기준** 유토곡선과 횡단 카드가 따라 움직인다
|
||||
// (2026-08-03 사용자 보고: B05에서 계획선을 끌어도 횡단 곡선이 그대로였음).
|
||||
scheduleCrossPreview();
|
||||
// (2026-08-03 사용자 보고: B05에서 계획선을 끌어도 횡단 곡선이 그대로였음). 묶음이 맡으면 그 cross 한 벌(365-4).
|
||||
if (!pagePipeline.pipelineOwnsCross(projectId)) scheduleCrossPreview();
|
||||
drawSoon();
|
||||
}
|
||||
/** 다음 틀에 한 번 그림 — 같은 차례 알림 · 갈아끼움 · 설정이 여러 번 불러도 한 번(349-0s). */
|
||||
|
||||
@@ -40,6 +40,8 @@ import {
|
||||
restoreSectionDetail,
|
||||
swapSectionStations,
|
||||
} from "../../B06_Section/B06_Section_Section_Store";
|
||||
import { refreshCrossDesigns } from "../../B06_Section/B06_Section_Cross_Refresh";
|
||||
import { detailCross } from "../../B05_Profile/logic/B05_Profile_Pipeline_Detail";
|
||||
|
||||
function L(key: keyof typeof ui_locales): string {
|
||||
return ui_locales[key][currentLanguageIndex];
|
||||
@@ -210,6 +212,16 @@ function startPagePipeline(shared: DesignPageShared, profile: DesignProfile): vo
|
||||
onCorridorStale: () => each((hooks) => hooks.onCorridorStale?.()),
|
||||
onFacilitiesFollowed: (far) => each((hooks) => hooks.onFacilitiesFollowed?.(far)),
|
||||
onUnstable: () => showToast(L("Design_Pipeline_Unstable"), "warning"),
|
||||
// 저장 노선 횡단 = 다시 셈 창구 한 벌(365-4) — 카드 · 유토곡선 · [저장] 패치가 보는 상세 설계를 고치고 그 값을
|
||||
// 묶음 출력으로(발자국 · 평면 횡단 판) · 측점이 상세와 다르면(갈아끼움 전) 묶음 엔진
|
||||
savedCross: async (detail, routeId, stations) => {
|
||||
if (!detailCross(detail, stations)) return null;
|
||||
const edits = profile.pipelineSource().edits;
|
||||
const updated = await refreshCrossDesigns({ projectId, routeId, detail, edits });
|
||||
if (updated.length) toAll(shared, "onCrossDesignsUpdated")(updated);
|
||||
profile.redraw();
|
||||
return detailCross(detail, stations);
|
||||
},
|
||||
structureAreas: ({ cross_sections: rows }) =>
|
||||
applyStructureAreaRows(rows, structureAreaRows(rows)),
|
||||
// 초안 시작 — 초안 전 상태를 떠 둠 · 버리면 그대로(측점이 달라도 · 349-0o) · 상세가 없으면 묶음 측점으로 갈아끼움
|
||||
|
||||
@@ -225,11 +225,11 @@ export async function mountCrossView(
|
||||
const seq = ++structuresRefreshSeq; // 잇단 조작 — 최신 요청만 그림(54-4)
|
||||
if (savingDesign(projectId)) return; // 저장 도중 = 낡은 캐시 · 빈 초안 — 저장 뒤 한 번(323-9)
|
||||
try {
|
||||
await new Promise((next) => setTimeout(next, 0)); // 겹친 신호(갈아끼움 · 묶음 · 초안) = 한 번(365-4)
|
||||
const fresh = await loadSectionDetail(projectId, currentRouteId);
|
||||
if (seq !== structuresRefreshSeq) return;
|
||||
sectionDetail = fresh;
|
||||
// 면적을 **먼저** 다시 얹는다 — 유토곡선은 카드보다 앞서 계산되므로, 카드 렌더가
|
||||
// 고치는 것만으로는 곡선이 옛 면적으로 남는다(2026-09-06 실측).
|
||||
// 면적을 **먼저** 다시 얹는다 — 유토곡선은 카드보다 앞서 셈(카드만 고치면 곡선이 옛 면적 · 2026-09-06 실측).
|
||||
if (sectionDetail) {
|
||||
applyStructureAreaRows(
|
||||
sectionDetail.cross_sections,
|
||||
|
||||
@@ -14,7 +14,13 @@ import type {
|
||||
PlanStructure,
|
||||
PlanType,
|
||||
} from "../../../common_util/common_util_structure_station";
|
||||
import type { PipelineStation, StageContext, StageFn } from "./design_pipeline_types";
|
||||
import type {
|
||||
PipelineRunRecord,
|
||||
PipelineStation,
|
||||
StageContext,
|
||||
StageFn,
|
||||
} from "./design_pipeline_types";
|
||||
import type { SectionDetailResponse } from "../../../B06_Section/B06_Section_Api_Fetch";
|
||||
|
||||
/** 범위 밖 측점은 앞 값(같은 열쇠 · 같은 자리)을 그대로 — 뒤 단계가 같은 객체로 「안 바뀜」을 앎. */
|
||||
export function reuseOutside(
|
||||
@@ -54,3 +60,19 @@ export function createStationsStage(
|
||||
return reuseOutside(next, ctx.prev, ctx.range);
|
||||
};
|
||||
}
|
||||
|
||||
/** 상세 측점 대 묶음 측점(1 mm) — 같으면 null · 다르면 첫 다른 자리 · 개수(묶음 기록 `restoreDiff`). */
|
||||
export function stationDiff(
|
||||
detail: SectionDetailResponse,
|
||||
stations: ReadonlyArray<PipelineStation>,
|
||||
): NonNullable<PipelineRunRecord["restoreDiff"]> | null {
|
||||
const saved = detail.cross_sections.map((section) => section.chainage_m);
|
||||
const n = Math.max(saved.length, stations.length);
|
||||
for (let i = 0; i < n; i += 1) {
|
||||
const pipelineM = stations[i]?.chainageM ?? null;
|
||||
if (saved[i] !== undefined && pipelineM !== null && Math.abs(saved[i] - pipelineM) < 1e-3)
|
||||
continue;
|
||||
return { savedM: saved[i] ?? null, pipelineM, saved: saved.length, pipeline: stations.length };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -102,6 +102,33 @@ function atChainage(own: SectionSpecs, chainageM: number): SectionSpecs {
|
||||
return out as SectionSpecs;
|
||||
}
|
||||
|
||||
/** 저장 상세 → 묶음 cross 출력(365-4 · 저장 노선 = 다시 셈 창구 한 벌이 고친 설계 그대로) — 측점이 상세와
|
||||
* 다르면(갈아끼움 전) null. */
|
||||
export function detailCross(
|
||||
detail: SectionDetailResponse,
|
||||
stations: ReadonlyArray<PipelineStation>,
|
||||
): CrossOutput | null {
|
||||
const sections = detail.cross_sections;
|
||||
const same =
|
||||
sections.length === stations.length &&
|
||||
stations.every((station, i) => Math.abs(station.chainageM - sections[i].chainage_m) < 1e-3);
|
||||
if (!same) return null;
|
||||
const byKey: CrossOutput["byKey"] = {};
|
||||
stations.forEach((station, i) => {
|
||||
const { design, samples } = sections[i];
|
||||
if (!design) return;
|
||||
byKey[station.key] = {
|
||||
key: station.key,
|
||||
chainageM: station.chainageM,
|
||||
cutAreaM2: design.cut_area_m2,
|
||||
fillAreaM2: design.fill_area_m2,
|
||||
detail: design,
|
||||
...(samples ? { samples } : {}),
|
||||
} as CrossOutput["byKey"][string];
|
||||
});
|
||||
return { byKey };
|
||||
}
|
||||
|
||||
/** 묶음 측점 하나 → 횡단(머리 · 지반 표본 · 설계) — 새 노선 상세 · B05 오른쪽 횡단 판(365-3)이 같은 값. */
|
||||
export function stationSection(
|
||||
station: PipelineStation,
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
* 종단 보기도 같은 가게를 듣고 같은 함수를 부름 — 먼저 부른 쪽이 갈아끼우고 다음 쪽은 「같은 목록」.
|
||||
* 노선 초안 동안(346-2 덧 3) 카드 = 묶음 새 노선 상세(`pipelineDetail` · cross 단계 결과) — 새 상세가 오면
|
||||
* 같은 길로 다시 그림 · 초안을 버리면(상세 null) 저장 노선 카드로.
|
||||
* 묶음 차례는 cross 뒤에만 다시 그림(profile 뒤 중간 상세는 안 그림 · 365-4) — 겹친 신호(갈아끼움 · 묶음 · 초안)는
|
||||
* 횡단 보기 다시 읽기가 한 번으로 묶음(`design_view_cross` refreshDetailForStructures · 최신 차례만).
|
||||
* ========================================================================== */
|
||||
|
||||
import { useDesignStore } from "../A00_Common/design/design_store";
|
||||
@@ -42,7 +44,7 @@ export function followSwaps(
|
||||
};
|
||||
}
|
||||
|
||||
/** 묶음 새 노선 상세가 바뀌면(profile · cross 단계 뒤) 부름 — 묶음은 종단 보기가 세우므로 차례 번호가 날 때 붙음. */
|
||||
/** 묶음 새 노선 상세가 바뀌면(cross 단계 뒤) 부름 — 묶음은 페이지가 세우므로 차례 번호가 날 때 붙음. */
|
||||
function followDraftDetail(projectId: string, changed: () => void): () => void {
|
||||
let pipe: DesignPipeline | null = null;
|
||||
let stopPipe = (): void => undefined;
|
||||
@@ -54,8 +56,7 @@ function followDraftDetail(projectId: string, changed: () => void): () => void {
|
||||
pipe = now;
|
||||
stopPipe =
|
||||
now?.subscribe((event) => {
|
||||
if (event.kind !== "stage" || (event.stage !== "profile" && event.stage !== "cross"))
|
||||
return;
|
||||
if (event.kind !== "stage" || event.stage !== "cross") return;
|
||||
const next = pipelineDetail(projectId);
|
||||
if (next === drawn) return;
|
||||
drawn = next;
|
||||
|
||||
@@ -99,7 +99,7 @@ out.viewOf = {
|
||||
buildAlignment: () => null,
|
||||
store: { edits: () => ({}) },
|
||||
projectId: "p",
|
||||
pagePipeline: { pipelineGrade: () => undefined }, // 계산 묶음(346-1) — 묶음 없으면 아무 일 없음
|
||||
pagePipeline: { pipelineGrade: () => undefined, pipelineOwnsCross: () => false }, // 계산 묶음(346-1 · 365-4) — 묶음 없으면 아무 일 없음
|
||||
scheduleCrossPreview: () => s.previews++,
|
||||
draw: () => s.draws++,
|
||||
root: inPopup(),
|
||||
|
||||
@@ -336,7 +336,60 @@ async function run() {
|
||||
};
|
||||
pageMod.disposePagePipeline(P);
|
||||
page.clearDrafts(P);
|
||||
return { kept, b05, b06, dropped, edit, loop, pane: paneCheck };
|
||||
|
||||
// ⑨ 저장 노선 ▲▼ 한 번 = 횡단 한 벌(365-4) — 묶음 cross 가 다시 셈 창구를 부르고 그 상세 설계를 냄
|
||||
const Q = "proj-365b";
|
||||
window.localStorage.setItem("frd_current_project_id", Q);
|
||||
let refreshCalls = 0;
|
||||
const qBridge = pageMod.routeDraftBridge(Q, link(), {
|
||||
stationIntervalM: job.interval,
|
||||
savedCross: async (detail, _routeId, stations) => {
|
||||
if (!detailMod.detailCross(detail, stations)) return null;
|
||||
refreshCalls += 1;
|
||||
return detailMod.detailCross(detail, stations);
|
||||
},
|
||||
});
|
||||
pageMod.pipelineEarthwork(Q, { conversion: job.conversion });
|
||||
const qPipe = pageMod.pagePipeline(Q);
|
||||
await settle(runs().length + 1);
|
||||
await tick(20);
|
||||
const openCalls = refreshCalls;
|
||||
const key = alignment.chainageKey(savedBase.stations[3].chainage_m);
|
||||
mark = runs().length;
|
||||
pageMod.pipelineGrade(Q, { station_offsets: { [key]: 0.3 }, curve_radii: {} });
|
||||
const gradeRun = await settle(mark + 1);
|
||||
const firstKey = qPipe.output("ground").stations[0].key;
|
||||
const saved = {
|
||||
owns: pageMod.pipelineOwnsCross(Q),
|
||||
openCalls,
|
||||
gradeCalls: refreshCalls - openCalls,
|
||||
runs: runs().length - mark,
|
||||
sources: gradeRun.sources,
|
||||
fromDetail:
|
||||
qPipe.output("cross").byKey[firstKey]?.detail === job.detail.cross_sections[0].design,
|
||||
};
|
||||
|
||||
// ⑧ 중심점 좌우(356-3 꼴) — 노선 한 걸음 → 새 종단이 오면 계획고 맞춤 · 첫 차례는 cross 전에 밀림(무거운 셈 한 번)
|
||||
let graded = false;
|
||||
const stopShift = qPipe.subscribe((event) => {
|
||||
if (event.kind !== "stage" || event.stage !== "profile" || graded) return;
|
||||
if (event.generation !== qPipe.generation) return;
|
||||
graded = true;
|
||||
void Promise.resolve().then(() =>
|
||||
pageMod.pipelineGrade(Q, { station_offsets: { [key]: 0.1 }, curve_radii: {} }),
|
||||
);
|
||||
}, "중심점 좌우");
|
||||
mark = runs().length;
|
||||
qBridge.edited(draftOf(shift(1, 0, 1)));
|
||||
await settle(mark + 2);
|
||||
await tick(job.haulDelayMs + 30);
|
||||
stopShift();
|
||||
const shiftRuns = runs()
|
||||
.slice(mark)
|
||||
.map((r) => ({ outcome: r.outcome, cross: "cross" in r.stages, sources: r.sources }));
|
||||
pageMod.disposePagePipeline(Q);
|
||||
page.clearDrafts(Q);
|
||||
return { kept, b05, b06, dropped, edit, loop, pane: paneCheck, saved, shiftRuns };
|
||||
}
|
||||
|
||||
run()
|
||||
|
||||
@@ -38,20 +38,20 @@ def out():
|
||||
def test_노선_초안_동안_카드는_새_노선_상세(out):
|
||||
cards = out["cards"]
|
||||
assert cards["before"] == "saved"
|
||||
assert cards["afterDraft"] == ["draft-profile-1", "draft-cross-1"]
|
||||
assert cards["afterDraft"] == ["draft-cross-1"] # profile 차례 중간 상세는 안 그림(365-4)
|
||||
assert cards["draftNow"] == "draft-cross-1"
|
||||
|
||||
|
||||
def test_숨은_동안은_안_그림(out):
|
||||
cards = out["cards"]
|
||||
assert cards["whileHidden"] == ["draft-profile-1", "draft-cross-1"]
|
||||
assert cards["whileHidden"] == ["draft-cross-1"]
|
||||
assert cards["hiddenNow"] == "draft-cross-2"
|
||||
|
||||
|
||||
def test_초안_버리면_저장_노선_카드(out):
|
||||
cards = out["cards"]
|
||||
assert cards["discarded"][-1] == "saved" and cards["savedNow"] == "saved"
|
||||
assert cards["afterStop"] == 3 # 끊은 뒤엔 안 그림
|
||||
assert cards["afterStop"] == 2 # 끊은 뒤엔 안 그림
|
||||
|
||||
|
||||
def test_알약은_따라간_누가거리(out):
|
||||
|
||||
@@ -10,6 +10,10 @@
|
||||
- 고리 — 같은 입력 = 0 바퀴 · 세션만 바뀜 = 1 바퀴 · 안 맞으면 3 바퀴 뒤 「계산 불안정」 한 번.
|
||||
못박는 것(365-3)
|
||||
- B05 오른쪽 횡단 판 = 묶음 그 측점 — 계획고 · 절토 면적 · 표본 = B06 카드 · 서버 길 없음.
|
||||
못박는 것(365-4)
|
||||
- 저장 노선 ▲▼ 한 번 = 횡단 한 벌 — 묶음 cross 가 다시 셈 창구(상세 설계) 한 번 · 종단도 예약 끔.
|
||||
- 중심점 좌우 = 노선 차례는 cross 전에 밀림 → cross 한 번(계획고 맞춤 차례만).
|
||||
- B06 다시 그림 = 겹친 신호(갈아끼움 · 묶음 cross · 초안) 한 번 · profile 차례는 안 그림.
|
||||
|
||||
짝: `helper_365_page_pipeline.cjs`(진짜 TS · 서버 창구만 가짜 · 틀 = 346-2 시험) ·
|
||||
`helper_365_swap_entry.ts` + `helper_365_swap.mjs`(진짜 상세 가게 · vite ssr).
|
||||
@@ -132,6 +136,30 @@ def test_365_3_서버_횡단_미리보기_길_없음():
|
||||
assert "crossPreview.refresh()" not in edit # 묶음 cross 차례가 그림
|
||||
|
||||
|
||||
def test_365_4_저장_노선_계획고_횡단_한_벌(r):
|
||||
s = r["saved"]
|
||||
assert s["owns"] and s["openCalls"] == 1
|
||||
assert s["gradeCalls"] == 1 and s["runs"] == 1 and s["sources"] == ["grade"]
|
||||
assert s["fromDetail"] # 묶음 cross = 상세 설계 그대로(엔진 따로 안 셈)
|
||||
|
||||
|
||||
def test_365_4_중심점_좌우_횡단_한_번(r):
|
||||
runs = r["shiftRuns"]
|
||||
assert [x["outcome"] for x in runs] == ["dropped", "done"], runs
|
||||
assert [x["cross"] for x in runs] == [False, True]
|
||||
assert runs[1]["sources"] == ["route", "grade"] # 밀린 노선 범위 + 계획고 = 한 차례
|
||||
|
||||
|
||||
def test_365_4_다시_그림_한_번():
|
||||
swap = (ROOT / "B06_Section/B06_Section_UI_Page_Station_Swap.ts").read_text(encoding="utf-8")
|
||||
view = (ROOT / "A00_Common/design/design_view_cross.ts").read_text(encoding="utf-8")
|
||||
profile = (ROOT / "A00_Common/design/design_profile.ts").read_text(encoding="utf-8")
|
||||
assert 'event.stage !== "cross") return;' in swap # profile 차례 중간 상세는 안 그림
|
||||
body = view.split("async function refreshDetailForStructures")[1].split("loadSectionDetail")[0]
|
||||
assert "setTimeout(next, 0)" in body # 겹친 신호는 seq 가 앞 차례를 버림 = 한 번
|
||||
assert "if (!pagePipeline.pipelineOwnsCross(projectId)) scheduleCrossPreview();" in profile
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def swap(tmp_path_factory) -> dict:
|
||||
work = tmp_path_factory.mktemp("b365")
|
||||
|
||||
Reference in New Issue
Block a user