fix(B06): 초안 되얹기는 온 칸만 · 조정값 쓰기는 저장값에서 출발(PLAN 24-4a)

- sub8 24-7a 되얹기(일부 칸)와 폼 「칸 전부」 규칙을 가름 — 되얹기는 안 온 칸 · 기슭막이 높이 · 형태를 건드리지 않음
- 기슭막이 조작값 쓰기가 세션이 없을 때 저장된 좌우 · 상하에서 출발 — 새 탭에서 폼 칸 하나만 고쳐도 저장값이 지워지던 것
- 시험 test_b06_adjust_single_form 7건(되얹기 1 추가)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019ACSGaHdLgnkEkoA4LDtMU
This commit is contained in:
2026-09-30 00:00:21 +09:00
co-authored by Claude Opus 5.5
parent 365ff5a13d
commit 767adc4a42
4 changed files with 33 additions and 6 deletions
@@ -33,11 +33,14 @@ export interface PipeOptionsContext {
* 덮는 측점 카드를 다시 그린다 — 조정창 조작과 같은 흐름이라 도면이 바로 따라온다
* (2026-08-29 사용자 보고: 값만 바뀌고 횡단도가 그대로였다).
* 여기서는 화면 캐시만 고친다 — 영구저장은 [저장]·[확정] 몫이다.
* `whole` — 폼이 보낸 **칸 전부**(비운 칸 = 지움 · 기슭막이 높이 · 형태 없음 = 자동 · 24-4a).
* 초안 되얹기처럼 일부만 오면 false — 온 칸만 얹는다.
*/
export function applyPipeOptionsToCache(
ctx: PipeOptionsContext,
chainageM: number,
patch: Record<string, number | string>,
whole = true,
): void {
const owner = ctx
.detail()
@@ -66,6 +69,7 @@ export function applyPipeOptionsToCache(
};
/** 폼 칸 값 — 폼이 보낸 것이 칸 전부라 비운 칸은 지운다(계획서 24-4a 빈 값). */
const own = <T extends object>(target: T, field: keyof T, value: unknown): void => {
if (value === undefined && !whole) return;
if (value === undefined) delete (target as Record<string, unknown>)[field as string];
else (target as Record<string, unknown>)[field as string] = value;
};
@@ -98,9 +102,12 @@ export function applyPipeOptionsToCache(
] as const) {
const height = num(`${prefix}_revet_height_m`);
const form = patch[`${prefix}_revet_form`];
if (!whole && height === undefined && typeof form !== "string") continue;
// 빈 칸 = 자동(null) · 일부만 온 되얹기는 안 온 칸을 그대로(undefined).
const blank = whole ? null : undefined;
ctx.revetOffset.update(owner.chainage_m, role, {
h: height ?? null,
m: typeof form === "string" ? form : null,
h: height ?? blank,
m: typeof form === "string" ? form : blank,
});
// 형태마다 높이 한계가 있다(돌쌓기(메) 2.0m 등). 기하가 잘라 낸 실제 높이를
// 폼에 되돌린다 — 숫자만 커지고 그림은 그대로인 상태를 남기지 않는다.
@@ -156,6 +163,6 @@ export function replayDraftPipeOptions(
const patch = Object.fromEntries(
Object.entries(pipe.options ?? {}).filter(([key]) => !ADJUST_OWNED.test(key)),
);
if (Object.keys(patch).length) applyPipeOptionsToCache(ctx, pipe.chainage_m, patch);
if (Object.keys(patch).length) applyPipeOptionsToCache(ctx, pipe.chainage_m, patch, false);
}
}
@@ -272,7 +272,10 @@ export function createStationControls(deps: StationControlDeps): StationControls
},
update: (chainageM, role, patch) => {
const key = revetKey(chainageM, role);
const current = revetShifts.get(key) ?? { ...ZERO_ADJUST };
// 세션이 없으면 **저장된 조작값**에서 출발한다 — 자동(0)에서 출발하면 폼 칸 하나만 고쳐도
// 저장해 둔 좌우 · 상하가 지워진다(계획서 24-4a).
const saved = sectionAt(chainageM)?.design?.revet_adjust?.[role];
const current = revetShifts.get(key) ?? { ...ZERO_ADJUST, ...saved };
const next: WallAdjust = {
x: clampMove(patch.x ?? current.x),
// d: null = 기본 자리(배관 벽 — 경사길이 4m 지점) 유지.
@@ -242,11 +242,18 @@ const ctx = {
applyPipeOptionsToCache(ctx, 100, { outlet_revet_height_m: 2.5, outlet_revet_form: "돌쌓기(메)" });
const filled = updates.splice(0);
applyPipeOptionsToCache(ctx, 100, {});
// 초안 되얹기(24-7a)는 일부 칸만 — 안 온 칸 · 기슭막이 높이 · 형태를 건드리지 않는다.
const clearedSpec = { outlet: { ...section.culvert.outlet }, inlet: { ...section.culvert.inlet } };
section.culvert.outlet = { revet_height_m: 3, revet_form: "콘크리트" };
const clearedUpdates = updates.splice(0);
applyPipeOptionsToCache(ctx, 100, { outlet_revet_length_m: 12 }, false);
out.replay = { updates: updates.splice(0), outletSpec: { ...section.culvert.outlet } };
updates.push(...clearedUpdates);
out.cache = {
filled,
cleared: updates,
outletSpec: section.culvert.outlet,
inletSpec: section.culvert.inlet,
outletSpec: clearedSpec.outlet,
inletSpec: clearedSpec.inlet,
};
process.stdout.write(JSON.stringify(out));
@@ -67,6 +67,16 @@ def test_폼_값이_그림으로_가고_비운_칸은_자동(out) -> None:
assert cache["outletSpec"] == {} and cache["inletSpec"] == {}
def test_초안_되얹기는_온_칸만(out) -> None:
"""24-7a 되얹기는 일부 칸만 보낸다 — 높이 · 형태(조정값)와 안 온 칸은 그대로."""
assert out["replay"]["updates"] == []
assert out["replay"]["outletSpec"] == {
"revet_height_m": 3,
"revet_form": "콘크리트",
"revet_length_m": 12,
}
def test_사본_줄은_모두_폼_칸으로_옮겨_붙는다() -> None:
"""사본이 만드는 줄 종류가 옮김 목록에 다 있어야 [횡단 조정] 상자 없이도 보인다."""
dock = (ROOT / "B06_Section" / "B06_Section_UI_Adjust_Dock.ts").read_text(encoding="utf-8")