fix(B05): 종단 계획고 변경을 횡단 설계에 자동 반영 - B05 진입 시에도 재계산
2026-08-23 사용자 보고: 종단 그래프의 계획고 변화가 B06 횡단도에 반영 안 됨. 원인: 횡단 설계는 계산 당시 계획고(design.design_elevation_m) 기준으로 설계선 좌표를 굳혀 둔다. 계획선이 뒤에 바뀌면(사용자 편집·확정, 배수 최소고 같은 백엔드 규칙 도입) 설계선은 옛 자리에 남는데, 화면의 계획고 십자선과 3D 예상형상은 최신 계획선을 쓴다 - 두 기준이 어긋난다. 실측으로 한 노선에서 최대 2.21m 어긋남 확인. 지금까지 이 보정은 B06 진입 때만 돌아서, B05의 횡단 기준 유토곡선과 3D는 옛 설계선을 계속 썼다. - staleDesignChainages() 신설(B06_Section_UI_Section_Common) - 계획선 보간값과 계산 기준의 차이로 재계산 대상을 판정하는 B05·B06 공용 규칙. - B05 Profile_Panel: 진입 렌더에서 어긋남이 있으면 프리뷰 1회로 맞춘다. - B06 Page: 같은 공용 규칙을 쓰도록 통일(옛 암 2단계 필드 누락 조건은 유지). 검증: tsc 통과 · pytest 191 passed(신규 stale 판정 6건) · 공용 브라우저 실측 - 계획고·지반고 배치 대조(전 측점 일치, 렌더 역산 RMS 0.004~0.03m), 측점 중심 좌표가 노선 폴리라인과 거리 0, 인위 stale 1건을 B05 진입으로 자동 복구(stale 1 to 0, 계획고 540.895 복원). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -29,6 +29,7 @@ import { createProgressCircle } from "@ui/ui_template_progress";
|
||||
import { showToast } from "@ui/ui_template_elements";
|
||||
import { saveProfileAlignment } from "./B05_Profile_Api_Fetch";
|
||||
import { previewCrossDesigns } from "../B06_Section/B06_Section_Api_Fetch";
|
||||
import { staleDesignChainages } from "../B06_Section/B06_Section_UI_Section_Common";
|
||||
import {
|
||||
findMinCoverViolations,
|
||||
minCoverPoints,
|
||||
@@ -598,6 +599,10 @@ export function createRouteProfilePanel(
|
||||
);
|
||||
draw();
|
||||
requestAnimationFrame(draw);
|
||||
// 저장된 횡단 설계가 옛 계획고로 굳어 있으면 진입 즉시 한 번 맞춘다 —
|
||||
// 지금까지는 B06에 들어가야만 고쳐져, B05의 횡단 기준 유토곡선과 3D
|
||||
// 예상형상이 옛 설계선을 그대로 썼다(2026-08-23 사용자 보고 · 실측 확인).
|
||||
if (staleDesignChainages(nextDetail).length) scheduleCrossPreview();
|
||||
},
|
||||
/** 라이브 계획선 샘플(편집 반영분) — 3D 측점 바·코리도가 이걸 본다(2026-08-23).
|
||||
* 편집 전·base 없음이면 null — 호출부는 정본 design_profiles로 폴백한다. */
|
||||
|
||||
@@ -41,7 +41,7 @@ import {
|
||||
import { computeMassHaul, massHaulPayload } from "@util/common_util_mass_haul";
|
||||
import { computeHaulPlan } from "@util/common_util_mass_haul_balance";
|
||||
import { balloonOffsetsPayload } from "@util/common_util_mass_haul_balance_view";
|
||||
import { designElevationAt } from "./B06_Section_UI_Section_Common";
|
||||
import { staleDesignChainages } from "./B06_Section_UI_Section_Common";
|
||||
import { createStandardPanel, type StandardPanelController } from "./B06_Section_UI_Standard_Panel";
|
||||
import "./B06_Section_UI_Style.css";
|
||||
import "./B06_Section_UI_Style_Cross.css";
|
||||
@@ -203,13 +203,14 @@ export async function renderB06ProfileCross(root: HTMLElement): Promise<void> {
|
||||
*/
|
||||
async function reconcileStaleDesigns(): Promise<void> {
|
||||
if (!sectionDetail || !projectId || currentRouteId === null) return;
|
||||
const profiles = sectionDetail.longitudinal.design_profiles;
|
||||
// 계획고 어긋남은 B05와 **같은 규칙**으로 판정한다(공용 staleDesignChainages).
|
||||
// 옛 암 2단계 필드 누락은 B06 전용 조건이라 여기서 더한다.
|
||||
const staleByPlan = new Set(staleDesignChainages(sectionDetail));
|
||||
const stale = sectionDetail.cross_sections.filter((section) => {
|
||||
const design = section.design;
|
||||
if (!design) return false;
|
||||
if (design.geometry_preset === "rock" && design.two_stage_slope === undefined) return true;
|
||||
const planZ = designElevationAt(profiles, section.chainage_m);
|
||||
return planZ !== undefined && Math.abs(planZ - design.design_elevation_m) > 1e-3;
|
||||
return staleByPlan.has(section.chainage_m);
|
||||
});
|
||||
if (!stale.length) return;
|
||||
showLoadingOverlay();
|
||||
|
||||
@@ -68,6 +68,35 @@ export function validElevation(
|
||||
* 측점 chainage 위치의 계획고를 계획선 샘플에서 선형보간한다.
|
||||
* 범위를 벗어나면 양 끝값으로 클램프하며, 계획선이 없으면 undefined를 반환해 지반고 폴백을 유도한다.
|
||||
*/
|
||||
/**
|
||||
* 저장된 횡단 설계가 **현재 계획선과 어긋난 측점**을 찾는다(B05·B06 공용 규칙).
|
||||
*
|
||||
* 횡단 설계는 계산 당시 계획고(`design.design_elevation_m`)를 기준으로 설계선 좌표를
|
||||
* 굳혀 둔다. 계획선이 그 뒤에 바뀌면(사용자 편집·확정, 배수 최소고 같은 백엔드 규칙
|
||||
* 도입) 설계선은 옛 계획고 자리에 남는데 화면의 계획고 십자선·3D는 최신 계획선을 쓴다
|
||||
* — 두 기준이 어긋나 횡단도·3D가 종단을 안 따라오는 것처럼 보인다(2026-08-23 실측:
|
||||
* 한 노선에서 최대 2.21m 어긋남). 재계산 대상을 한 규칙으로 판정해 두 화면이 같은
|
||||
* 시점에 같은 조치를 하게 한다.
|
||||
*/
|
||||
export function staleDesignChainages(
|
||||
detail: {
|
||||
longitudinal: { design_profiles?: DesignProfile[] };
|
||||
cross_sections: Array<{ chainage_m: number; design?: { design_elevation_m: number } | null }>;
|
||||
},
|
||||
toleranceM = 1e-3,
|
||||
): number[] {
|
||||
const profiles = detail.longitudinal.design_profiles;
|
||||
if (!profiles?.length) return [];
|
||||
return detail.cross_sections
|
||||
.filter((section) => {
|
||||
const design = section.design;
|
||||
if (!design) return false;
|
||||
const planned = designElevationAt(profiles, section.chainage_m);
|
||||
return planned !== undefined && Math.abs(planned - design.design_elevation_m) > toleranceM;
|
||||
})
|
||||
.map((section) => section.chainage_m);
|
||||
}
|
||||
|
||||
export function designElevationAt(
|
||||
designProfiles: DesignProfile[] | undefined,
|
||||
chainageM: number,
|
||||
|
||||
Reference in New Issue
Block a user