feat(511-B B-2 stations 줄 엶): 계산 자리 판 stations(R5 R6 R7 F3) 바꿀 수 있음 · 기본 both 그대로 · 「서버」 = 묶음 stations 단계가 잠잠 뒤 서버 Node stations.list(같은 buildStations · 못 내면 화면 셈 · 화면 셈은 동기 그대로) · 창구가 stations.list 에 서버 구조물 정의를 붙임(브라우저 정의 안 씀) · 까닭 글 stations · 309-2 · 511-B1 시험 = 열린 줄 — 요구 320-4
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -27,6 +27,7 @@ import { publishPagePipeline } from "./design_pipeline_page_publish";
|
||||
import { SOURCE_INPUT } from "./logic/design_pipeline_types";
|
||||
export { pipelineWork } from "./logic/design_pipeline";
|
||||
import { createStationsStage, snapOptions, stationDiff } from "./logic/design_pipeline_stations";
|
||||
import { stationsOnServer } from "./design_stations_server";
|
||||
import type {
|
||||
ChangeSource,
|
||||
FollowPatch,
|
||||
@@ -293,6 +294,7 @@ function createPagePipeline(
|
||||
intervalM: options.stationIntervalM,
|
||||
types: () => enabled,
|
||||
saved: () => plan,
|
||||
remote: stationsOnServer, // 계산 자리 stations 「서버」(511-B B-2)
|
||||
}),
|
||||
ground: createGroundStage(groundDeps),
|
||||
planline: createPlanlineStage({
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/* =============================================================================
|
||||
* A00_Common/design/design_stations_server.ts
|
||||
* 계산 자리 stations 「서버」(PLAN 511-B B-2) — 묶음 stations 단계 측점 목록을 서버 Node
|
||||
* `stations.list`(같은 `buildStations`)로. 판이 both 면 null(화면 셈) · 서버 값만 · 잠잠 뒤 ·
|
||||
* 끊김 · 실패 = undefined(단계가 화면 셈으로). 구조물 정의는 서버 창구가 붙임.
|
||||
* ========================================================================== */
|
||||
|
||||
import { plusStations } from "@util/common_util_structure_station";
|
||||
import { calcOnServerNow } from "./design_calc_sites";
|
||||
import { calcOnServerOnly } from "./design_calc_server";
|
||||
import type { StationsArgs } from "./logic/design_pipeline_stations";
|
||||
import type { PipelineStation } from "./logic/design_pipeline_types";
|
||||
|
||||
export function stationsOnServer(
|
||||
args: StationsArgs,
|
||||
signal: AbortSignal,
|
||||
): Promise<PipelineStation[] | undefined> | null {
|
||||
if (!calcOnServerNow("stations")) return null;
|
||||
const [line, curves, pipes, structures, options] = args;
|
||||
const stations = {
|
||||
line,
|
||||
curves,
|
||||
pipes,
|
||||
structures,
|
||||
types: options.types,
|
||||
plus: [...(options.plus ?? plusStations())],
|
||||
interval_m: options.intervalM,
|
||||
include_endpoint: options.includeEndpoint,
|
||||
station_snap: options.stationSnap ?? false,
|
||||
station_snap_step: options.stationSnapStep ?? null,
|
||||
};
|
||||
return calcOnServerOnly<PipelineStation[]>(
|
||||
"stations",
|
||||
"stations.list",
|
||||
{ run: ["stations.list"], inputs: { stations }, pick: (results) => results["stations.list"] },
|
||||
signal,
|
||||
);
|
||||
}
|
||||
@@ -61,18 +61,27 @@ export function snapOptions(
|
||||
};
|
||||
}
|
||||
|
||||
/** stations 단계 — 입력 관 · 구조물(follow 뒤 누가거리) · 그린 노선 · 곡선표 · 스냅(초안 · 없으면 저장 노선). */
|
||||
/** `buildStations` 인자 한 벌 — 화면 셈 · 서버 셈(계산 자리 「서버」)이 같은 값을 받음. */
|
||||
export type StationsArgs = Parameters<typeof buildStations>;
|
||||
|
||||
/** stations 단계 — 입력 관 · 구조물(follow 뒤 누가거리) · 그린 노선 · 곡선표 · 스냅(초안 · 없으면 저장 노선).
|
||||
* `remote` = 계산 자리 「서버」면 서버 Node 값(같은 `buildStations` · 511-B B-2) · null = 화면 셈 · 서버가
|
||||
* 못 내면(끊김 · 실패) 화면 셈. */
|
||||
export function createStationsStage(
|
||||
options: Omit<StationsOptions, "stationSnap" | "stationSnapStep" | "types"> & {
|
||||
types: () => ReadonlyArray<PlanType>;
|
||||
saved?: () => SavedSnap | null;
|
||||
remote?: (
|
||||
args: StationsArgs,
|
||||
signal: AbortSignal,
|
||||
) => Promise<PipelineStation[] | undefined> | null;
|
||||
},
|
||||
): StageFn<"stations"> {
|
||||
const { saved, ...rest } = options;
|
||||
const { saved, remote, ...rest } = options;
|
||||
return (ctx: StageContext<"stations">) => {
|
||||
const route = ctx.out.route;
|
||||
if (!route) return [];
|
||||
const next = buildStations(
|
||||
const args: StationsArgs = [
|
||||
route.line,
|
||||
route.curves,
|
||||
(ctx.inputs.pipes as PlanPipe[] | null) ?? [],
|
||||
@@ -83,8 +92,11 @@ export function createStationsStage(
|
||||
...snapOptions(ctx.inputs.route, saved?.()),
|
||||
plus: ctx.inputs.plusStations ?? undefined, // follow 가 옮긴 추가 측점(459 · 가게보다 먼저)
|
||||
},
|
||||
);
|
||||
return reuseOutside(next, ctx.prev, ctx.range);
|
||||
];
|
||||
const done = (next: PipelineStation[] | undefined) =>
|
||||
reuseOutside(next ?? buildStations(...args), ctx.prev, ctx.range);
|
||||
const asked = remote?.(args, ctx.signal);
|
||||
return asked ? asked.then(done) : done(undefined); // 화면 셈 = 동기 그대로
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -50,6 +50,11 @@ async def run_calc_endpoint(project_id: UUID, request: Request) -> dict | JSONRe
|
||||
return _error(400, "inputs = 객체")
|
||||
if SERVER_ONLY_IDS.intersection(run):
|
||||
return _error(400, "서버 측점 생성은 창구 밖")
|
||||
if "stations.list" in run and isinstance(inputs.get("stations"), dict):
|
||||
# 심는 측점 · 카드 이름 표 = 서버 구조물 정의(서버 측점 생성과 같은 표 · 511-B B-2)
|
||||
from B05_Profile.B05_Profile_Engine_Sections_Node import structure_table
|
||||
|
||||
inputs["stations"]["definition"] = (await asyncio.to_thread(structure_table))[1]
|
||||
try:
|
||||
answer = await asyncio.to_thread(run_calc_served, run, inputs)
|
||||
except ValueError as exc:
|
||||
|
||||
@@ -30,7 +30,7 @@ GROUPS: tuple[dict[str, Any], ...] = (
|
||||
"why": "quantity",
|
||||
},
|
||||
{"key": "carry", "nos": ["R9", "F10"], "switchable": True, "fixed": None, "why": "carry"},
|
||||
# 묶음 단계 다섯(511-B · 단계째 서버 Node `stage.*`) — 짝 모으기 B-2 ~ B-5 끝난 줄부터 엶
|
||||
# 묶음 단계(511-B · 단계째 서버 Node `stage.*`) — 짝 모으기 B-3 ~ B-5 끝난 줄부터 엶
|
||||
{"key": "route", "nos": ["R1", "R3"], "switchable": False, "fixed": "both", "why": "pair_B5"},
|
||||
{
|
||||
"key": "follow",
|
||||
@@ -39,12 +39,13 @@ GROUPS: tuple[dict[str, Any], ...] = (
|
||||
"fixed": "both",
|
||||
"why": "pair_B5",
|
||||
},
|
||||
# 측점(511-B B-2 · 서버 측점 생성 = Node `stations.sections` · 조작 중 「서버」 = `stations.list`)
|
||||
{
|
||||
"key": "stations",
|
||||
"nos": ["R5", "R6", "R7", "F3"],
|
||||
"switchable": False,
|
||||
"fixed": "both",
|
||||
"why": "pair_B2",
|
||||
"switchable": True,
|
||||
"fixed": None,
|
||||
"why": None,
|
||||
},
|
||||
{
|
||||
"key": "profile",
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"corridor_mesh": "both",
|
||||
"quantity": "both",
|
||||
"carry": "both",
|
||||
"stations": "both",
|
||||
"fixed_browser": "both"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,8 +63,9 @@ def test_묶음_여덟_줄_등록부_전부():
|
||||
"corridor_mesh",
|
||||
"quantity",
|
||||
"carry",
|
||||
"stations",
|
||||
"fixed_browser",
|
||||
} # 511-A 셋 더
|
||||
} # 511-A 셋 더 · 511-B B-2 stations
|
||||
for group in calc_sites.GROUPS:
|
||||
got = {sites[no] for no in group["nos"]}
|
||||
if group["fixed"] == "browser":
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
못박는 것
|
||||
- 단계 id 다섯(`stage.route` · `follow` · `stations` · `profile` · `cross`) = TS 표 =
|
||||
파이썬 `STAGE_IDS` · `available`(등록부 `node:`) 밖 · 창구(`run_calc_served`)가 받음.
|
||||
- 판 = 단계 다섯 줄(잠금 · 기본 both · 까닭 「짝 모으는 중 · B-n」) · F9 = 폼 기본값 ·
|
||||
- 판 = 단계 줄(잠금 · 기본 both · 까닭 「짝 모으는 중 · B-n」 · stations = B-2 가 엶) · F9 = 폼 기본값 ·
|
||||
커밋한 판 그대로.
|
||||
- 고정 자료(`fixtures/golden_472`) — Node 단계가 저장 노선 한 바퀴를 돎
|
||||
(측점 = 저장 상세 측점 0.01 m).
|
||||
@@ -78,7 +78,6 @@ def test_판_다섯_줄_잠금_기본값_그대로():
|
||||
expected = {
|
||||
"route": (["R1", "R3"], "pair_B5"),
|
||||
"follow": (["P5", "P6", "F1", "F2"], "pair_B5"),
|
||||
"stations": (["R5", "R6", "R7", "F3"], "pair_B2"),
|
||||
"profile": (["P1", "P2", "P3", "P4", "P8"], "pair_B3"),
|
||||
"cross": (["G2", "G4", "C1", "C2", "C3", "C5", "C6", "F4", "F5", "F6"], "pair_B4"),
|
||||
}
|
||||
@@ -93,6 +92,7 @@ def test_판_다섯_줄_잠금_기본값_그대로():
|
||||
"corridor_mesh",
|
||||
"quantity",
|
||||
"carry",
|
||||
"stations", # B-2 끝 = 엶(511-B B-2)
|
||||
"fixed_browser",
|
||||
}
|
||||
committed = subprocess.run( # noqa: S603 — 고정 실행 파일
|
||||
@@ -103,7 +103,7 @@ def test_판_다섯_줄_잠금_기본값_그대로():
|
||||
).stdout
|
||||
assert calc_sites.PATH.read_bytes().replace(b"\r\n", b"\n") == committed.replace(b"\r\n", b"\n")
|
||||
locale = (ROOT / "ui_template" / "ui_template_locale_m1.ts").read_text(encoding="utf-8")
|
||||
for n in (2, 3, 4, 5):
|
||||
for n in (3, 4, 5):
|
||||
assert f'B01_CalcSites_Why_pair_B{n}: ["짝 모으는 중 · B-{n}"' in locale
|
||||
|
||||
|
||||
|
||||
@@ -213,3 +213,32 @@ def test_이월_열쇠_Node_출력_없으면_파이썬(project):
|
||||
assert keyed_stations([*stations, {"chainage_m": 1.0}]) is None # 섞이면(구조물 측점 덧) 파이썬
|
||||
route_save = (ROOT / "B05_Profile/B05_Profile_Route_Save.py").read_text(encoding="utf-8")
|
||||
assert route_save.count("keyed_stations(") == 2
|
||||
|
||||
|
||||
def test_창구_측점_목록에_서버_정의_붙임(monkeypatch):
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from B06_Section import B06_Section_Router_Calc as calc_router
|
||||
|
||||
seen = []
|
||||
monkeypatch.setattr(
|
||||
calc_router, "run_calc_served", lambda run, inputs: seen.append(inputs) or {"results": {}}
|
||||
)
|
||||
app = FastAPI()
|
||||
app.include_router(calc_router.router)
|
||||
url = "/api/projects/11111111-2222-3333-4444-555555555555/calc"
|
||||
body = {"run": ["stations.list"], "inputs": {"stations": {"line": [], "definition": {"x": 1}}}}
|
||||
assert TestClient(app).post(url, json=body).status_code == 200
|
||||
definition = seen[0]["stations"]["definition"]
|
||||
assert definition != {"x": 1} and definition["types"] # 브라우저가 보낸 정의 안 씀
|
||||
|
||||
|
||||
def test_판_stations_줄_열림_기본_both():
|
||||
from common_util import common_util_calc_sites as calc_sites
|
||||
|
||||
group = next(g for g in calc_sites.GROUPS if g["key"] == "stations")
|
||||
assert group["switchable"] and group["fixed"] is None
|
||||
assert calc_sites.read_sites()["sites"]["stations"] == "both"
|
||||
page = (ROOT / "A00_Common/design/design_pipeline_page.ts").read_text(encoding="utf-8")
|
||||
assert "remote: stationsOnServer" in page
|
||||
|
||||
@@ -115,7 +115,10 @@ export const ui_locales_m1 = {
|
||||
"끄면 잠잠 뒤 서버 셈 · F10 = 브라우저 부름 없음(판만)",
|
||||
"Off = server calc after a pause · F10 = no browser call",
|
||||
],
|
||||
B01_CalcSites_Why_pair_B2: ["짝 모으는 중 · B-2", "Merging pairs · B-2"],
|
||||
B01_CalcSites_Why_stations: [
|
||||
"끄면 잠잠 뒤 서버 측점(같은 TS) · [저장] 측점 = 서버 Node · DTM 아닌 지표면은 파이썬",
|
||||
"Off = server stations after a pause (same TS) · [Save] stations = server Node · non-DTM surface = Python",
|
||||
],
|
||||
B01_CalcSites_Why_pair_B3: ["짝 모으는 중 · B-3", "Merging pairs · B-3"],
|
||||
B01_CalcSites_Why_pair_B4: ["짝 모으는 중 · B-4", "Merging pairs · B-4"],
|
||||
B01_CalcSites_Why_pair_B5: ["짝 모으는 중 · B-5", "Merging pairs · B-5"],
|
||||
|
||||
Reference in New Issue
Block a user