GreenPlusbyGXS / web /src /utils /airQualityAnalysis.js
gaialive's picture
Upload 106 files
759768a verified
/**
* 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;