fix(B05·B06 한 페이지): 노선 바뀐 저장 때 옛 저장 관 자리 조절 = 같은 관 id 의 새 자리로 (417-1 M2)
- 노선 저장 직전 저장 관(id · 옛 누가거리)을 떠 두고 저장 뒤 같은 id 새 자리로 — 초안으로 안 따라간 관 조절(첫 초안 측점 짝 · 관 id 규칙 갈림) - carryChainageStates 시설 풀이 = 옛 자리 → 새 자리 · 관 열쇠 → 새 자리 - 시험 test_417 옛 자리 짝 · 346-3 저장 차례(관 받기) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Se7ytqbohmBQnfYpwYFnYf
This commit is contained in:
@@ -119,6 +119,7 @@ export async function saveRouteDraft(
|
||||
projectId: string,
|
||||
body: RouteSaveBody,
|
||||
): Promise<RouteReplanResponse> {
|
||||
pipesBeforeSave.set(projectId, await savedPipes(projectId));
|
||||
const saved = await replanRoute(projectId, body);
|
||||
useDesignStore(projectId, null).set("routeDraft", null);
|
||||
// 노선이 바뀌면 조회 결과 캐시는 옛 노선 것 — 추천은 새 노선의 출발점이라 다시 적는다.
|
||||
@@ -141,6 +142,15 @@ export function routeSaveSummary(saved: RouteReplanResponse, outside = 0): strin
|
||||
return `노선 저장 — ${parts.join(" · ")}`;
|
||||
}
|
||||
|
||||
type SavedPipe = { id?: string; chainage_m: number };
|
||||
/** 노선 저장 직전 저장 관(id · 옛 누가거리) — 저장 뒤 같은 id 의 새 자리로 조절을 옮김(초안으로 안 따라간 고침 · 417). */
|
||||
const pipesBeforeSave = new Map<string, SavedPipe[]>();
|
||||
|
||||
const savedPipes = (projectId: string): Promise<SavedPipe[]> =>
|
||||
fetchDetailPipePoints(projectId)
|
||||
.then((res) => res.pipe_points)
|
||||
.catch(() => []);
|
||||
|
||||
/**
|
||||
* 노선 저장 뒤 세션 횡단 초안(옛 노선 측점 열쇠 · 누가거리 열쇠 조절 · 417) → 새 노선으로 넘기고 그 고침을 저장 —
|
||||
* 초안을 통째로 지우지 않는다. 사라진 측점 고침은 버림(수를 돌려줌 · 서버 `cross_dropped` 와 한 줄로).
|
||||
@@ -154,18 +164,22 @@ export async function carryAfterRouteSave(
|
||||
const detail = await loadSectionDetail(projectId, toRouteId);
|
||||
useStationKeys(projectId, toRouteId, detail);
|
||||
// 관 열쇠 → 새 자리 — 세션 id(`pipe-s3`) = 관 장부 · 저장 관 id(`p8017`) = 서버가 옮긴 관
|
||||
const saved = await fetchDetailPipePoints(projectId)
|
||||
.then((res) => res.pipe_points)
|
||||
.catch(() => []);
|
||||
const saved = await savedPipes(projectId);
|
||||
const before = pipesBeforeSave.get(projectId) ?? [];
|
||||
pipesBeforeSave.delete(projectId);
|
||||
const newAt = (id: string | undefined): number | null =>
|
||||
saved.find((pipe) => id !== undefined && pipe.id === id)?.chainage_m ?? null;
|
||||
const pipeAt = (key: string): number | null => {
|
||||
const id = key.startsWith("pipe:") ? key.slice(5) : null;
|
||||
if (!id) return null;
|
||||
if (id.startsWith("pipe-")) return pipeChainageOf(id, projectId);
|
||||
return saved.find((pipe) => pipe.id === id)?.chainage_m ?? null;
|
||||
return id.startsWith("pipe-") ? pipeChainageOf(id, projectId) : newAt(id);
|
||||
};
|
||||
// 옛 저장 관 자리에 남은 고침(초안으로 안 따라감) = 같은 관 id 의 새 자리
|
||||
const movedAt = (chainageM: number): number | null =>
|
||||
newAt(before.find((pipe) => Math.abs(pipe.chainage_m - chainageM) < 0.01)?.id);
|
||||
const outside =
|
||||
carryStationDrafts(projectId, fromRouteId, toRouteId, { quiet: true }) +
|
||||
carryChainageStates(projectId, fromRouteId, toRouteId, pipeAt);
|
||||
carryChainageStates(projectId, fromRouteId, toRouteId, { keyAt: pipeAt, movedAt });
|
||||
// 옮긴 고침을 새 상세 설계 칸에 — 같은 [저장]의 횡단 패치가 실음(사면 쉬프트 · 조절 · 소단 깊이 · 417)
|
||||
withDraftAdjusts(detail, projectId, toRouteId);
|
||||
withCarriedEdits(detail, projectId, toRouteId);
|
||||
|
||||
@@ -380,6 +380,14 @@ function carriedValue(name: KeyedState, value: unknown): unknown {
|
||||
return Object.keys(picked).length ? picked : null;
|
||||
}
|
||||
|
||||
/** 노선 저장 때 시설 측점 풀이(`carryChainageStates`). */
|
||||
export interface CarryFacilities {
|
||||
/** 옛 저장 시설 측점 누가거리 → 새 노선 그 시설 측점 누가거리(없으면 null). */
|
||||
movedAt?: (chainageM: number) => number | null | undefined;
|
||||
/** 관 · 구조물 열쇠 → 새 노선 누가거리(없으면 null). */
|
||||
keyAt?: (key: string) => number | null | undefined;
|
||||
}
|
||||
|
||||
/** 누가거리 열쇠 머리(`b` · 0.01 m 글 · `:역할`) — 조정창 맵 열쇠 꼴(`renameChainageKeys` 와 같음). */
|
||||
const CHAINAGE_HEAD = /^(b?)(-?\d+(?:\.\d+)?)(:.*)?$/;
|
||||
|
||||
@@ -387,16 +395,20 @@ const CHAINAGE_HEAD = /^(b?)(-?\d+(?:\.\d+)?)(:.*)?$/;
|
||||
* 노선 저장 뒤(새 노선 측점 열쇠를 세운 뒤) 누가거리 열쇠 칸(`ROUTE_CARRY_STATES` 중 측점 열쇠 칸 빼고)을 옛 노선
|
||||
* → 새 노선 측점으로 옮김 — 옛 누가거리 → 옛 측점 열쇠 → 새 누가거리. 구조물 조절은 관 · 구조물 열쇠만 ·
|
||||
* 못 옮긴 고침은 버림. 돌려주는 값 = 버린 측점 수(「노선 밖 고침」 · 측점 단위).
|
||||
* `facilityAt` = 관 · 구조물 열쇠 → 새 노선 누가거리(관 id 규칙이 갈림 — 세션 `pipe-s3` · 계산 줄 `pipe:p8017`).
|
||||
* 시설 풀이(`facilities`) = 옛 저장 시설 측점 → 새 노선 그 시설 측점(좌표 짝 · 초안으로 안 따라간 고침) · 관 ·
|
||||
* 구조물 열쇠 → 새 누가거리(관 id 규칙이 갈림 — 세션 `pipe-s3` · 계산 줄 `pipe:p8017`).
|
||||
*/
|
||||
export function carryChainageStates(
|
||||
projectId: string,
|
||||
fromRouteId: number,
|
||||
toRouteId: number,
|
||||
facilityAt: (key: string) => number | null | undefined = () => null,
|
||||
facilities: CarryFacilities = {},
|
||||
): number {
|
||||
const facilityAt = facilities.keyAt ?? (() => null);
|
||||
const outside = new Set<string>();
|
||||
const moveAt = (state: string, text: string): string | null => {
|
||||
const moved = facilities.movedAt?.(Number(text));
|
||||
if (moved !== null && moved !== undefined) return chainageKey(moved);
|
||||
const stable = keyAt(projectId, fromRouteId, Number(text));
|
||||
const structural = stable.startsWith("pipe:") || stable.startsWith("structure:");
|
||||
// 새 목록에 그 열쇠가 없으면 그 시설의 새 자리 · 없으면 같은 자리 측점(초안 측점 = 저장 측점)
|
||||
|
||||
@@ -38,7 +38,10 @@ const STUBS = {
|
||||
},
|
||||
"../B06_Section/B06_Section_Section_Store": {
|
||||
invalidateSectionDetail: (_p, routeId) => calls.push(`invalidate:${routeId ?? "all"}`),
|
||||
loadSectionDetail: async (_p, routeId) => (calls.push(`load:${routeId}`), { tag: routeId }),
|
||||
loadSectionDetail: async (_p, routeId) => (
|
||||
calls.push(`load:${routeId}`),
|
||||
{ tag: routeId, cross_sections: [] }
|
||||
),
|
||||
saveCachedCrossPatches: async (_p, routeId) => (calls.push(`crossSave:${routeId}`), 1),
|
||||
},
|
||||
"../B06_Section/B06_Section_Station_Keys": {
|
||||
|
||||
@@ -142,9 +142,14 @@ m.useStationKeys(
|
||||
detailOf([section(0, "bp"), section(45, "irregular", pipe), section(60, "ep")]),
|
||||
);
|
||||
m.writeState("revetx", { "45.00:outlet": wall }, P, R);
|
||||
m.carryChainageStates(P, R, R3, (key) => (key === "pipe:pipe-s1" ? 52 : null));
|
||||
m.carryChainageStates(P, R, R3, { keyAt: (key) => (key === "pipe:pipe-s1" ? 52 : null) });
|
||||
m.carryChainageStates(P, R, R4);
|
||||
out.splitResolved = raw("revetx", R3);
|
||||
out.splitDropped = raw("revetx", R4);
|
||||
// 옛 저장 시설 측점(초안으로 안 따라간 고침) — 좌표 짝으로 새 시설 측점에
|
||||
const R5 = 11;
|
||||
m.useStationKeyList(P, R5, list);
|
||||
m.carryChainageStates(P, R, R5, { movedAt: (c) => (c === 45 ? 52 : null) });
|
||||
out.movedByPlace = raw("revetx", R5);
|
||||
|
||||
process.stdout.write(JSON.stringify(out));
|
||||
|
||||
@@ -94,7 +94,8 @@ def test_추천_적용은_as_initial_저장_뒤_다시_세움(out):
|
||||
body = out["recommendBody"]
|
||||
assert body["as_initial"] is True and body["initial"] == {"mode": "earthwork"}
|
||||
assert body["draft"]["nodes"] == [[1, 1], [9, 9]]
|
||||
assert out["recommendCalls"] == ["overlay:on", "replan", "overlay:off"] # 잠금 막 하나
|
||||
# 잠금 막 하나 · 저장 직전 저장 관 떠 둠(417 — 저장 뒤 같은 관 id 새 자리로 조절 옮김)
|
||||
assert out["recommendCalls"] == ["overlay:on", "pipes", "replan", "overlay:off"]
|
||||
assert out["recommendDraftsLeft"] == {"route": None, "pipes": None} # 새 초기값에서 다시
|
||||
assert out["rebuilt"] == 1
|
||||
|
||||
|
||||
@@ -101,6 +101,8 @@ def test_조절은_관_따라_옮김_일반_측점_옹벽은_버림(out):
|
||||
def test_관_id_규칙이_갈려도_시설_자리로_옮김(out):
|
||||
assert out["splitResolved"] == {"52.00:outlet": {"x": 0.3, "d": None, "h": 1.5, "m": None}}
|
||||
assert out["splitDropped"] == {} # 풀이 없음 = 같은 자리 측점 없음 → 버림
|
||||
# 초안으로 안 따라간 옛 저장 시설 측점 고침 = 좌표 짝 시설 측점으로
|
||||
assert out["movedByPlace"] == {"52.00:outlet": {"x": 0.3, "d": None, "h": 1.5, "m": None}}
|
||||
|
||||
|
||||
def test_새_상세_설계_칸에_얹힘(out):
|
||||
|
||||
Reference in New Issue
Block a user