import type { StatusTone } from "@nodedc/ui-react"; import type { BackendStatus } from "@mission-core/plugin-sdk"; import type { XgridsK1Metrics } from "./api"; const phaseLabels: Record = { idle: "Ожидание", scanning: "Поиск Bluetooth", device_selected: "Устройство выбрано", provisioning: "Передача настроек Wi‑Fi", connecting: "Подключение", connected: "Устройство подключено", starting_live: "Запуск потока", live: "Поток в реальном времени", replay: "Повтор записи", stopping: "Остановка", error: "Ошибка", }; export function phaseLabel(phase: string | null | undefined): string { if (!phase) return "Нет состояния"; return phaseLabels[phase] ?? "Неизвестное состояние"; } export function phaseTone(phase: string | null | undefined): StatusTone { if (!phase) return "neutral"; if (phase === "error") return "danger"; if (["connected", "live", "replay"].includes(phase)) return "success"; if (["scanning", "provisioning", "connecting", "starting_live", "stopping"].includes(phase)) return "accent"; return "neutral"; } export function backendLabel(status: BackendStatus): string { return { unconfigured: "Модель не выбрана", checking: "Проверка контура", online: "Контур доступен", degraded: "Контур ограничен", offline: "Контур недоступен", }[status]; } export function backendTone(status: BackendStatus): StatusTone { if (status === "online") return "success"; if (status === "degraded" || status === "checking") return "warning"; if (status === "unconfigured") return "neutral"; return "danger"; } export function eventStatusLabel(status: string): string { return { connecting: "подключение", open: "подключён", closed: "закрыт", error: "ошибка", }[status] ?? "неизвестно"; } export function finiteMetric(value: number | null | undefined): number | null { return typeof value === "number" && Number.isFinite(value) ? value : null; } export function pipelineLatency(metrics: XgridsK1Metrics | undefined): number | null { if (!metrics) return null; const direct = finiteMetric(metrics.pipeline_ms ?? metrics.end_to_end_ms); if (direct !== null) return direct; const segments = [finiteMetric(metrics.mqtt_to_decode_ms), finiteMetric(metrics.publish_ms)] .filter((value): value is number => value !== null); return segments.length === 2 ? segments.reduce((total, value) => total + value, 0) : null; } function nonNegativeMetric(value: number | null | undefined): number | null { const finite = finiteMetric(value); return finite !== null && finite >= 0 ? finite : null; } export interface K1DeviceTelemetry { elapsedSeconds: number | null; routeDistanceMeters: number | null; speedMetersPerSecond: number | null; } export interface SpatialActionFailure { title: string; detail: string; } export function spatialActionFailure( error: string | null | undefined, ): SpatialActionFailure | null { const detail = error?.trim(); return detail ? { title: "Действие K1 не выполнено", detail, } : null; } export function deviceTelemetry( metrics: XgridsK1Metrics | null | undefined, ): K1DeviceTelemetry { return { elapsedSeconds: nonNegativeMetric( metrics?.device_elapsed_seconds ?? metrics?.elapsed_seconds, ), routeDistanceMeters: nonNegativeMetric( metrics?.device_route_distance_meters ?? metrics?.route_distance_meters, ), speedMetersPerSecond: nonNegativeMetric( metrics?.device_speed_meters_per_second ?? metrics?.device_speed_mps ?? metrics?.speed_meters_per_second, ), }; } export function formatNumber(value: number | null, digits = 1): string { if (value === null) return "—"; return value.toLocaleString("ru-RU", { maximumFractionDigits: digits, minimumFractionDigits: digits, }); } export function sourceModeLabel(mode: string | null | undefined): string { if (mode === "live") return "Реальное время"; if (mode === "replay") return "Повтор записи"; if (mode === "idle") return "Ожидание"; return "Неизвестно"; }