feat(횡단초안): 344-3 측점 열쇠 — 세션 횡단 초안을 측점 열쇠로 · 옛 초안 옮김 · 노선 바뀜 넘김 · 노선 밖 고침 알림

- 열쇠 규칙 한 벌 = common_util_station_key(ts · py 짝 · 거울 시험) — 일반 grid:k · 관 pipe:<id> · 구조물 structure:<id> · 시종점 end:BP/EP · id 모름 at:<0.01 m>
- 세션 칸 다섯(카드 선택 · 노견 확장 · 소단 깊이 · 암 경계 · 암 경사) = 측점 열쇠로 담음 · 부르는 쪽은 그대로 누가거리로 읽고 씀 · 칸마다 쓴 때 자리를 함께 담아 구조물 id 를 아직 모를 때도 그 측점 고침을 읽음
- 옛 초안(누가거리 열쇠)은 읽을 때 측점 열쇠로 옮김 · 지금 측점에 없는 고침은 지우지 않고 남김
- 측점 목록 = 상세가 초안 측점을 얹을 때 · 다시 셈 때 세움(관 id = 관 장부 · 구조물 id = 측점 계획 · 구조물 목록)
- 노선 바뀜 넘김 carryStationDrafts — 카드 선택 넷(지반 유형 · 측구 종류 · 측구 선택 · 2단 경사) · 암 경계 · 암 경사만 · 단면 유형 · 측구 방향은 새 지형이 정함 · 사라진 측점 = 「노선 밖 고침 N 건」 한 줄(부르는 자리 = 3파)
- 옛 시험 셋 새 창구로 맞춤

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QfHKQs7J3hQELKMWQMUJEy
This commit is contained in:
2026-10-16 11:45:31 +09:00
co-authored by Claude Opus 5.5
parent 70ad119cf3
commit 1c090c0f5e
12 changed files with 527 additions and 42 deletions
+16 -22
View File
@@ -8,9 +8,11 @@
* 계산은 브라우저가 하고(`common_util_cross_design.ts`), 선택값은 여기 담긴다.
*
* 담는 것은 **사용자가 고른 것만**이다 — 계산 결과(면적·설계선)는 담지 않는다.
* 세션 열쇠 = 측점 열쇠(344-3 · `B06_Section_Station_Keys`) · 부르는 쪽에는 누가거리로 내줌.
* ========================================================================== */
import { readState, writeState } from "../A00_Common/b_page_state";
import { chainageKey } from "@util/common_util_station_key";
import { readKeyed, writeKeyed } from "./B06_Section_Station_Keys";
/** 카드에서 고를 수 있는 값 한 벌 — 서버 `CrossDesignRequest` 와 같은 이름을 쓴다. */
export interface CrossDesignChoice {
@@ -25,14 +27,9 @@ export interface CrossDesignChoice {
ditch_choice?: boolean | null;
}
type ChoiceMap = Record<string, CrossDesignChoice>;
/** 측점 키 — 암 경계선 저장소와 같은 규칙(0.01m 단위). */
const keyOf = (chainageM: number): string => chainageM.toFixed(2);
function readAll(projectId: string, routeId: number): ChoiceMap {
return readState<ChoiceMap>("crossdesign", projectId, routeId) ?? {};
}
/** 누가거리(0.01 m 글) → 선택값 — 지금 측점에 있는 것만. */
const readAll = (projectId: string, routeId: number) =>
readKeyed<CrossDesignChoice>("crossdesign", projectId, routeId);
/** 이 측점의 선택값(없으면 null). */
export function readCrossDesignChoice(
@@ -40,7 +37,7 @@ export function readCrossDesignChoice(
routeId: number,
chainageM: number,
): CrossDesignChoice | null {
return readAll(projectId, routeId)[keyOf(chainageM)] ?? null;
return readAll(projectId, routeId).get(chainageKey(chainageM)) ?? null;
}
/** 선택값을 세션에 담는다. 같은 측점의 앞선 값은 덮어쓴다. */
@@ -50,9 +47,8 @@ export function writeCrossDesignChoice(
chainageM: number,
choice: CrossDesignChoice,
): void {
const all = readAll(projectId, routeId);
all[keyOf(chainageM)] = choice;
writeState("crossdesign", all, projectId, routeId);
const at = chainageKey(chainageM);
writeKeyed("crossdesign", projectId, routeId, new Map([[at, choice]]), [at]);
}
/** [저장]·[확정]이 patch 로 실을 목록 — 누가거리(숫자) → 선택값. */
@@ -62,7 +58,7 @@ export function crossDesignChoices(
): Map<number, CrossDesignChoice> {
const choices = new Map<number, CrossDesignChoice>();
if (!projectId || routeId === null) return choices;
for (const [key, value] of Object.entries(readAll(projectId, routeId))) {
for (const [key, value] of readAll(projectId, routeId)) {
const chainage = Number(key);
if (Number.isFinite(chainage) && value) choices.set(chainage, value);
}
@@ -80,12 +76,11 @@ type SideState = "shoulderext" | "bermdepth";
function readSides(name: SideState, projectId: string | null, routeId: number | null) {
const out: ShoulderExtends = new Map();
if (!projectId || routeId === null) return out;
const raw = readState<Record<string, Partial<Record<ShoulderSide, number>>>>(
for (const [key, value] of readKeyed<Partial<Record<ShoulderSide, number>>>(
name,
projectId,
routeId,
);
for (const [key, value] of Object.entries(raw ?? {})) {
)) {
const chainage = Number(key);
if (Number.isFinite(chainage) && value) out.set(chainage, value);
}
@@ -100,11 +95,10 @@ function writeSide(
side: ShoulderSide,
valueM: number,
): void {
const all =
readState<Record<string, Partial<Record<ShoulderSide, number>>>>(name, projectId, routeId) ??
{};
all[keyOf(chainageM)] = { ...(all[keyOf(chainageM)] ?? {}), [side]: Math.max(valueM, 0) };
writeState(name, all, projectId, routeId);
const at = chainageKey(chainageM);
const own = readKeyed<Partial<Record<ShoulderSide, number>>>(name, projectId, routeId).get(at);
const next = { ...(own ?? {}), [side]: Math.max(valueM, 0) };
writeKeyed(name, projectId, routeId, new Map([[at, next]]), [at]);
}
/** 누가거리(숫자) → 쪽마다 노견 확장(세션에 있는 쪽만). */