refactor(387-4): 횡단 다시 셈 창구 하나 · 면적 얹기 한 곳 — 화면은 묶음에 부탁
- 다시 셈(refreshCrossDesigns) 부르는 곳 = 묶음 cross 단계 하나(design_shared savedCross) - 종단 미리 셈 · B06 설계 맞춤 · 3D 업데이트 = pipelineCross · reconcilePagePipeline(▲ 한 번 = cross 한 번) - 입력 칸 밖 세션 고침(노견 · 소단 · 단면 유형 · 조정창)은 forceCross 로 같은 입력이어도 한 번 더 - 카드 그리기 · 횡단 보기 면적 쓰기 지움 · 다시 셈 창구가 면적을 늘 얹음(바뀐 측점만 알림) - 저장 안 한 조정창 값을 상세 가게가 읽을 때 얹음(새 Section_Adjusts · B05 만 열어도 같은 면적) - 새 test_384_4_cross_window · 옛 약속 시험 넷 새 약속으로 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014utrjfoW831Vc5wJUd13kL
This commit is contained in:
@@ -54,7 +54,6 @@ import {
|
||||
import type { ProfileAlignment } from "../B05_Profile/logic/B05_Profile_UI_Profile_Alignment";
|
||||
import { readAlignment, toDesignProfile } from "../B05_Profile/logic/B05_Profile_UI_Profile_Data";
|
||||
import { useStationKeys } from "./B06_Section_Station_Keys";
|
||||
import { readAlignmentDraft } from "../B05_Profile/B05_Profile_UI_Profile_Edit";
|
||||
import { hasStaleDesigns } from "./B06_Section_UI_Section_Common";
|
||||
import { applyStructureAreaRows, structureAreaRows } from "./B06_Section_Structure_Layouts";
|
||||
|
||||
@@ -168,35 +167,10 @@ export function staleAgainstScreen(detail: SectionDetailResponse, routeId: numbe
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 낡은 설계만 다시 셈 — B05 [3D 업데이트]가 예상형상을 짓기 전에 부름(317-1).
|
||||
* 편집 = 세션 계획선 초안 → 없으면 저장분 · 초안이 있으면 늘 맞춤(B06 `reconcileStaleDesigns` 와 같은 규칙).
|
||||
*/
|
||||
export async function refreshStaleDesigns(
|
||||
input: Omit<CrossRefreshInput, "edits" | "shouldApply">,
|
||||
): Promise<number[]> {
|
||||
const draft = readAlignmentDraft(input.routeId);
|
||||
if (!draft && !staleAgainstScreen(input.detail, input.routeId)) return [];
|
||||
const saved = (
|
||||
input.detail.longitudinal.profile_alignment as
|
||||
| {
|
||||
edits?: {
|
||||
station_offsets?: Record<string, number>;
|
||||
curve_radii?: Record<string, number>;
|
||||
};
|
||||
}
|
||||
| undefined
|
||||
)?.edits;
|
||||
const edits = draft ?? {
|
||||
station_offsets: saved?.station_offsets ?? {},
|
||||
curve_radii: saved?.curve_radii ?? {},
|
||||
};
|
||||
return refreshCrossDesigns({ ...input, edits });
|
||||
}
|
||||
|
||||
/**
|
||||
* 전 측점 횡단을 현재 계획선으로 다시 계산해 `detail.cross_sections[].design`을 제자리 교체한다.
|
||||
* 돌려주는 값은 실제로 바뀐 측점의 누가거리 목록 — 호출한 쪽이 그 카드만 다시 그리면 된다.
|
||||
* 돌려주는 값은 설계 · 면적이 실제로 바뀐 측점의 누가거리 목록 — 호출한 쪽이 그 카드만 다시 그리면 된다.
|
||||
* 부르는 곳은 계산 묶음 cross 단계 하나(`design_shared` savedCross · 387-4) — 화면은 `pipelineCross` 로 부탁.
|
||||
* 계획선 ▲ 한 번에 값이 바뀌는 측점은 몇 개뿐 — 전부 돌려주면 카드를 다 갈아 끼워 멈춤(54-22).
|
||||
*/
|
||||
export async function refreshCrossDesigns(input: CrossRefreshInput): Promise<number[]> {
|
||||
@@ -206,16 +180,13 @@ export async function refreshCrossDesigns(input: CrossRefreshInput): Promise<num
|
||||
if (sections.length && sections.every((section) => section.preview)) return [];
|
||||
useStationKeys(input.projectId, input.routeId, input.detail); // 세션 고침을 이 측점 목록으로 읽음(344-3)
|
||||
const before = new Map(sections.map((section) => [section, JSON.stringify(section.design)]));
|
||||
const updated = refreshLocally(input) ?? (await refreshFromServer(input));
|
||||
// 구조물이 선 측점은 폐회로 면적이 정본이다 — 설계를 갈아 끼운 **이 자리에서 한 번** 다시
|
||||
// 얹는다. 부르는 쪽마다 얹으면 B06 흐름만 얹고 B05 는 안 얹어 두 보기의 유토 뱃지가
|
||||
// 갈렸다(계획서 24-2a · 실측 B05 −14,144.5 · B06 −12,533.2㎥).
|
||||
if (updated.length) {
|
||||
applyStructureAreaRows(
|
||||
input.detail.cross_sections,
|
||||
structureAreaRows(input.detail.cross_sections),
|
||||
);
|
||||
}
|
||||
if (!refreshLocally(input)) await refreshFromServer(input);
|
||||
// 구조물이 선 측점은 폐회로 면적이 정본이다 — **이 자리 한 곳**에서 늘 다시 얹는다(387-4 · 카드 그리기는
|
||||
// 안 얹음 · 설계가 그대로여도 조정창 4축 · 구조물 제원이 바뀌면 면적이 바뀜 · 6 ms/67 측점).
|
||||
applyStructureAreaRows(
|
||||
input.detail.cross_sections,
|
||||
structureAreaRows(input.detail.cross_sections),
|
||||
);
|
||||
return sections
|
||||
.filter((section) => JSON.stringify(section.design) !== before.get(section))
|
||||
.map((section) => section.chainage_m);
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
/* =============================================================================
|
||||
* B06_Section_Section_Adjusts.ts
|
||||
* 저장 안 한 조정창 값(세션 · 측점 누가거리 열쇠)을 상세 설계 칸에 얹음 — 상세 가게가 읽을 때 한 번(387-4).
|
||||
*
|
||||
* 조정창(`_UI_Page_Station_Controls` · `_Ford_Controls` · `_Span_Control`)이 고칠 때 캐시에 얹는 것과 같은
|
||||
* 칸 · 같은 꼴. 예전에는 고칠 때만 얹어 새로고침 · B05 만 연 페이지는 세션 값을 몰랐음 — 카드 그리기가 면적을
|
||||
* 고쳐 들어 B06 을 연 뒤에만 면적이 바뀜(B05 만 연 면적 ≠ B06). 조정창은 세션 → 설계 순으로 읽으니 겹쳐도 같음.
|
||||
* ========================================================================== */
|
||||
|
||||
import { readState } from "../A00_Common/b_page_state";
|
||||
import type { CrossDesign, SectionDetailResponse } from "./B06_Section_Api_Fetch";
|
||||
import { DEFAULT_BASIN_ADJUST, ZERO_ADJUST } from "./B06_Section_UI_Cross_Culvert_Types";
|
||||
import type { WallAdjust } from "./B06_Section_UI_Cross_Culvert_Types";
|
||||
import { fordAdjustOf } from "./B06_Section_UI_Cross_Ford_Geom";
|
||||
import { DEFAULT_BOX_SIDE_ADJUST } from "./B06_Section_UI_Cross_Box_Geom";
|
||||
|
||||
type Cells = Record<string, unknown>;
|
||||
type Obj = Record<string, unknown>;
|
||||
const INLET = ["auto", "revet", "I", "L", "U"];
|
||||
const isObj = (value: unknown): value is Obj => !!value && typeof value === "object";
|
||||
const count = (value: unknown): number =>
|
||||
Number.isInteger(value) && (value as number) > 0 ? (value as number) : 0;
|
||||
|
||||
/** 조정창 읽기와 같은 꼴 — 옛 숫자 = x 이동량 · d 0 = 기본 자리(null). */
|
||||
function wallAdjust(value: unknown): WallAdjust | null {
|
||||
if (typeof value === "number" && Number.isFinite(value)) return { ...ZERO_ADJUST, x: value };
|
||||
if (!isObj(value)) return null;
|
||||
const partial = value as Partial<WallAdjust>;
|
||||
return { ...ZERO_ADJUST, ...partial, d: partial.d ? partial.d : null };
|
||||
}
|
||||
|
||||
export function withDraftAdjusts(
|
||||
detail: SectionDetailResponse,
|
||||
projectId: string,
|
||||
routeId: number,
|
||||
): void {
|
||||
const read = (name: Parameters<typeof readState>[0]): Cells =>
|
||||
readState<Cells>(name, projectId, routeId) ?? {};
|
||||
const revet = read("revetx");
|
||||
const basin = read("basinadjust");
|
||||
const inlet = read("inletstruct");
|
||||
const extra = read("extrawall");
|
||||
const spans = read("extraspan");
|
||||
const ford = read("fordadjust");
|
||||
const box = read("boxadjust");
|
||||
const link = read("revetlink") as { detached?: Cells; grade?: Cells };
|
||||
for (const section of detail.cross_sections) {
|
||||
const design = section.design as (CrossDesign & Obj) | null | undefined;
|
||||
if (!design) continue;
|
||||
const at = section.chainage_m.toFixed(2);
|
||||
const own = (cells: Cells): Array<[string, unknown]> =>
|
||||
Object.entries(cells)
|
||||
.filter(([key]) => key.startsWith(`${at}:`))
|
||||
.map(([key, value]) => [key.slice(at.length + 1), value]);
|
||||
for (const [role, value] of own(revet)) {
|
||||
const adjust = wallAdjust(value);
|
||||
if (adjust) design.revet_adjust = { ...(design.revet_adjust ?? {}), [role]: adjust };
|
||||
}
|
||||
if (typeof inlet[at] === "string" && INLET.includes(inlet[at] as string))
|
||||
design.inlet_structure = inlet[at] as CrossDesign["inlet_structure"];
|
||||
if (isObj(basin[at])) design.basin_adjust = { ...DEFAULT_BASIN_ADJUST, ...basin[at] };
|
||||
if (at in extra || `b${at}` in extra)
|
||||
design.extra_wall_counts = { outlet: count(extra[at]), basin: count(extra[`b${at}`]) };
|
||||
for (const [wall, value] of own(spans)) {
|
||||
const span = value as { lengthM: number; beforeM: number; afterM: number } | null;
|
||||
if (!span || !Number.isFinite(span.beforeM) || !Number.isFinite(span.afterM)) continue;
|
||||
design.extra_spans = {
|
||||
...(design.extra_spans ?? {}),
|
||||
[wall]: { length_m: span.lengthM, before_m: span.beforeM, after_m: span.afterM },
|
||||
};
|
||||
}
|
||||
if (isObj(link.detached) && at in link.detached)
|
||||
design.revet_link_detached = !!link.detached[at];
|
||||
if (isObj(link.grade) && at in link.grade) design.revet_follow_grade = !!link.grade[at];
|
||||
if (isObj(ford[at])) design.ford_adjust = fordAdjustOf(ford[at]);
|
||||
const sides = box[at] as { left?: Obj; right?: Obj } | undefined;
|
||||
if (isObj(sides))
|
||||
design.box_adjust = {
|
||||
left: { ...DEFAULT_BOX_SIDE_ADJUST, ...sides.left },
|
||||
right: { ...DEFAULT_BOX_SIDE_ADJUST, ...sides.right },
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
withDraftStations,
|
||||
} from "./B06_Section_Section_Drafts";
|
||||
import { applyStructureAreaRows, structureAreaRows } from "./B06_Section_Structure_Layouts";
|
||||
import { withDraftAdjusts } from "./B06_Section_Section_Adjusts";
|
||||
import type { CrossSectionPatch, SectionDetailResponse } from "./B06_Section_Api_Fetch";
|
||||
import { fetchSectionDetail, saveSections } from "./B06_Section_Api_Fetch";
|
||||
import { USER_TOUCHED_KEYS } from "./B06_Section_Cross_Refresh";
|
||||
@@ -71,6 +72,7 @@ async function withDraftWalls(
|
||||
await loadSavedPlusStations(projectId);
|
||||
await withDraftStations(detail, projectId, routeId); // 옛 자리 숨김 · 새 자리 미리 뜬 횡단(273-2)
|
||||
await withDraftShapes(detail, projectId);
|
||||
withDraftAdjusts(detail, projectId, routeId); // 저장 안 한 조정창 4축 · 구간 · 형식(387-4 · B05 만 열어도)
|
||||
return withStructureAreas(detail);
|
||||
}
|
||||
|
||||
|
||||
@@ -281,7 +281,7 @@ function areaRowOf(
|
||||
|
||||
/**
|
||||
* 구조물이 선 측점의 절·성토 면적 — **카드를 그리지 않은 측점까지** 전부 낸다.
|
||||
* 화면 그리기(`applyStructureAreas`)와 같은 계산이며, [저장]·[확정]과 초기값 산출
|
||||
* 다시 셈 창구 · 상세 가게(화면이 보는 면적 · 카드 그리기는 안 얹음 · 387-4)와 [저장]·[확정] · 초기값 산출
|
||||
* (Node 진입점)이 이 한 벌을 함께 쓴다.
|
||||
*/
|
||||
export function structureAreaRows(
|
||||
|
||||
@@ -42,7 +42,6 @@ import { createBodyWiring } from "./B06_Section_UI_Cross_View_Bodies";
|
||||
import {
|
||||
appendCardFrame,
|
||||
appendGroundProfile,
|
||||
applyStructureAreas,
|
||||
drawnContentBounds,
|
||||
} from "./B06_Section_UI_Cross_View_Frame";
|
||||
import { appendFordOverlay, computeFordLayout } from "./B06_Section_UI_Cross_Ford";
|
||||
@@ -507,10 +506,7 @@ export function createCrossSectionCard(
|
||||
fordLayout?.designTrim ??
|
||||
boxLayout?.designTrim ??
|
||||
ownDesignTrim;
|
||||
// 구조물이 선 측점의 절·성토 면적 — **그려지는 폐회로**의 넓이로 고쳐 든다
|
||||
// (2026-09-06 사용자 확정: 구조물 자체 면적을 빼는 것이 아니다). 그림과 면적이
|
||||
// 같은 트림 값을 쓰므로 "그림은 이런데 수량은 저렇다"가 생기지 않는다.
|
||||
applyStructureAreas(section, designTrim);
|
||||
// 구조물 측점 면적(그려지는 폐회로)은 다시 셈 창구 · 상세 가게가 같은 배치 셈으로 얹음 — 그리기는 안 고침(387-4).
|
||||
const designFrom = plotLayer.childElementCount;
|
||||
appendCrossDesignOverlay(plotLayer, section.design, x, toDisplayY, drawSamples, designTrim, {
|
||||
layer: svg,
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
* ========================================================================== */
|
||||
|
||||
import type { CrossSection } from "./B06_Section_Api_Fetch";
|
||||
import { computeStructureAreas, type DesignTrim } from "@util/common_util_cross_structure_areas";
|
||||
import { CROSS_PAD, L, svgElement, svgText, validElevation } from "./B06_Section_UI_Section_Common";
|
||||
|
||||
type Sample = CrossSection["samples"][number];
|
||||
@@ -88,43 +87,6 @@ export function appendCardFrame(
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 구조물이 만든 폐회로 면적을 측점 설계에 반영한다(2026-09-06 사용자 확정).
|
||||
*
|
||||
* 표준 설계선만 보는 기본 계산은 구조물이 있는지 모른다 — 기슭막이·세월교·BOX가 서면
|
||||
* 성토 사면이 벽에서 끊기고 그 바깥은 벽·성토부선이 대신 그리므로 폐회로가 달라진다.
|
||||
* 트림이 없으면(구조물 없는 측점) 아무것도 하지 않는다.
|
||||
*
|
||||
* 조작 중 즉시 반영용이다. 정본은 [저장]·[확정] 때 서버가 같은 코드
|
||||
* (`B06_Section_Structure_Areas_Node.ts`)로 다시 계산해 얹는다(2026-09-06).
|
||||
*/
|
||||
export function applyStructureAreas(
|
||||
section: CrossSection,
|
||||
trim: DesignTrim | undefined | null,
|
||||
): void {
|
||||
const design = section.design;
|
||||
if (!trim || !design || !Array.isArray(design.design_line) || design.design_line.length < 2) {
|
||||
return;
|
||||
}
|
||||
const ground = section.samples
|
||||
.filter((sample) => sample.valid !== false && typeof sample.elevation_m === "number")
|
||||
.map((sample) => ({ offset: sample.offset_m ?? 0, elevation: sample.elevation_m as number }))
|
||||
.sort((a, b) => a.offset - b.offset);
|
||||
const areas = computeStructureAreas({
|
||||
designLine: design.design_line as Array<{ offset_m: number; elevation_m: number }>,
|
||||
ground,
|
||||
trim,
|
||||
rockBoundaryOffsetM:
|
||||
typeof design.rock_boundary_offset_m === "number" ? design.rock_boundary_offset_m : null,
|
||||
});
|
||||
if (!areas) return;
|
||||
design.cut_area_m2 = Number(areas.cutAreaM2.toFixed(4));
|
||||
design.fill_area_m2 = Number(areas.fillAreaM2.toFixed(4));
|
||||
if (typeof design.cut_soil_area_m2 === "number") {
|
||||
design.cut_soil_area_m2 = Number(areas.cutSoilAreaM2.toFixed(4));
|
||||
}
|
||||
}
|
||||
|
||||
/** 원지반선 — 끊긴 샘플(무효 표고)마다 폴리선을 나눠 그린다. `toY` = 표고 → 화면 y(배율 반영). */
|
||||
export function appendGroundProfile(
|
||||
plotLayer: SVGElement,
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* 동작은 그대로다(2026-09-13 분리).
|
||||
* - `handleDesignChange` : 버튼 선택 즉시 반영 + 세션 초안 기록 + 재계산
|
||||
* - `recomputeIfRock` : 암 경계·암 절토 경사 변경 후 암 측점만 재계산
|
||||
* - `reconcileStaleDesigns`: 낡은 design 일괄 재계산(B05 와 같은 창구)
|
||||
* - `reconcileStaleDesigns`: 횡단 다시 셈을 계산 묶음에 부탁(cross 단계 한 곳 · 387-4)
|
||||
* - `syncBermSpans` : 구조물 목록의 소단을 세션 사본으로 펴기
|
||||
* - `applyPanelToAll` : 표준 횡단면 설정 [전체 반영]
|
||||
* 고친 뒤 계산 묶음 cross 입력도 다시(`pipelineCross` · 노선 초안 동안 카드 = 묶음 새 노선 상세 · 365-2).
|
||||
@@ -15,21 +15,20 @@ import {
|
||||
writeCrossDesignChoice,
|
||||
writeShoulderExtend,
|
||||
} from "./B06_Section_Cross_Design_Session";
|
||||
import { showToast } from "@ui/ui_template_elements";
|
||||
import { refreshCrossDesigns, staleAgainstScreen } from "./B06_Section_Cross_Refresh";
|
||||
import {
|
||||
bermSpansFromStructures,
|
||||
readBermSpans,
|
||||
writeBermSpans,
|
||||
} from "./B06_Section_UI_Page_Persist";
|
||||
import { readAlignmentDraft } from "../B05_Profile/B05_Profile_UI_Profile_Edit";
|
||||
import { showToast } from "@ui/ui_template_elements";
|
||||
import { L } from "./B06_Section_UI_Page_Common";
|
||||
import type { SectionDetailResponse } from "./B06_Section_Api_Fetch";
|
||||
import type { CrossDesignChange, SectionViewController } from "./B06_Section_UI_Section_View";
|
||||
import type { StructureInstance } from "../B05_Profile/B05_Profile_Api_Structures";
|
||||
import { setCrossDetailHost } from "./B06_Section_UI_Cross_Detail";
|
||||
import { useStructureIds } from "./B06_Section_Station_Keys";
|
||||
import { pipelineCross } from "../A00_Common/design/design_pipeline_page";
|
||||
import { pipelineCross, reconcilePagePipeline } from "../A00_Common/design/design_pipeline_page";
|
||||
import { forceCross } from "../A00_Common/design/design_pipeline_cross";
|
||||
|
||||
export interface DesignSyncContext {
|
||||
projectId: string | null;
|
||||
@@ -120,74 +119,22 @@ export function createDesignSync(ctx: DesignSyncContext): DesignSyncController {
|
||||
await reconcileStaleDesigns({ force: true });
|
||||
}
|
||||
|
||||
/** 암 경계 오프셋 변경 후 암 지반이면 2단계 무릎·단면적을 다시 셈 — 지금 값을 「사용자 선택」으로
|
||||
/** 암 경계 · 암 경사 변경 뒤 다시 셈(암 측점 2단계 무릎 · 단면적) — 지금 값을 「사용자 선택」으로
|
||||
* 박지 않음(자동 판정이 굳으면 노선이 바뀌어도 재판정 안 됨 · 346-2). 암 경계는 세션에서 읽음. */
|
||||
function recomputeIfRock(chainageM: number): void {
|
||||
const design = ctx
|
||||
.detail()
|
||||
?.cross_sections.find((section) => Math.abs(section.chainage_m - chainageM) < 0.01)?.design;
|
||||
if (design && design.ground_type !== "soil") void reconcileStaleDesigns({ force: true });
|
||||
else if (projectId) pipelineCross(projectId);
|
||||
function recomputeIfRock(_chainageM: number): void {
|
||||
void reconcileStaleDesigns({ force: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* 로드 시 stale design을 최신 엔진·최신 종단 계획고로 자동 재계산한다(E-1 + N-6).
|
||||
* 대상: (1) 2단계 경사 필드(`two_stage_slope`)가 없는 옛 암 측점, (2) B05에서 종단이
|
||||
* 변경·확정돼 저장된 계산 기준 계획고(`design.design_elevation_m`)가 현재 계획선
|
||||
* (`design_profiles`) 보간값과 어긋난 측점. 종단이 안 바뀐 측점은 0건이라 불필요한 API
|
||||
* 호출이 없다.
|
||||
*
|
||||
* 재계산은 측점별 순차 호출이 아니라 **일괄 프리뷰 1회**로 한다(2026-08-04 사용자 확인
|
||||
* — 예전 for-await 루프는 stale 측점 수만큼 왕복하며 카드가 하나씩 바뀌어 "이력 재생"처럼
|
||||
* 보였고 제일 느렸다). 편집 델타는 B05 세션 초안이 있으면 그것을(두 화면 동일 계획선),
|
||||
* 없으면 저장분(profile_alignment.edits)을 쓴다. 측점별 사용자 선택값(지반유형·단면유형·
|
||||
* 측구·암 경계)은 서버가 저장분에서 유지하고, 세션에만 있는 암 경계 오프셋은 함께 실어 보낸다.
|
||||
* 횡단 다시 셈 = 계산 묶음 cross 단계 **한 곳**(387-4) — 여기서는 묶음에 부탁하고 그 차례가 끝날 때까지
|
||||
* 기다림. 입력이 같으면 안 셈 · `force` = 입력 칸 밖 세션 고침(노견 · 소단 · 단면 유형 · 조정창)이라 한 번 더.
|
||||
* 바뀐 카드는 묶음 알림(`onCrossDesignsUpdated`)이 다시 그림.
|
||||
*/
|
||||
async function reconcileStaleDesigns(options?: { force?: boolean }): Promise<void> {
|
||||
const sectionDetail = ctx.detail();
|
||||
const currentRouteId = ctx.routeId();
|
||||
if (!sectionDetail || !projectId || currentRouteId === null) return;
|
||||
const draft = readAlignmentDraft(currentRouteId);
|
||||
// 낡음 판정은 B05와 **같은 규칙** 하나뿐이다(공용 판정 — 계획고 어긋남 +
|
||||
// 옛 암 2단계 필드 누락). 조건이 갈리면 같은 데이터가 두 화면에서 다른 값이 된다.
|
||||
//
|
||||
// 다만 **미저장 세션 편집이 있으면 판정을 건너뛰고 무조건 맞춘다**. 서버에서 갓 받은
|
||||
// 저장분끼리는 늘 일치해 「낡지 않음」으로 나오는데, B05가 세션에 남긴 편집은 그 안에
|
||||
// 없어 B06이 재계산을 통째로 건너뛰었다 — 같은 시점에 B05 절토 4,774.1㎥ ↔ B06
|
||||
// 4,515.0㎥ 로 갈렸다(2026-09-03 실측). 재계산은 브라우저 안에서 끝나 값싸다.
|
||||
// 판정 · 다시 셈 모두 종단도가 쥔 선(측점 한 벌) 기준 — 저장분 선끼리 견주면 화면과 갈림(342-1).
|
||||
if (!options?.force && !draft && !staleAgainstScreen(sectionDetail, currentRouteId)) return;
|
||||
// 진입 정합은 화면을 잠그지 않는다 — 카드가 도착하는 대로 조용히 갱신된다
|
||||
// (CLAUDE.md 5장).
|
||||
try {
|
||||
const alignment = sectionDetail.longitudinal.profile_alignment as
|
||||
| {
|
||||
edits?: {
|
||||
station_offsets?: Record<string, number>;
|
||||
curve_radii?: Record<string, number>;
|
||||
};
|
||||
}
|
||||
| undefined;
|
||||
const edits = draft ?? {
|
||||
station_offsets: alignment?.edits?.station_offsets ?? {},
|
||||
curve_radii: alignment?.edits?.curve_radii ?? {},
|
||||
};
|
||||
// 재계산은 B05와 **같은 창구**를 쓴다 — 인자가 갈리면 같은 데이터가 두 화면에서
|
||||
// 다른 값이 된다(2026-09-03 사용자 지시로 일원화).
|
||||
const updated = await refreshCrossDesigns({
|
||||
projectId,
|
||||
routeId: currentRouteId,
|
||||
detail: sectionDetail,
|
||||
edits,
|
||||
});
|
||||
// 카드는 한꺼번에 갈아 끼운다 — 측점마다 `refreshCard` 를 부르면 그때마다 종단
|
||||
// 그래프·유토곡선까지 다시 그려 측점 수만큼 화면이 멈췄다(2026-09-12).
|
||||
ctx.view().refreshCards(updated);
|
||||
} catch (error) {
|
||||
const detail = error instanceof Error ? ` ${error.message}` : "";
|
||||
showToast(`${L("B06_Design_Failed")}${detail}`, "error");
|
||||
}
|
||||
pipelineCross(projectId); // 세션 고침 · 표준 → 묶음(같은 입력이면 안 셈)
|
||||
if (!projectId) return;
|
||||
if (options?.force) forceCross();
|
||||
pipelineCross(projectId);
|
||||
await reconcilePagePipeline(projectId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user