/** * Air Quality Analysis System * Real-time air quality monitoring and assessment */ export class AirQualityAnalyzer { constructor() { this.aqiBreakpoints = { pm25: [ { low: 0, high: 12, aqi_low: 0, aqi_high: 50, category: 'Good', color: '#00e400' }, { low: 12.1, high: 35.4, aqi_low: 51, aqi_high: 100, category: 'Moderate', color: '#ffff00' }, { low: 35.5, high: 55.4, aqi_low: 101, aqi_high: 150, category: 'Unhealthy for Sensitive Groups', color: '#ff7e00' }, { low: 55.5, high: 150.4, aqi_low: 151, aqi_high: 200, category: 'Unhealthy', color: '#ff0000' }, { low: 150.5, high: 250.4, aqi_low: 201, aqi_high: 300, category: 'Very Unhealthy', color: '#8f3f97' }, { low: 250.5, high: 500, aqi_low: 301, aqi_high: 500, category: 'Hazardous', color: '#7e0023' } ], pm10: [ { low: 0, high: 54, aqi_low: 0, aqi_high: 50, category: 'Good', color: '#00e400' }, { low: 55, high: 154, aqi_low: 51, aqi_high: 100, category: 'Moderate', color: '#ffff00' }, { low: 155, high: 254, aqi_low: 101, aqi_high: 150, category: 'Unhealthy for Sensitive Groups', color: '#ff7e00' }, { low: 255, high: 354, aqi_low: 151, aqi_high: 200, category: 'Unhealthy', color: '#ff0000' }, { low: 355, high: 424, aqi_low: 201, aqi_high: 300, category: 'Very Unhealthy', color: '#8f3f97' }, { low: 425, high: 604, aqi_low: 301, aqi_high: 500, category: 'Hazardous', color: '#7e0023' } ], o3: [ { low: 0, high: 54, aqi_low: 0, aqi_high: 50, category: 'Good', color: '#00e400' }, { low: 55, high: 70, aqi_low: 51, aqi_high: 100, category: 'Moderate', color: '#ffff00' }, { low: 71, high: 85, aqi_low: 101, aqi_high: 150, category: 'Unhealthy for Sensitive Groups', color: '#ff7e00' }, { low: 86, high: 105, aqi_low: 151, aqi_high: 200, category: 'Unhealthy', color: '#ff0000' }, { low: 106, high: 200, aqi_low: 201, aqi_high: 300, category: 'Very Unhealthy', color: '#8f3f97' } ] }; this.healthRecommendations = { 'Good': { general: 'Air quality is satisfactory. Enjoy outdoor activities!', sensitive: 'No health concerns for sensitive individuals.', activities: ['Outdoor exercise', 'Windows open', 'Outdoor events'] }, 'Moderate': { general: 'Air quality is acceptable for most people.', sensitive: 'Sensitive individuals may experience minor symptoms.', activities: ['Normal outdoor activities', 'Monitor sensitive individuals'] }, 'Unhealthy for Sensitive Groups': { general: 'Most people can continue normal activities.', sensitive: 'Sensitive groups should limit prolonged outdoor exertion.', activities: ['Reduce outdoor exercise', 'Close windows', 'Use air purifiers'] }, 'Unhealthy': { general: 'Everyone may experience health effects.', sensitive: 'Sensitive groups should avoid outdoor activities.', activities: ['Limit outdoor activities', 'Wear masks outdoors', 'Stay indoors'] }, 'Very Unhealthy': { general: 'Health alert: everyone may experience serious health effects.', sensitive: 'Sensitive groups should remain indoors.', activities: ['Avoid outdoor activities', 'Keep windows closed', 'Use air purifiers'] }, 'Hazardous': { general: 'Emergency conditions: everyone should avoid outdoor activities.', sensitive: 'Everyone should remain indoors.', activities: ['Stay indoors', 'Seal windows/doors', 'Use high-quality air purifiers'] } }; } /** * Analyze air quality from sensor data or API */ async analyzeAirQuality(data, location = null) { try { console.log('🌬️ Starting air quality analysis...'); let airQualityData; if (typeof data === 'object' && data.pm25 !== undefined) { // Direct sensor data airQualityData = data; } else if (location) { // Fetch from API or simulate based on location airQualityData = await this.fetchAirQualityData(location); } else { // Generate realistic mock data airQualityData = this.generateMockAirQualityData(); } // Calculate AQI for each pollutant const aqiResults = {}; const pollutants = ['pm25', 'pm10', 'o3', 'no2', 'so2', 'co']; pollutants.forEach(pollutant => { if (airQualityData[pollutant] !== undefined) { aqiResults[pollutant] = this.calculateAQI(pollutant, airQualityData[pollutant]); } }); // Determine overall AQI (highest individual AQI) const overallAQI = Math.max(...Object.values(aqiResults).map(r => r.aqi)); const dominantPollutant = Object.keys(aqiResults).find(p => aqiResults[p].aqi === overallAQI); const category = aqiResults[dominantPollutant]?.category || 'Unknown'; // Generate health recommendations const recommendations = this.generateHealthRecommendations(category, aqiResults); // Calculate air quality trends (mock for now) const trends = this.calculateAirQualityTrends(airQualityData); // Generate detailed analysis const analysis = { timestamp: new Date().toISOString(), location: location, overallAQI: overallAQI, category: category, dominantPollutant: dominantPollutant, color: aqiResults[dominantPollutant]?.color || '#666666', pollutants: airQualityData, aqiBreakdown: aqiResults, healthRecommendations: recommendations, trends: trends, confidence: this.calculateConfidence(airQualityData), dataSource: airQualityData.source || 'Simulated', lastUpdated: airQualityData.timestamp || new Date().toISOString() }; console.log('✅ Air quality analysis completed'); return analysis; } catch (error) { console.error('Air quality analysis failed:', error); throw new Error(`Air quality analysis failed: ${error.message}`); } } /** * Calculate AQI for a specific pollutant */ calculateAQI(pollutant, concentration) { const breakpoints = this.aqiBreakpoints[pollutant]; if (!breakpoints) { return { aqi: 0, category: 'Unknown', color: '#666666' }; } // Find the appropriate breakpoint let breakpoint = breakpoints.find(bp => concentration >= bp.low && concentration <= bp.high); if (!breakpoint) { // Handle values outside normal ranges if (concentration < breakpoints[0].low) { breakpoint = breakpoints[0]; } else { breakpoint = breakpoints[breakpoints.length - 1]; } } // Calculate AQI using linear interpolation const aqi = Math.round( ((breakpoint.aqi_high - breakpoint.aqi_low) / (breakpoint.high - breakpoint.low)) * (concentration - breakpoint.low) + breakpoint.aqi_low ); return { aqi: Math.max(0, Math.min(500, aqi)), category: breakpoint.category, color: breakpoint.color, concentration: concentration, unit: this.getPollutantUnit(pollutant) }; } /** * Get pollutant unit */ getPollutantUnit(pollutant) { const units = { pm25: 'μg/m³', pm10: 'μg/m³', o3: 'ppb', no2: 'ppb', so2: 'ppb', co: 'ppm' }; return units[pollutant] || ''; } /** * Generate health recommendations */ generateHealthRecommendations(category, aqiResults) { const baseRecommendations = this.healthRecommendations[category] || this.healthRecommendations['Good']; const recommendations = { ...baseRecommendations, specific: [] }; // Add specific recommendations based on pollutants Object.keys(aqiResults).forEach(pollutant => { const result = aqiResults[pollutant]; if (result.aqi > 100) { switch (pollutant) { case 'pm25': case 'pm10': recommendations.specific.push('High particulate matter - consider wearing N95 masks outdoors'); break; case 'o3': recommendations.specific.push('High ozone levels - avoid outdoor exercise during peak hours'); break; case 'no2': recommendations.specific.push('High nitrogen dioxide - avoid busy roads and traffic areas'); break; } } }); return recommendations; } /** * Calculate air quality trends */ calculateAirQualityTrends(currentData) { // Mock trend data - in real implementation, this would use historical data const trends = { hourly: this.generateHourlyTrend(), daily: this.generateDailyTrend(), weekly: this.generateWeeklyTrend(), forecast: this.generateForecast() }; return trends; } generateHourlyTrend() { const hours = []; const now = new Date(); for (let i = 23; i >= 0; i--) { const hour = new Date(now.getTime() - i * 60 * 60 * 1000); hours.push({ time: hour.toISOString(), aqi: Math.floor(Math.random() * 100) + 20, category: this.getAQICategory(Math.floor(Math.random() * 100) + 20) }); } return hours; } generateDailyTrend() { const days = []; const now = new Date(); for (let i = 6; i >= 0; i--) { const day = new Date(now.getTime() - i * 24 * 60 * 60 * 1000); days.push({ date: day.toISOString().split('T')[0], avgAQI: Math.floor(Math.random() * 80) + 30, maxAQI: Math.floor(Math.random() * 120) + 50, category: this.getAQICategory(Math.floor(Math.random() * 80) + 30) }); } return days; } generateWeeklyTrend() { const weeks = []; const now = new Date(); for (let i = 3; i >= 0; i--) { const week = new Date(now.getTime() - i * 7 * 24 * 60 * 60 * 1000); weeks.push({ week: `Week of ${week.toISOString().split('T')[0]}`, avgAQI: Math.floor(Math.random() * 70) + 35, trend: Math.random() > 0.5 ? 'improving' : 'worsening' }); } return weeks; } generateForecast() { const forecast = []; const now = new Date(); for (let i = 1; i <= 3; i++) { const day = new Date(now.getTime() + i * 24 * 60 * 60 * 1000); forecast.push({ date: day.toISOString().split('T')[0], predictedAQI: Math.floor(Math.random() * 90) + 25, category: this.getAQICategory(Math.floor(Math.random() * 90) + 25), confidence: Math.floor(Math.random() * 30) + 70 }); } return forecast; } /** * Get AQI category from numeric value */ getAQICategory(aqi) { if (aqi <= 50) return 'Good'; if (aqi <= 100) return 'Moderate'; if (aqi <= 150) return 'Unhealthy for Sensitive Groups'; if (aqi <= 200) return 'Unhealthy'; if (aqi <= 300) return 'Very Unhealthy'; return 'Hazardous'; } /** * Fetch air quality data from API or simulate */ async fetchAirQualityData(location) { // In a real implementation, this would call an actual API // For now, generate realistic mock data based on location console.log('🌐 Fetching air quality data for:', location); // Simulate API delay await new Promise(resolve => setTimeout(resolve, 500)); return this.generateMockAirQualityData(location); } /** * Generate realistic mock air quality data */ generateMockAirQualityData(location = null) { // Base values that vary by location type let baseValues = { pm25: 25, pm10: 45, o3: 60, no2: 30, so2: 15, co: 1.2 }; // Adjust based on location characteristics if (location) { const locationLower = location.toLowerCase(); if (locationLower.includes('urban') || locationLower.includes('city')) { baseValues.pm25 += 15; baseValues.pm10 += 20; baseValues.no2 += 20; baseValues.co += 0.8; } else if (locationLower.includes('rural') || locationLower.includes('forest')) { baseValues.pm25 -= 10; baseValues.pm10 -= 15; baseValues.no2 -= 15; baseValues.co -= 0.5; baseValues.o3 += 10; // Higher ozone in rural areas sometimes } else if (locationLower.includes('industrial')) { baseValues.pm25 += 25; baseValues.pm10 += 30; baseValues.so2 += 20; baseValues.no2 += 25; } } // Add realistic variation const variation = 0.3; // 30% variation Object.keys(baseValues).forEach(pollutant => { const variance = baseValues[pollutant] * variation * (Math.random() - 0.5); baseValues[pollutant] = Math.max(0, baseValues[pollutant] + variance); }); // Add time-of-day effects const hour = new Date().getHours(); if (hour >= 7 && hour <= 9) { // Morning rush hour baseValues.no2 *= 1.3; baseValues.co *= 1.2; } else if (hour >= 17 && hour <= 19) { // Evening rush hour baseValues.no2 *= 1.4; baseValues.co *= 1.3; } else if (hour >= 12 && hour <= 16) { // Afternoon ozone peak baseValues.o3 *= 1.2; } return { pm25: Math.round(baseValues.pm25 * 10) / 10, pm10: Math.round(baseValues.pm10), o3: Math.round(baseValues.o3), no2: Math.round(baseValues.no2), so2: Math.round(baseValues.so2), co: Math.round(baseValues.co * 10) / 10, timestamp: new Date().toISOString(), source: 'Simulated Data', location: location }; } /** * Calculate confidence in air quality data */ calculateConfidence(data) { let confidence = 85; // Base confidence // Reduce confidence if data seems unrealistic if (data.pm25 > 200 || data.pm10 > 400) confidence -= 15; if (data.o3 > 150) confidence -= 10; // Increase confidence for consistent data const pollutantCount = Object.keys(data).filter(key => typeof data[key] === 'number' && key !== 'timestamp' ).length; if (pollutantCount >= 5) confidence += 10; return Math.max(60, Math.min(98, confidence)); } /** * Generate air quality alerts */ generateAlerts(analysis) { const alerts = []; if (analysis.overallAQI > 150) { alerts.push({ level: 'high', message: `Air quality is ${analysis.category}. Limit outdoor activities.`, pollutant: analysis.dominantPollutant, aqi: analysis.overallAQI }); } else if (analysis.overallAQI > 100) { alerts.push({ level: 'medium', message: `Air quality is ${analysis.category}. Sensitive individuals should be cautious.`, pollutant: analysis.dominantPollutant, aqi: analysis.overallAQI }); } // Check for specific pollutant alerts Object.keys(analysis.aqiBreakdown).forEach(pollutant => { const result = analysis.aqiBreakdown[pollutant]; if (result.aqi > 200) { alerts.push({ level: 'high', message: `Very high ${pollutant.toUpperCase()} levels detected (${result.concentration} ${result.unit})`, pollutant: pollutant, aqi: result.aqi }); } }); return alerts; } } // Create singleton instance export const airQualityAnalyzer = new AirQualityAnalyzer(); // Export main analysis function export const analyzeAirQuality = async (data, location) => { try { const analysis = await airQualityAnalyzer.analyzeAirQuality(data, location); const alerts = airQualityAnalyzer.generateAlerts(analysis); return { ...analysis, alerts: alerts }; } catch (error) { console.error('Air quality analysis error:', error); throw error; } }; export default airQualityAnalyzer;