/* ============================================================================= * 종단면 패널 자료 어댑터 (B05) * * 저장된 종단면 자료(`longitudinal_sections.data`)를 화면이 쓰는 모양으로 바꾸는 순수 함수들. * 상태를 갖지 않으며 DOM도 만들지 않는다 — 패널 본체가 700줄 한계에 닿아 분리했다. * ========================================================================== */ import type { DesignProfile, LongitudinalSection } from "../B06_Section/B06_Section_Api_Fetch"; import type { ProfileAlignment } from "./B05_Profile_UI_Profile_Alignment"; export function readAlignment(data: LongitudinalSection): ProfileAlignment | null { const candidate = data.profile_alignment as ProfileAlignment | undefined; if (!candidate?.base_pvi?.length || !candidate.samples?.length) return null; if (!Number.isFinite(candidate.policy?.default_curve_radius_m)) return null; if (!candidate.stations || !candidate.segments || !candidate.curves) return null; candidate.edits = { station_offsets: candidate.edits?.station_offsets ?? {}, curve_radii: candidate.edits?.curve_radii ?? {}, }; return candidate; } /** 저장분이 구버전이라 편집을 붙일 수 없는 상태인가 (재계산 안내용). */ export function hasLegacyAlignment(data: LongitudinalSection): boolean { return Boolean(data.profile_alignment) && readAlignment(data) === null; } /** 편집 결과를 종단면도 렌더러가 받는 계획선 형태로 감싼다. */ export function toDesignProfile( alignment: ProfileAlignment, original: DesignProfile | undefined, ): DesignProfile { const balance = alignment.balance; return { id: original?.id ?? "design_grade_line", name: original?.name ?? "계획선", basis: original?.basis ?? "station_alignment", samples: alignment.samples, balance_segments: [ { index: 0, start_chainage_m: alignment.samples[0]?.chainage_m ?? 0, end_chainage_m: alignment.samples[alignment.samples.length - 1]?.chainage_m ?? 0, cut_area_m2: balance.cut_area_m2, fill_area_m2: balance.fill_area_m2, balance_error_m2: balance.net_area_m2, }, ], summary: { ...(original?.summary ?? { max_grade_pct: 0, vertical_curve_count: 0, pvi_count: 0, balance_segment_count: 1, main_direction: "none", suggested_elevation_offset_m: null, warnings: [], }), cut_area_m2: balance.cut_area_m2, fill_area_m2: balance.fill_area_m2, balance_error_m2: balance.net_area_m2, balanced: balance.within_tolerance, }, }; } export function normalizedLongitudinal(data: LongitudinalSection): LongitudinalSection { return { ...data, samples: data.samples.map((sample: LongitudinalSection["samples"][number]) => ({ ...sample, elevation_m: sample.elevation_m ?? sample.z ?? null, })), }; }