/* ============================================================================= * design_calc_profile.ts * 계산 자리 「서버」 종단 길(PLAN 511-B B-3) — 판(`design_calc_sites.ts`)이 profile 묶음을 server 로 둔 때 * 묶음 planline 의 새 지반 첫 계획선 꼭짓점(P1 · `profile.base`)과 profile 단계 계획선 짓기(P2 ~ P4 · P8 · * `profile.alignment`)를 서버 Node 가 같은 TS 로 셈(창구 `POST /projects/{id}/calc`). both 면 브라우저 그대로. * 서버 실패(끊김 밖) = 경고 한 줄 · 브라우저 값. * ========================================================================== */ import { calcOnServerNow } from "./design_calc_sites"; import { serverCalc } from "./design_calc_server"; import type { StageFn } from "./logic/design_pipeline_types"; import { groundFollowingBasePvi, groundProfile, type GroundFollowingInput, } from "../../B05_Profile/logic/B05_Profile_Grade_Ground"; import { profileStage } from "../../B05_Profile/logic/B05_Profile_Pipeline_Grade"; import { emptyEdits, type AlignmentNode, type ProfileAlignment, } from "../../B05_Profile/logic/B05_Profile_UI_Profile_Alignment"; const KEY = "profile"; const r6 = (value: number): number => Number(value.toFixed(6)); /** 서버 값 — 끊김은 그대로 던짐(묶음이 버림) · 그 밖 실패는 브라우저 값. */ async function orLocal(signal: AbortSignal, remote: () => Promise, local: () => T) { try { return await remote(); } catch (error) { if (signal.aborted) throw error; console.warn("[calc-server] profile 실패 — 브라우저 값", error); return local(); } } /** planline `groundPvi` — 서버면 첫 계획선 꼭짓점 약속(0.000001 m · `groundFollowingBasePvi` 꼴) · 아니면 null * (브라우저 셈 · 단계 동기). 판은 앞 ground 단계가 이미 받음(`calcOnServerNow`). */ export function groundPviAt( input: GroundFollowingInput, signal: AbortSignal, ): Promise | null { if (!calcOnServerNow(KEY)) return null; const local = () => groundFollowingBasePvi(input); const profile = groundProfile(input.samples); if (!profile) return null; return orLocal( signal, async () => { const follow = { stations: input.stations, start_offset_m: input.startOffsetM ?? 0, end_offset_m: input.endOffsetM ?? 0, }; const inputs = { profile: { ...profile, policy: input.policy, ground_following: follow } }; const results = await serverCalc(KEY, ["profile.base"], inputs, signal); const nodes = results["profile.base"] as AlignmentNode[] | null; return ( nodes?.map((node) => ({ chainage_m: r6(node.chainage_m), elevation_m: r6(node.elevation_m), })) ?? null ); }, local, ); } /** profile 단계 — 첫 계획선 + 계획고 편집 → 종단(서버면 `profile.alignment` · 아니면 동기 그대로). */ export const profileAt: StageFn<"profile"> = (ctx) => { const base = ctx.out.planline; if (!base || !calcOnServerNow(KEY)) return profileStage(ctx); return orLocal( ctx.signal, async () => { const profile = { chainage: base.chainage, ground: base.ground, policy: base.policy, stations: base.stations, base_pvi: base.basePvi, edits: ctx.inputs.alignment ?? emptyEdits(), only_explicit_curves: base.onlyExplicitCurves, }; const results = await serverCalc(KEY, ["profile.alignment"], { profile }, ctx.signal); return results["profile.alignment"] as ProfileAlignment; }, () => profileStage(ctx) as ProfileAlignment, ); };