/* ============================================================================= * B06_Section_Api_Fetch.ts * 3차 워크플로우(종·횡단 생성) API 클라이언트 * * 백엔드 계약 (B06_Section_Router.py): * GET /api/projects/{project_id}/sections/context → 확정 경로 + 기본 옵션 * GET /api/projects/{project_id}/sections/{route_id} → 종단 요약 조회 * GET /api/projects/{project_id}/sections/{route_id}/detail → 종횡단 원시 샘플 조회 * POST /api/projects/{project_id}/sections/{route_id}/regenerate → 횡단 반폭 재생성 * POST /api/projects/{project_id}/sections/{route_id}/confirm → 종횡단 확정 * * 규칙: * - 모든 제어 상수는 config_frontend에서 참조 (하드코딩 금지). * - 오류 응답 형식 {status:"error", message:"..."}을 Error로 변환. * ========================================================================== */ import { API_BASE_URL, API_TIMEOUT_MS } from "@config/config_frontend"; import type { BalloonOffsets, EarthworkConversion, GroundType, HaulEquipmentLimit, } from "@util/common_util_mass_haul_types"; export interface SectionOptionDefaults { station_interval_m: number; cross_half_width_m: number; cross_sample_interval_m: number; long_sample_interval_m: number; vertical_exaggeration: number; } /** 측구 규격(상단폭/저폭/깊이, m). */ export interface DitchSpec { top_width_m: number; bottom_width_m: number; depth_m: number; } /** 지반그룹 하나의 표준 횡단면 기본값 (config STANDARD_CROSS_SECTION 사본). */ export interface StandardCrossGroup { road_width_m: number; shoulder_left_m: number; shoulder_right_m: number; ditch: DitchSpec; /** 암 그룹만 존재: L형 측구(폭/깊이, m). */ ditch_l_type?: { width_m: number; depth_m: number }; cross_slope_pct: { min: number; max: number }; fill_slope_ratio: number; cut_slope_ratio: number; /** 포장 그룹만 존재: 포장층 두께(m). */ pavement_thickness_m?: number; } /** 표준 횡단면 설정 패널 그룹 키. */ export type StandardCrossKey = "soil" | "rock" | "paved"; export type StandardCrossSection = Record; // 지반유형·토량환산계수·운반장비 한계거리는 B05 계획 유토곡선과 공유하므로 정의처를 // `@util/common_util_mass_haul_types` 한 곳에 두고 여기서는 재수출만 한다(사본 금지). export type { GroundType, EarthworkConversionFactor, EarthworkConversion, HaulEquipmentLimit, BalloonOffsets, } from "@util/common_util_mass_haul_types"; /** 물넘이포장 제원 — 정의처는 렌더 모듈이다(사본 금지, 타입 전용 import라 순환 없음). */ import type { FordPavementSpec } from "./B06_Section_UI_Cross_Ford_Pavement"; import type { RevetmentSpec } from "./B06_Section_UI_Cross_Revetment"; export type { FordPavementSpec, RevetmentSpec }; export interface SectionContextResponse { project_id: string; route_id: number | null; filter_key: string | null; method: string | null; smooth: boolean | null; crs_epsg: number | null; /** 프로젝트 등록(B02)에서 정한 임도 종류 — B05 계획선 법정 기준의 출발점(2026-08-19). */ road_type: string | null; defaults: SectionOptionDefaults; /** 표준 횡단면 설정 패널(토사/암/포장) 기본값. */ standard_cross_section: StandardCrossSection; /** 암 경계선 기본 오프셋(m)과 상/하 제어 스텝(m). */ rock_boundary_default_offset_m: number; rock_boundary_step_m: number; /** 지반유형별 토량환산계수. 유토곡선은 프론트가 이 값으로 계산한다. */ earthwork_conversion: EarthworkConversion; /** 평균운반거리별 운반장비 경계. 유토곡선의 토량 분배가 이 값으로 장비를 고른다. */ haul_equipment_limits?: HaulEquipmentLimit[]; /** * 자연방토 판정 경사(rise/run). 성토측 자연 지반이 이보다 가파르면 흙이 스스로 흘러내려 * 운반비를 세지 않는다. 못 받으면 프론트는 **자연방토 없음**으로 본다(보수적). */ natural_spoil_min_ground_slope?: number | null; } /** 종단 요약 조회 결과 (SectionSummaryResponse) */ export interface SectionSummaryResponse { status: string; project_id: string; route_id: number; longitudinal: Record | null; length_m: number | null; cross_section_count: number; } export interface SectionSample { chainage_m?: number; offset_m?: number; elevation_m?: number | null; z?: number | null; valid: boolean; } export interface SectionStation { station_id: string; chainage_m: number; label: string; /** irregular = 사용자가 구조물용으로 추가한 비정규 측점(프론트 주입, 백엔드 미영속). */ kind: "bp" | "ep" | "regular" | "irregular"; /** 비정규 측점의 구조물 설명(백엔드가 확정 시 부여). 복귀 시 사이드바 목록 복원에 쓴다. */ structure?: string; center_z: number | null; azimuth_deg: number | null; center_x: number; center_y: number; /** 횡단 기준 등고가 높은 쪽(측구 설계 기본 방향). B05 solve 자동 판정 + 사용자 변경. */ uphill_side?: "left" | "right" | null; frame: { left_xy: [number, number] }; } /** 계획선 샘플 (계획고와 지반고, 그 차이). */ export interface DesignProfileSample { chainage_m: number; elevation_m: number; ground_elevation_m: number; difference_m: number; } /** 절·성토 균형을 판정하는 구역 단위 결과. */ export interface DesignProfileSegment { index: number; start_chainage_m: number; end_chainage_m: number; cut_area_m2: number; fill_area_m2: number; balance_error_m2: number; } export interface DesignProfileSummary { cut_area_m2: number; fill_area_m2: number; balance_error_m2: number; max_grade_pct: number; vertical_curve_count: number; pvi_count: number; balance_segment_count: number; balanced: boolean; main_direction: string; suggested_elevation_offset_m: number | null; warnings: string[]; } /** * 종단 계획선. 횡단 설계 기반 계획선이 추가될 수 있어 배열로 전달된다. */ export interface DesignProfile { id: string; name: string; basis: string; samples: DesignProfileSample[]; balance_segments: DesignProfileSegment[]; summary: DesignProfileSummary; } export interface LongitudinalSection { length_m: number; samples: SectionSample[]; stations: SectionStation[]; design_profiles?: DesignProfile[]; /** * 계획선 변화점(PVI) 구조. B05가 편집 기준선으로 사용하며 `design_profiles`는 * 여기서 파생된다. 구 데이터에는 없을 수 있어 optional이다. * 구조는 `B05_Profile_UI_Profile_Alignment.ProfileAlignment`. */ profile_alignment?: unknown; } /** * 배수관 세트의 유입/유출 한쪽 부속 제원 (백엔드 `B06_Section_Engine_Culvert` 산출). * 구조가 "집수정"이면 기슭막이·보호공 필드가 없다 — 라벨만 쓴다(집수정 단면은 후속). */ export interface CulvertSideSpec { role: "inlet" | "outlet"; structure: string; revet_form?: string | null; revet_height_m?: number | null; revet_length_m?: number | null; /** 기준측점 전/후 종방향 몫(m) — 3D 예상형상 배치(2026-08-23). 없으면 길이 절반씩. */ revet_before_m?: number | null; revet_after_m?: number | null; /** 집수정 종방향 길이(m) — structure="집수정"일 때만. 기본 2m. */ basin_length_m?: number | null; /** 집수정 기준측점 전/후 몫(m) — 기슭막이와 같은 체계(2026-08-24). 기본 각 1m. */ basin_before_m?: number | null; basin_after_m?: number | null; /** 기슭막이 전면 기울기(1:n). 돌쌓기 전면 1:0.3(교본 7-3). */ face_slope?: number; /** 보호공(물받이) 길이 = 낙차고 × 2 (사방교본 교차 참조, 2026-08-19 사용자 확정). */ apron_length_m?: number; /** 보호공 두께 1.0m 내외 (사방교본 교차 참조). */ apron_thickness_m?: number; } /** 배수관 측점의 세트(배관·기슭막이·보호공) 제원 — 횡단 카드 오버레이 입력. */ export interface CulvertSet { type: "pipe"; pipe_kind: string | null; diameter_m: number; /** 관 위 최소 토피(m) — 별표2 교량·암거 복토 50㎝ 교차 참조. B05 하향 차단 기준. */ min_cover_m: number; inlet: CulvertSideSpec; outlet: CulvertSideSpec; /** 독립 기슭막이(관 없는 벽) — true면 관을 그리지 않고 수량에서도 뺀다(2026-08-28). */ hidden_pipe?: boolean; /** 독립 기슭막이 설치 측 — "양쪽" | "좌" | "우". 좌/우면 반대쪽 벽을 숨긴다. */ side?: string; /** 독립 기슭막이 다단 요청 수(1이면 단일 벽). */ tiers?: number; } /** 세월교 날개벽 한쪽 — 횡단면엔 안 보이고 바닥판 연장량만 넘긴다. */ export interface FordWingSpec { installed: boolean; height_m: number | null; length_m: number | null; angle_deg: number | null; /** 바닥판 편측 연장(m) = 길이 × cos(각도). 각도는 관축 기준 벌어짐각. */ slab_extend_m: number; } /** 세월교 측점의 세트 제원 — 양측 ㄴ형 측벽 + 바닥판 + 관, 관 위는 성토 채움. */ export interface FordSet { type: "ford"; pipe_kind: string | null; diameter_m: number; /** 관 련수. 단면엔 1개만 그리고 라벨에만 쓴다. */ pipe_count: number; /** 구체의 도로 진행 방향 길이(m) = 월류 폭. 기준 측점 전후로 절반씩 걸친다. */ span_m: number; slab_thickness_m: number; wall_thickness_m: number; min_cover_m: number; wing_in: FordWingSpec; wing_out: FordWingSpec; } /** BOX암거 측점의 세트 제원 — 상판·내공(유로)·저판 + 날개벽 투영 연장. */ export interface BoxSet { type: "box"; /** 사용자 입력 내공(유로) 폭·높이(m). */ inner_width_m: number; inner_height_m: number; /** 부재 두께(m) — 세월교 승계(2026-08-25 사용자 확정). */ wall_thickness_m: number; slab_thickness_m: number; top_thickness_m: number; /** 암거 위 복토(m) — 별표2 "복토 흙 두께 50㎝ 이상" 교차 참조. */ cover_m: number; /** 도로 진행 방향 길이(m) = 내공 폭 + 측벽 2장. 기준 측점 전후로 절반씩 걸친다. */ span_m: number; wing_in: FordWingSpec; wing_out: FordWingSpec; } export interface CrossSection extends SectionStation { samples: SectionSample[]; /** DB에 저장된 잠정 설계 지정(있을 때만). 상세 조회 시 얹혀 온다. */ design?: CrossDesign; /** 배수관 측점의 세트 제원(있을 때만). `pipe_points.json` 정본 + 레지스트리 기본값. */ culvert?: CulvertSet; /** 세월교 구체가 걸치는 측점의 세트 제원(있을 때만). 구체 폭 안이면 여러 측점에 붙는다. */ ford?: FordSet; /** BOX암거 구체가 걸치는 측점의 세트 제원(있을 때만). */ box?: BoxSet; /** 물넘이포장이 파는 노면 제원(있을 때만). 월류 폭 안의 측점 전부에 붙는다. */ ford_pavement?: FordPavementSpec; /** 독립 기슭막이 제원(있을 때만). 구조물 정본 D군 구간 안의 측점 전부에 붙는다. */ revetment?: RevetmentSpec; } export interface SectionDetailResponse { longitudinal: LongitudinalSection; cross_sections: CrossSection[]; /** 확정 시 저장해 둔 유토곡선 balloon 위치. 브라우저가 바뀌어도 같은 자리에 뜬다. */ balloon_offsets?: BalloonOffsets | null; } /** 종횡단 확정 결과 (SectionConfirmResponse) */ export interface SectionConfirmResponse { status: string; project_id: string; route_id: number; confirmed: boolean; } /** 측점 표준횡단 설계 지정값 (버튼 상태). */ export type SectionMode = "left_cut" | "right_cut" | "both_cut" | "both_fill"; export type DitchSide = "left" | "right"; export type DitchType = "standard" | "l_type"; /** 기슭막이 한 벽의 4축 조작값(좌우 x·상하 d·높이 h·형태 m). null = 자동. */ export interface StoredWallAdjust { x: number; d: number | null; h: number | null; /** 형태 — B05 폼과 같은 목록(`REVET_FORMS`). 2026-08-29 이전 저장분은 재질 * 코드("dry"/"wet"/"concrete")라 `revetFormOf`가 형태로 옮겨 읽는다. */ m: string | null; } /** 다단 기슭막이 한 단의 종방향 구간값(길이·기준측점 전/후 m — 2026-08-29). */ export interface StoredWallSpan { length_m: number; before_m: number; after_m: number; } /** 다단 기슭막이 단 수(유출 성토부 / 집수정 계류측). */ export interface StoredExtraWallCounts { outlet: number; basin: number; } /** 세월교 측벽 한 매의 저장형 조작값(2026-08-25). */ export interface StoredFordWallAdjust { heightM: number | null; lateralM: number; slopeM: number; } /** 세월교 측점의 저장형 조작값 — 유입·유출 측벽을 따로 담는다. */ export interface StoredFordAdjust { inlet: StoredFordWallAdjust; outlet: StoredFordWallAdjust; } /** BOX암거 한쪽 끝의 저장형 조작값(2026-08-25). */ export interface StoredBoxSideAdjust { lengthM: number; riseM: number; } /** BOX암거 측점의 저장형 조작값 — 좌·우 끝을 따로 담는다. */ export interface StoredBoxAdjust { left: StoredBoxSideAdjust; right: StoredBoxSideAdjust; } /** 측점 표준횡단 설계 계산 결과(잠정치). data.design에 저장되는 구조와 동일. */ export interface CrossDesign { inlet_structure?: "auto" | "revet" | "I" | "L" | "U"; basin_adjust?: { innerWidthM: number; innerHeightM: number; lateralM: number; slopeM: number; }; /** 세월교 측벽 조작값(유입·유출) — 높이·좌우·상하(2026-08-25). */ ford_adjust?: StoredFordAdjust; /** BOX암거 구체 조작값(좌·우 끝) — 길이·표고(2026-08-25). */ box_adjust?: StoredBoxAdjust; /** 기슭막이 4축 조작값 — 키는 역할("inlet"/"outlet"/"extra0"…/"bextra0"…). * 세션 전용이던 값을 정본에 남긴다(2026-08-24: 3D는 확정 결과물). */ revet_adjust?: Record; /** 다단 기슭막이 단 수 — 유출 성토부·집수정 계류측. */ extra_wall_counts?: StoredExtraWallCounts; /** 다단 기슭막이 **단별** 구간값 — 키는 벽 키("extra0"…/"bextra0"…). 기준벽 연장에 * 종속되지 않고 단마다 따로 잡는다(2026-08-29 사용자). 없으면 기본 10m(5/5). */ extra_spans?: Record; /** 연동 해제(측점별 — 2026-08-24 사용자). 옆 측점에서 연장돼 온 기슭막이의 위치 * 4축을 이 측점에서 따로 잡는다. 구조물 추가가 아니라 3D 위치의 개별 지정이다. */ revet_link_detached?: boolean; /** 종단경사 반영(소유 측점 1개 = 기슭막이 한 벌 전체 공통, 기본 켬). * 끄면 연장 구간의 표고를 소유 측점과 같게 본다. */ revet_follow_grade?: boolean; ground_type: GroundType; geometry_preset: "soil" | "rock"; section_mode: SectionMode; ditch_side: DitchSide; /** 측구 형식(일반/L형). 양성(측구 없음)은 null. */ ditch_type: DitchType | null; cut_slope_ratio: number; /** 2단계 절토의 토사(상단) 경사비. 암 지반에서만 의미. */ soil_cut_slope_ratio?: number; /** 암반 경계 기준 2단계 경사 적용 여부(엔진이 실제 적용했는지). */ two_stage_slope?: boolean; fill_slope_ratio: number; roadbed_width_m: number; carriageway_width_m: number; cross_slope_pct: number; ditch: | { type: "standard"; top_width_m: number; bottom_width_m: number; depth_m: number } | { type: "l_type"; width_m: number; depth_m: number } | { type: "none" }; /** 측구 생성 여부(엔진이 자동/override 반영해 실제 적용한 결과). */ ditch_enabled?: boolean; /** 포장 중첩 여부와 포장층 두께(포장 시). */ paved: boolean; pavement_thickness_m?: number; /** B05 법정 경사 분석의 포장 제안 여부(사용자 토글과 무관하게 유지). */ pavement_suggested?: boolean; /** 노면(노견 포함) 양 끝점 — 노면 렌더링 기준. */ road_edges: { left: { offset_m: number; elevation_m: number }; right: { offset_m: number; elevation_m: number }; }; /** 차도(노견 제외) 양 끝점 — 포장 범위 기준. */ carriageway_edges?: { left: { offset_m: number; elevation_m: number }; right: { offset_m: number; elevation_m: number }; }; design_elevation_m: number; cut_area_m2: number; /** * 절토 내역(합 = `cut_area_m2`). 지표면~암반 경계선이 토사, 그 아래가 암이다. * 경계선을 올리내리면 두 값이 함께 바뀌고 유토곡선도 따라 움직인다. * 구 데이터에는 없으므로 optional — 없으면 `cut_area_m2` 전량을 지반유형으로 본다. */ cut_soil_area_m2?: number; cut_rock_area_m2?: number; /** 암반부에 적용할 지반유형(`ripping_rock`/`blasting_rock`). 토사 측점은 null. */ cut_rock_kind?: GroundType | null; fill_area_m2: number; /** 성토측 자연 지반 경사(rise/run). 자연방토 판정 입력. 성토측이 없으면 null. */ fill_ground_slope?: number | null; ditch_area_m2: number; design_line: Array<{ offset_m: number; elevation_m: number }>; /** 확정 시 병합되는 암 경계선 오프셋(m). 세션 값이 우선이며 복원 폴백으로 쓴다. */ rock_boundary_offset_m?: number; /** 측점 개별 표시 반폭(m, 2026-08-06). 확정 시 병합되며 세션 값이 우선이다. */ display_half_width_m?: number; } export interface CrossDesignResponse { status: string; chainage_m: number; design: CrossDesign; } export interface CrossDesignRequest { chainage_m: number; ground_type: GroundType; section_mode: SectionMode; ditch_side?: DitchSide | null; /** 측구 형식(일반/L형). L형은 암 지반에서만 허용된다. */ ditch_type?: DitchType; /** 포장 중첩 여부 — 횡단경사·포장층만 포장 그룹 값으로 계산된다. */ paved?: boolean; /** 암 경계선 오프셋(m, 지면선 기준 하향 음수). 암 지반 2단계 절토 무릎 계산용. */ rock_boundary_offset_m?: number | null; /** 암 지반 2단계 경사 적용 여부(기본 true, 토글로 해제). */ two_stage_slope?: boolean; /** 측구 생성 여부. null/미지정=자동 판정, true/false=수동 override. */ ditch_enabled?: boolean | null; /** 설정 패널 편집값. 요청값 → config 기본값 순으로 우선한다. */ standard_cross_section?: StandardCrossSection; } /** 확정 시 측점별 data.design에 병합할 프론트 세션 보관값. */ export interface CrossSectionPatch { chainage_m: number; rock_boundary_offset_m?: number; /** 측점 개별 표시 반폭(m, 2026-08-06 사용자 지시). */ display_half_width_m?: number; inlet_structure?: "auto" | "revet" | "I" | "L" | "U"; basin_adjust?: { innerWidthM: number; innerHeightM: number; lateralM: number; slopeM: number; }; revet_adjust?: Record; ford_adjust?: StoredFordAdjust; box_adjust?: StoredBoxAdjust; extra_wall_counts?: StoredExtraWallCounts; extra_spans?: Record; /** 연동 해제(측점별)·종단경사 반영(전체 공통) — 2026-08-24 사용자. */ revet_link_detached?: boolean; revet_follow_grade?: boolean; } /** 공통 fetch 헬퍼: 타임아웃 + 인증 헤더 + 오류 응답 변환. */ async function requestJson(path: string, init: RequestInit): Promise { const controller = new AbortController(); const timeoutId = window.setTimeout(() => controller.abort(), API_TIMEOUT_MS); try { const response = await fetch(`${API_BASE_URL}${path}`, { ...init, credentials: "include", headers: { "Content-Type": "application/json", ...(init.headers ?? {}), }, signal: controller.signal, }); const payload = (await response.json()) as T & { message?: string }; if (!response.ok) { throw new Error(payload.message ?? `HTTP ${response.status}`); } return payload; } finally { window.clearTimeout(timeoutId); } } /** 최신 확정 경로와 B04 확정값, config 기반 생성 기본값을 조회한다. */ export async function fetchSectionContext(projectId: string): Promise { return requestJson(`/projects/${projectId}/sections/context`, { method: "GET", }); } /** 경로의 종단면 요약을 조회한다. */ export async function getSections( projectId: string, routeId: number, ): Promise { return requestJson(`/projects/${projectId}/sections/${routeId}`, { method: "GET", }); } /** 경로의 SVG 렌더링용 종단·횡단 원시 샘플을 조회한다. */ export async function fetchSectionDetail( projectId: string, routeId: number, ): Promise { return requestJson(`/projects/${projectId}/sections/${routeId}/detail`, { method: "GET", }); } /** 횡단 반폭을 반영해 종횡단을 재생성·저장하고 갱신된 상세를 반환한다. */ export async function regenerateSections( projectId: string, routeId: number, crossHalfWidthM: number, ): Promise { return requestJson( `/projects/${projectId}/sections/${routeId}/regenerate`, { method: "POST", body: JSON.stringify({ cross_half_width_m: crossHalfWidthM }) }, ); } /** 측점 표준횡단 설계(지반유형·단면유형)를 즉시 계산·저장하고 잠정 결과를 반환한다. */ export async function computeCrossDesign( projectId: string, routeId: number, request: CrossDesignRequest, ): Promise { return requestJson( `/projects/${projectId}/sections/${routeId}/cross-design`, { method: "POST", body: JSON.stringify(request) }, ); } /** 경로의 종·횡단면을 확정한다. 표준 횡단면 설정값·측점별 세션 보관값을 함께 저장할 수 있다. */ export async function confirmSections( projectId: string, routeId: number, standardCrossSection?: StandardCrossSection, crossPatches?: CrossSectionPatch[], /** 프론트에서 계산한 유토곡선 결과. 확정 시점에만 영구 저장한다. */ massHaul?: Record, ): Promise { const body: Record = {}; if (standardCrossSection) body.standard_cross_section = standardCrossSection; if (crossPatches?.length) body.cross_patches = crossPatches; if (massHaul) body.mass_haul = massHaul; return requestJson(`/projects/${projectId}/sections/${routeId}/confirm`, { method: "POST", body: JSON.stringify(body), }); } /** * 편집 중인 종·횡단을 **확정하지 않고** 영구저장소에만 남긴다(임시 저장). * 저장 내용은 확정과 같지만 경로 상태·워크플로 단계를 건드리지 않아 페이지 이동도 없다. */ export async function saveSections( projectId: string, routeId: number, standardCrossSection?: StandardCrossSection, crossPatches?: CrossSectionPatch[], massHaul?: Record, ): Promise { const body: Record = {}; if (standardCrossSection) body.standard_cross_section = standardCrossSection; if (crossPatches?.length) body.cross_patches = crossPatches; if (massHaul) body.mass_haul = massHaul; return requestJson(`/projects/${projectId}/sections/${routeId}/save`, { method: "POST", body: JSON.stringify(body), }); } /** 같은 회사에서 표준횡단 설계값을 불러올 수 있는 프로젝트 항목. */ export interface CompanyStandardProject { project_id: string; name: string; } export interface CompanyStandardListResponse { status: string; projects: CompanyStandardProject[]; } export interface CompanyStandardResponse { status: string; project_id: string; standard_cross_section: StandardCrossSection; } /** 같은 회사에서 설계값을 불러올 수 있는 프로젝트 목록을 조회한다(회사 스코프). */ export async function listCompanyStandards( projectId: string, ): Promise { return requestJson( `/projects/${projectId}/sections/company-standards`, { method: "GET" }, ); } /** 특정 프로젝트의 표준횡단 설계값을 미리보기용으로 조회한다(적용 전, 현재 값 불변). */ export async function getCompanyStandard( projectId: string, sourceProjectId: string, ): Promise { return requestJson( `/projects/${projectId}/sections/company-standards/${sourceProjectId}`, { method: "GET" }, ); } /** 계획선 편집 델타(자동 선형 대비 계획고 델타 + 종단곡선 반경). B05 편집 스토어와 같은 모양. */ export interface ProfileAlignmentEdits { station_offsets: Record; curve_radii: Record; } /** * 계획선 편집 프리뷰 결과 — **바뀌는 것만** 온다. * 횡단 지반선 원시 샘플은 계획고와 무관해 그대로이므로 싣지 않는다(응답 1MB → 수십 KB). */ export interface CrossDesignPreviewResponse { status: string; /** 유토곡선이 읽는 설계 필드만 담긴 부분 갱신값(설계선 좌표 등은 오지 않는다). */ designs: Array<{ chainage_m: number; design: Partial }>; } /** * 계획선 편집을 반영해 **계획선 + 전 측점 횡단 설계**를 다시 계산해 받는다(서버 저장 없음). * * B05에서 계획고를 끄는 동안 횡단 단면적이 함께 움직여야 횡단 기준 유토곡선이 따라온다. * 측점마다 따로 부르면 수십 번 왕복하므로 한 번에 계산한다. 영속화는 각 페이지의 * 임시저장·확정이 맡는다. */ export async function previewCrossDesigns( projectId: string, routeId: number, edits: ProfileAlignmentEdits, standardCrossSection?: StandardCrossSection, options?: { /** true면 설계 전체(설계선 좌표 포함)를 받는다 — B06 진입 시 stale 일괄 재계산용. */ fullDesigns?: boolean; /** 측점별 암 경계 오프셋 세션값(chainage 키 → m). DB 저장분보다 우선한다. */ rockBoundaryOffsets?: Record; }, ): Promise { return requestJson( `/projects/${projectId}/sections/${routeId}/cross-design/preview`, { method: "POST", body: JSON.stringify({ ...edits, standard_cross_section: standardCrossSection ?? null, full_designs: options?.fullDesigns ?? false, rock_boundary_offsets: options?.rockBoundaryOffsets ?? null, }), }, ); }