fix(B05): 유토곡선이 편집 중 계속 「다시 계산 중」에 머물던 문제

낡음 판정이 **편집 전 계획선**과 견주고 있었음. 로컬 재계산은 편집된 계획고로 횡단
설계를 갱신하는데 비교 대상은 저장분이라, 편집이 있는 동안 판정이 영원히 참으로 남아
유토곡선이 빈 화면에 문구만 남았음(사용자 보고).

- 그리기(`_Profile_Render`)와 진입 판정(`_Profile_Panel.render`) 모두 편집이 반영된
  계획선(`toDesignProfile(alignment, …)`)과 견주도록 바꿈. 특히 진입 판정은 세션에
  미저장 편집이 남은 채 새로고침하면 「어긋남 없음」으로 나와 재계산을 예약조차 하지
  않았음 — 그래서 새로고침해도 문구가 그대로였음.
- 세월교 측점은 노면이 월류 높이만큼 일부러 내려 앉으므로, 판정에서 `surface_drop_m`
  을 더해 비교. 안 그러면 계획선을 안 건드려도 늘 어긋난 것으로 나옴.

화면 검증(공용 브라우저 5174, route 132): [초기화] 직후 문구 없음·유토곡선 정상
(절토 1,113.5㎥ / 성토 1,424.1㎥). 계획고 ▲ 1클릭 시 문구 없이 즉시 갱신
(절토 1,108.3㎥ / 성토 1,428.6㎥), ↶ 로 되돌리면 원값 복귀. 임시 진단 코드는 제거함.
전체 테스트 183건 통과.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-03 20:12:34 +09:00
co-authored by Claude Opus 5
parent a35c8ff356
commit 91a0157324
3 changed files with 33 additions and 6 deletions
+18 -2
View File
@@ -20,7 +20,7 @@ import {
createProfileTableOverlay,
TABLE_OVERLAY_MIN_HEIGHT,
} from "./B05_Profile_UI_Profile_TableOverlay";
import { hasLegacyAlignment, readAlignment } from "./B05_Profile_UI_Profile_Data";
import { hasLegacyAlignment, readAlignment, toDesignProfile } from "./B05_Profile_UI_Profile_Data";
import { createProgressCircle } from "@ui/ui_template_progress";
import { showToast } from "@ui/ui_template_elements";
import { saveProfileAlignment } from "./B05_Profile_Api_Fetch";
@@ -584,7 +584,23 @@ export function createRouteProfilePanel(
// 저장된 횡단 설계가 옛 계획고로 굳어 있으면 진입 즉시 한 번 맞춘다 —
// 지금까지는 B06에 들어가야만 고쳐져, B05의 횡단 기준 유토곡선과 3D
// 예상형상이 옛 설계선을 그대로 썼다(2026-08-23 사용자 보고 · 실측 확인).
if (hasStaleDesigns(nextDetail)) scheduleCrossPreview();
//
// 비교 대상은 **편집이 반영된 계획선**이다. 저장분끼리 견주면, 세션에 미저장 편집이
// 남은 채로 새로고침했을 때 「어긋남 없음」으로 나와 재계산이 예약되지 않고, 그리기
// 쪽은 편집분 기준으로 어긋났다고 보아 유토곡선이 영영 빈 화면이 됐다
// (2026-09-03 사용자 보고 — 편집 측점 2개가 세션에 남은 상태에서 재현).
if (
hasStaleDesigns({
longitudinal: {
design_profiles: alignment
? [toDesignProfile(alignment, nextDetail.longitudinal.design_profiles?.[0])]
: nextDetail.longitudinal.design_profiles,
},
cross_sections: nextDetail.cross_sections,
})
) {
scheduleCrossPreview();
}
},
/** 라이브 계획선 샘플(편집 반영분) — 3D 측점 바·코리도가 이걸 본다(2026-08-23).
* 편집 전·base 없음이면 null — 호출부는 정본 design_profiles로 폴백한다. */
+9 -3
View File
@@ -326,9 +326,15 @@ export function renderProfile(ctx: ProfileRenderContext): void {
// 측점별 횡단 설계(기본 프리뷰 포함)를 입력으로 쓴다 — B06과 같은 재료다.
longitudinal: { length_m: longitudinal.length_m, design_profiles: designProfiles },
crossSections: detail.cross_sections,
// 저장된 횡단이 지금 계획선과 어긋나면 그 면적은 옛 계획고로 만든 값이다 —
// Panel이 재계산을 예약해 두므로 유토곡선은 그 결과만 그린다(2026-09-03 사용자 확정).
pendingRecalc: hasStaleDesigns(detail),
// 횡단이 지금 계획선과 어긋나면 그 면적은 옛 계획고로 만든 값이다 — Panel이
// 재계산을 예약해 두므로 유토곡선은 그 결과만 그린다(2026-09-03 사용자 확정).
// 비교 대상은 **편집이 반영된 계획선**(`designProfiles`)이다. 저장분
// (`detail.longitudinal.design_profiles`)과 견주면 편집 중에는 영원히 어긋난 것으로
// 나와 곡선이 계속 빈 화면이 된다(2026-09-03 사용자 보고).
pendingRecalc: hasStaleDesigns({
longitudinal: { design_profiles: designProfiles },
cross_sections: detail.cross_sections,
}),
axis: {
maxChainageM: maxChainageOf(longitudinal),
// 종단 그래프의 `chainageMapper`와 정확히 같은 매핑이 되도록 반 칸 들여쓰기를
+6 -1
View File
@@ -107,6 +107,7 @@ interface StaleCandidate {
design_elevation_m: number;
geometry_preset?: string;
two_stage_slope?: boolean;
surface_drop_m?: number;
} | null;
}
@@ -123,7 +124,11 @@ function isStaleSection(
if (design.geometry_preset === "rock" && design.two_stage_slope === undefined) return true;
if (!profiles?.length) return false;
const planned = designElevationAt(profiles, section.chainage_m);
return planned !== undefined && Math.abs(planned - design.design_elevation_m) > toleranceM;
if (planned === undefined) return false;
// 세월교가 앉은 측점의 노면은 월류 높이만큼 **일부러** 내려 앉는다 — 그 차이를 빼지
// 않으면 계획선을 안 건드려도 늘 어긋난 것으로 나온다(2026-09-03 사용자 보고).
const designed = design.design_elevation_m + (design.surface_drop_m ?? 0);
return Math.abs(planned - designed) > toleranceM;
}
/**