import { SensorData, Device } from '../services/api'; import { isSensorData, isDevice, toSafeNumber, toSafeString, safeGet } from './typeGuards'; // 차트 데이터 인터페이스 export interface ChartDataPoint { device_id: string; temperature: number; humidity: number; pm10: number; pm25: number; pressure: number; illumination: number; tvoc: number; co2: number; o2: number; co: number; timestamp?: string; } // 센서 데이터를 차트 데이터로 변환 export const transformSensorDataToChartData = (sensorData: SensorData[]): ChartDataPoint[] => { if (!Array.isArray(sensorData) || sensorData.length === 0) { return createDefaultChartData(); } try { const validData = sensorData.filter(data => { if (!isSensorData(data)) { console.warn('Invalid sensor data found:', data); return false; } return true; }); if (validData.length === 0) { console.warn('No valid sensor data found, returning default data'); return createDefaultChartData(); } return validData.map(data => ({ device_id: toSafeString(data.device_id, 'Unknown'), temperature: toSafeNumber(data.temperature, 0), humidity: toSafeNumber(data.humidity, 0), pm10: toSafeNumber(data.pm10, 0), pm25: toSafeNumber(data.pm25, 0), pressure: toSafeNumber(data.pressure, 0), illumination: toSafeNumber(data.illumination, 0), tvoc: toSafeNumber(data.tvoc, 0), co2: toSafeNumber(data.co2, 0), o2: toSafeNumber(data.o2, 0), co: toSafeNumber(data.co, 0), timestamp: toSafeString(data.recorded_time, '') })); } catch (error) { console.error('Error transforming sensor data to chart data:', error); return createDefaultChartData(); } }; // 기본 차트 데이터 생성 export const createDefaultChartData = (): ChartDataPoint[] => { return [{ device_id: 'No Data', temperature: 0, humidity: 0, pm10: 0, pm25: 0, pressure: 0, illumination: 0, tvoc: 0, co2: 0, o2: 0, co: 0, timestamp: new Date().toISOString() }]; }; // 센서 카드 데이터 인터페이스 export interface SensorCardData { title: string; value: number; unit: string; icon: string; bgColor: string; precision: number; } // 센서 데이터를 카드 데이터로 변환 export const transformSensorDataToCardData = (sensorData: SensorData | null): SensorCardData[] => { if (!sensorData || !isSensorData(sensorData)) { return createDefaultCardData(); } try { return [ { title: '현재 온도', value: toSafeNumber(sensorData.temperature, 0), unit: '°C', icon: '🌡️', bgColor: 'bg-green-100', precision: 1 }, { title: '현재 습도', value: toSafeNumber(sensorData.humidity, 0), unit: '%', icon: '💧', bgColor: 'bg-yellow-100', precision: 1 }, { title: 'PM10', value: toSafeNumber(sensorData.pm10, 0), unit: 'μg/m³', icon: '🌫️', bgColor: 'bg-orange-100', precision: 1 }, { title: 'PM2.5', value: toSafeNumber(sensorData.pm25, 0), unit: 'μg/m³', icon: '💨', bgColor: 'bg-red-100', precision: 1 }, { title: '기압', value: toSafeNumber(sensorData.pressure, 0), unit: 'hPa', icon: '🌪️', bgColor: 'bg-blue-100', precision: 1 }, { title: '조도', value: toSafeNumber(sensorData.illumination, 0), unit: 'lux', icon: '☀️', bgColor: 'bg-yellow-100', precision: 0 }, { title: 'TVOC', value: toSafeNumber(sensorData.tvoc, 0), unit: 'ppb', icon: '🧪', bgColor: 'bg-purple-100', precision: 1 }, { title: 'CO2', value: toSafeNumber(sensorData.co2, 0), unit: 'ppm', icon: '🌿', bgColor: 'bg-green-100', precision: 0 }, { title: 'O2', value: toSafeNumber(sensorData.o2, 0), unit: '%', icon: '💨', bgColor: 'bg-cyan-100', precision: 1 }, { title: 'CO', value: toSafeNumber(sensorData.co, 0), unit: 'ppm', icon: '🔥', bgColor: 'bg-red-100', precision: 2 } ]; } catch (error) { console.error('Error transforming sensor data to card data:', error); return createDefaultCardData(); } }; // 기본 카드 데이터 생성 export const createDefaultCardData = (): SensorCardData[] => { return [ { title: '현재 온도', value: 0, unit: '°C', icon: '🌡️', bgColor: 'bg-gray-100', precision: 1 }, { title: '현재 습도', value: 0, unit: '%', icon: '💧', bgColor: 'bg-gray-100', precision: 1 }, { title: 'PM10', value: 0, unit: 'μg/m³', icon: '🌫️', bgColor: 'bg-gray-100', precision: 1 }, { title: 'PM2.5', value: 0, unit: 'μg/m³', icon: '💨', bgColor: 'bg-gray-100', precision: 1 }, { title: '기압', value: 0, unit: 'hPa', icon: '🌪️', bgColor: 'bg-gray-100', precision: 1 }, { title: '조도', value: 0, unit: 'lux', icon: '☀️', bgColor: 'bg-gray-100', precision: 0 }, { title: 'TVOC', value: 0, unit: 'ppb', icon: '🧪', bgColor: 'bg-gray-100', precision: 1 }, { title: 'CO2', value: 0, unit: 'ppm', icon: '🌿', bgColor: 'bg-gray-100', precision: 0 }, { title: 'O2', value: 0, unit: '%', icon: '💨', bgColor: 'bg-gray-100', precision: 1 }, { title: 'CO', value: 0, unit: 'ppm', icon: '🔥', bgColor: 'bg-gray-100', precision: 2 } ]; }; // 데이터 정규화 함수 export const normalizeSensorData = (data: any): SensorData | null => { if (!data || typeof data !== 'object') { return null; } try { // 필수 필드 검증 const requiredFields = ['id', 'device_id', 'node_id', 'temperature', 'humidity']; const hasRequiredFields = requiredFields.every(field => data.hasOwnProperty(field) && data[field] !== null && data[field] !== undefined ); if (!hasRequiredFields) { console.warn('Missing required fields in sensor data:', data); return null; } // 데이터 정규화 const normalized: SensorData = { id: toSafeNumber(data.id, 0), device_id: toSafeString(data.device_id, ''), node_id: toSafeNumber(data.node_id, 0), temperature: toSafeNumber(data.temperature, 0), humidity: toSafeNumber(data.humidity, 0), longitude: toSafeNumber(data.longitude, 0), latitude: toSafeNumber(data.latitude, 0), recorded_time: toSafeString(data.recorded_time, new Date().toISOString()), received_time: toSafeString(data.received_time, new Date().toISOString()), // 선택적 필드들 float_value: safeGet(data, 'float_value', undefined), signed_int32_value: safeGet(data, 'signed_int32_value', undefined), unsigned_int32_value: safeGet(data, 'unsigned_int32_value', undefined), raw_tem: safeGet(data, 'raw_tem', undefined), raw_hum: safeGet(data, 'raw_hum', undefined), pm10: safeGet(data, 'pm10', undefined), pm25: safeGet(data, 'pm25', undefined), pressure: safeGet(data, 'pressure', undefined), illumination: safeGet(data, 'illumination', undefined), tvoc: safeGet(data, 'tvoc', undefined), co2: safeGet(data, 'co2', undefined), o2: safeGet(data, 'o2', undefined), co: safeGet(data, 'co', undefined) }; return normalized; } catch (error) { console.error('Error normalizing sensor data:', error); return null; } }; // 데이터 유효성 검증 함수 export const validateChartData = (data: ChartDataPoint[]): boolean => { if (!Array.isArray(data) || data.length === 0) { return false; } return data.every(item => { if (!item || typeof item !== 'object') { return false; } // 필수 필드 검증 if (!item.device_id || typeof item.device_id !== 'string') { return false; } // 숫자 필드 검증 const numberFields = ['temperature', 'humidity', 'pm10', 'pm25', 'pressure', 'illumination', 'tvoc', 'co2', 'o2', 'co']; return numberFields.every(field => { const value = item[field as keyof ChartDataPoint]; return typeof value === 'number' && !isNaN(value) && isFinite(value); }); }); }; // 데이터 통계 계산 함수 export const calculateDataStats = (data: ChartDataPoint[]): { avg: ChartDataPoint; min: ChartDataPoint; max: ChartDataPoint; } => { if (!Array.isArray(data) || data.length === 0) { const defaultPoint = createDefaultChartData()[0]; return { avg: defaultPoint, min: defaultPoint, max: defaultPoint }; } const numericFields = ['temperature', 'humidity', 'pm10', 'pm25', 'pressure', 'illumination', 'tvoc', 'co2', 'o2', 'co'] as const; const stats = { avg: { ...data[0] }, min: { ...data[0] }, max: { ...data[0] } }; numericFields.forEach(field => { const values = data.map(item => item[field]).filter(val => typeof val === 'number' && !isNaN(val)); if (values.length > 0) { stats.avg[field] = values.reduce((sum, val) => sum + val, 0) / values.length; stats.min[field] = Math.min(...values); stats.max[field] = Math.max(...values); } }); return stats; };