From 57c36a9d150c52776f3c174e578648af6785875d Mon Sep 17 00:00:00 2001 From: umsangdon Date: Thu, 22 Oct 2026 12:48:15 +0900 Subject: [PATCH] =?UTF-8?q?feat(511-A=20=E2=91=A3=20=EC=9D=B4=EC=9B=94=20?= =?UTF-8?q?=EC=97=B4=EA=B8=B0):=20carry=20=3D=20server=20=EB=A9=B4=20R9=20?= =?UTF-8?q?=EC=B8=A1=EC=A0=90=20=EC=97=B4=EC=87=A0=20=EB=84=98=EA=B9=80?= =?UTF-8?q?=EC=9D=84=20=EC=9E=A0=EC=9E=A0=20=EB=92=A4=20=EC=84=9C=EB=B2=84?= =?UTF-8?q?(=EA=B0=99=EC=9D=80=20carryKeyedStates)=EA=B0=80=20=EC=85=88=20?= =?UTF-8?q?=E2=86=92=20=EB=B8=8C=EB=9D=BC=EC=9A=B0=EC=A0=80=20=EA=B0=92?= =?UTF-8?q?=EA=B3=BC=20=EB=8C=80=EC=A1=B0=20=C2=B7=20=EB=8B=A4=EB=A5=B4?= =?UTF-8?q?=EB=A9=B4=20=EA=B7=B8=20=EB=85=B8=EC=84=A0=20=EC=B9=B8=EC=9D=84?= =?UTF-8?q?=20=EC=84=9C=EB=B2=84=20=EA=B0=92=EC=9C=BC=EB=A1=9C=20=C2=B7=20?= =?UTF-8?q?F10=20=3D=20=EB=B8=8C=EB=9D=BC=EC=9A=B0=EC=A0=80=20=EB=B6=80?= =?UTF-8?q?=EB=A6=84=20=EC=97=86=EC=9D=8C(=ED=8C=90=EB=A7=8C)=20=C2=B7=20?= =?UTF-8?q?=EA=B1=B0=EC=9A=B8=20=EC=8B=9C=ED=97=98=20=EC=85=8B=20=E2=80=94?= =?UTF-8?q?=20=EC=9A=94=EA=B5=AC=20320-4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- B06_Section/B06_Section_Station_Keys.ts | 23 +++++++++---- resources/tester/helper_511_a_carry.ts | 10 ++++++ resources/tester/test_511_a_calc_browser.py | 6 ++++ resources/tester/test_511_a_calc_mirror.py | 36 +++++++++++++++++++++ 4 files changed, 69 insertions(+), 6 deletions(-) create mode 100644 resources/tester/helper_511_a_carry.ts diff --git a/B06_Section/B06_Section_Station_Keys.ts b/B06_Section/B06_Section_Station_Keys.ts index 532cef832..78b91350d 100644 --- a/B06_Section/B06_Section_Station_Keys.ts +++ b/B06_Section/B06_Section_Station_Keys.ts @@ -22,6 +22,7 @@ import { renameChainageKeys } from "./logic/B06_Section_Station_Swap"; import { afterAnySwap } from "./B06_Section_Swap_Bus"; import { pipeIdAt, pipeIdNear } from "../A00_Common/design/design_pipe_ids"; import { showToast } from "@ui/ui_template_elements"; +import { calcChecked } from "../A00_Common/design/design_calc_server"; import { chainageKey, looseChainage, type StationKeyRole } from "@util/common_util_station_key"; import { inferStationInterval } from "@util/common_util_svg"; import type { CrossSection, SectionDetailResponse } from "./B06_Section_Api_Fetch"; @@ -253,12 +254,22 @@ export function carryStationDrafts( projectId: string, fromRouteId: number, toRouteId: number, - { quiet = false }: { quiet?: boolean } = {}, + { quiet = false, applyTo = toRouteId }: { quiet?: boolean; applyTo?: number } = {}, ): number { - const { states, outside } = carryKeyedStates( - (name) => readRaw(name, projectId, fromRouteId), - lists.get(scopeOf(projectId, fromRouteId)), - lists.get(scopeOf(projectId, toRouteId)), + // 계산 자리 carry = server 면 잠잠 뒤 같은 입력 서버 셈 → 다르면 그 칸을 서버 값으로(applyTo · 511-A) + const read = Object.fromEntries( + KEYED_STATES.map((name) => [name, readRaw(name, projectId, fromRouteId)]), + ) as Record; + const { states, outside } = calcChecked( + "carry", + "stations.carry_keyed", + (from: typeof read, before?: Keyed[], after?: Keyed[]) => + carryKeyedStates((name) => from[name], before, after), + [read, lists.get(scopeOf(projectId, fromRouteId)), lists.get(scopeOf(projectId, toRouteId))], + { + apply: (server) => + KEYED_STATES.forEach((name) => writeState(name, server.states[name], projectId, applyTo)), + }, ); for (const name of KEYED_STATES) writeState(name, states[name], projectId, toRouteId); if (outside.size && !quiet) @@ -275,7 +286,7 @@ const NEXT_ROUTE = -1; */ export function carryStationKeys(projectId: string, routeId: number, next: Keyed[]): number { lists.set(scopeOf(projectId, NEXT_ROUTE), next); - const outside = carryStationDrafts(projectId, routeId, NEXT_ROUTE); + const outside = carryStationDrafts(projectId, routeId, NEXT_ROUTE, { applyTo: routeId }); for (const name of KEYED_STATES) { writeState(name, readRaw(name, projectId, NEXT_ROUTE), projectId, routeId); writeState(name, null, projectId, NEXT_ROUTE); diff --git a/resources/tester/helper_511_a_carry.ts b/resources/tester/helper_511_a_carry.ts new file mode 100644 index 000000000..d052fae2e --- /dev/null +++ b/resources/tester/helper_511_a_carry.ts @@ -0,0 +1,10 @@ +/* PLAN 511-A 거울 — R9 브라우저 자리(`carryStationDrafts`)가 부르는 꼴 그대로(읽기 = 칸 이름 → 값 표). */ +import { + carryKeyedStates, + type Keyed, + type KeyedState, + type Raw, +} from "../../B06_Section/logic/B06_Section_Route_Carry"; + +export const carryWith = (from: Record, before?: Keyed[], after?: Keyed[]) => + carryKeyedStates((name) => from[name], before, after); diff --git a/resources/tester/test_511_a_calc_browser.py b/resources/tester/test_511_a_calc_browser.py index 4c45b0e84..0519b588f 100644 --- a/resources/tester/test_511_a_calc_browser.py +++ b/resources/tester/test_511_a_calc_browser.py @@ -71,3 +71,9 @@ def test_자리마다_판_열쇠_저장_길_안_부름(): assert "design_calc_server" not in _text(rel), rel page = _text("A00_Common/design/design_pipeline_page.ts") assert len(page.splitlines()) <= 699 and "design_calc_server" not in page + + +def test_이월_R9_자리_판_열쇠(): + keys = _text("B06_Section/B06_Section_Station_Keys.ts") + assert '"carry",\n "stations.carry_keyed",' in keys + assert "carryStationDrafts(projectId, routeId, NEXT_ROUTE, { applyTo: routeId })" in keys diff --git a/resources/tester/test_511_a_calc_mirror.py b/resources/tester/test_511_a_calc_mirror.py index 066fc5206..7391e1498 100644 --- a/resources/tester/test_511_a_calc_mirror.py +++ b/resources/tester/test_511_a_calc_mirror.py @@ -92,3 +92,39 @@ def test_quantity_F7_C4_E1_서버_브라우저_같음(tmp_path): for index, case in enumerate(STRUCTURE_CASES): got = _served(["cross.berm_spans"], {"berm": {"structures": case, "chainages": []}}) assert got["cross.berm_spans"]["spans"] == browser[2 + index], index + + +# ── ④ 이월 · 다시 얹기(R9 · F10 = 브라우저 부름 없음) ────────────────────────────────── + + +def test_carry_R9_서버_브라우저_같음(tmp_path): + seat = {"x": 10.0, "y": 5.0, "left": [0.0, 1.0]} + states = { + "crossdesign": {"grid:1": {"ditch_type": "U"}, "20.00": {"paved": True}}, + "shoulderext": {"grid:2": 0.5, "@at": {"grid:2": 40.0}, "@seat": {"grid:2": seat}}, + "bermdepth": {"at:55.00": 1.0}, + "rockb": {"30.00": -2.5}, + "cutslope": {}, + } + before = [ + {"key": "grid:1", "chainageM": 20.0, "seat": seat}, + {"key": "grid:2", "chainageM": 40.0}, + {"key": "end:EP", "chainageM": 60.0}, + ] + after = [ + {"key": "grid:1", "chainageM": 22.0}, + {"key": "grid:2", "chainageM": 41.0, "seat": seat}, + {"key": "end:EP", "chainageM": 58.0}, + ] + empty = dict.fromkeys(states, {}) # 브라우저 읽기 = 빈 칸도 {}(readRaw) + cases = [[states, before, after], [states, before, None], [empty, None, after]] + browser = _browser( + tmp_path, + [ + {"mod": "resources/tester/helper_511_a_carry.ts", "fn": "carryWith", "args": case} + for case in cases + ], + ) + for index, case in enumerate(cases): + assert _trial("stations.carry_keyed", case) == browser[index], index + assert browser[0]["states"]["crossdesign"] # 넘김이 실제로 일어남