#!/usr/bin/env python3 """ Vue synthétique combinée : Qualité de l'air et Pollen pour Nancy ================================================================ Ce script combine les données d'indices ATMO (qualité de l'air) et d'indices Pollen pour la ville de Nancy (54395) afin de fournir une vue synthétique de la situation environnementale. Fonctionnalités: - Récupération simultanée des indices ATMO et Pollen - Analyse croisée qualité air / risque pollinique - Vue synthétique avec recommandations - Affichage avec émojis et couleurs - Détection des risques combinés """ import sys import os from datetime import datetime # Ajouter le répertoire parent au PYTHONPATH pour importer le package local sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from atmo_data_wrapper import AtmoDataClient, AASQA_CODES def get_risk_level_combined(atmo_code: int, pollen_max: int, pollen_alert: bool) -> dict: """Détermine le niveau de risque combiné air + pollen""" # Conversion des codes en niveaux de risque (0-4) atmo_risk = min(atmo_code, 4) if atmo_code > 0 else 0 pollen_risk = min(pollen_max, 4) if pollen_max > 0 else 0 # Bonus si alerte pollen active if pollen_alert: pollen_risk = min(pollen_risk + 1, 4) # Calcul du risque combiné (moyenne pondérée) combined_risk = int((atmo_risk * 0.6 + pollen_risk * 0.4)) risk_levels = { 0: {"level": "Très faible", "emoji": "🟢", "color": "#50F0E6", "advice": "Conditions excellentes"}, 1: {"level": "Faible", "emoji": "🟡", "color": "#50CCAA", "advice": "Conditions favorables"}, 2: {"level": "Modéré", "emoji": "🟠", "color": "#F0E641", "advice": "Prudence pour personnes sensibles"}, 3: {"level": "Élevé", "emoji": "🔴", "color": "#FF5050", "advice": "Éviter activités extérieures prolongées"}, 4: {"level": "Très élevé", "emoji": "🟣", "color": "#A020F0", "advice": "Rester à l'intérieur si possible"} } result = risk_levels.get(combined_risk, risk_levels[2]) result["risk_score"] = combined_risk result["atmo_contribution"] = atmo_risk result["pollen_contribution"] = pollen_risk return result def format_pollutants_summary(atmo_indice) -> str: """Formate un résumé des polluants""" summary = atmo_indice.get_pollutants_summary() parts = [] for polluant, data in summary.items(): if data['code'] > 2: # Seulement les polluants problématiques parts.append(f"{polluant}({data['code']})") return ", ".join(parts) if parts else "Aucun polluant problématique" def format_pollens_summary(pollen_indice) -> str: """Formate un résumé des pollens""" summary = pollen_indice.get_pollens_summary() parts = [] for code_taxon, data in summary.items(): if data['code'] >= 3: # Pollens avec indice moyen à élevé espece = data['espece'] niveau = data['code'] emoji = data['emoji'] parts.append(f"{espece} {emoji}({niveau})") return ", ".join(parts) if parts else "Tous pollens à niveau faible" def get_detailed_recommendations(atmo_indice, pollen_indice, combined_risk: dict) -> list: """Génère des recommandations détaillées""" recommendations = [] # Recommandations basées sur la qualité de l'air if atmo_indice.is_poor_quality(): worst_pol, code = atmo_indice.get_worst_pollutant() recommendations.append(f"🌬️ Qualité air dégradée ({worst_pol}: niveau {code})") if code >= 4: recommendations.append(" → Éviter le sport en extérieur") recommendations.append(" → Aérer pendant les pics de trafic") # Recommandations basées sur les pollens if pollen_indice.is_alert_active(): recommendations.append("🌸 Alerte pollen active") recommendations.append(" → Éviter sorties tôt le matin") recommendations.append(" → Garder fenêtres fermées") dangerous_pollens = pollen_indice.get_dangerous_pollens() if dangerous_pollens: pollens_str = ", ".join(dangerous_pollens) recommendations.append(f"🤧 Pollens à risque élevé: {pollens_str}") recommendations.append(" → Prévoir antihistaminiques si nécessaire") # Recommandations combinées if combined_risk["risk_score"] >= 3: recommendations.append("⚠️ Risque combiné élevé") recommendations.append(" → Limiter activités extérieures") recommendations.append(" → Privilégier intérieur climatisé/filtré") # Recommandations positives if combined_risk["risk_score"] <= 1: recommendations.append("✅ Conditions favorables") recommendations.append(" → Profitez des activités extérieures") recommendations.append(" → Idéal pour aérer le logement") return recommendations def display_synthesis_header(): """Affiche l'en-tête de la synthèse""" print("=" * 70) print("🌍 SYNTHÈSE ENVIRONNEMENTALE - Nancy") print("=" * 70) print(f"📅 Date: {datetime.now().strftime('%d/%m/%Y à %H:%M')}") print(f"📍 Ville: Nancy (54395) - Région Grand Est") print() def display_section_header(title: str, emoji: str = "📊"): """Affiche un en-tête de section""" print(f"{emoji} {title}") print("-" * 50) def main(): """Fonction principale""" display_synthesis_header() try: # Connexion au client print("🔗 Connexion à l'API Atmo Data...") client = AtmoDataClient() success = client.auto_login() if not success: print("❌ Échec de la connexion") return print("✅ Connexion réussie") print() # === RÉCUPÉRATION DES DONNÉES === print("📥 Récupération des données...") # Données ATMO spécifiques à Nancy (code INSEE 54395) atmo_data = client.get_indices_atmo( code_zone="54395", # Code INSEE de Nancy format="geojson" ) # Données Pollen pour Nancy pollen_data = client.get_indices_pollens( code_zone="54395", format="geojson" ) if not atmo_data or len(atmo_data) == 0: print("❌ Aucune donnée ATMO trouvée pour Nancy (54395)") print("ℹ️ Essai avec les données régionales...") # Fallback: récupérer les données régionales du Grand Est atmo_data = client.get_indices_atmo(aasqa="44", format="geojson") if not atmo_data or len(atmo_data) == 0: print("❌ Aucune donnée ATMO trouvée pour la région Grand Est") return else: print(f"✅ Données régionales récupérées ({len(atmo_data)} stations)") if not pollen_data or len(pollen_data) == 0: print("❌ Aucune donnée Pollen trouvée pour Nancy") return # Sélection des données pertinentes # Pour ATMO: prendre la première donnée (spécifique à Nancy si disponible) atmo_indice = atmo_data[0] # Pour Pollen: prendre la première donnée pollen_indice = pollen_data[0] print(f"✅ Données récupérées - ATMO: {atmo_indice.lib_zone}, Pollen: {pollen_indice.lib_zone}") print() # === ANALYSE QUALITÉ DE L'AIR === display_section_header("QUALITÉ DE L'AIR", "🌬️") print(f"Zone: {atmo_indice.lib_zone}") print(f"Indice global: {atmo_indice.get_qualificatif()} {atmo_indice.get_emoji()} (niveau {atmo_indice.code_qual})") hex_color, rgb_color = atmo_indice.get_color() print(f"Couleur: {hex_color}") # Polluants problématiques worst_pol, worst_code = atmo_indice.get_worst_pollutant() print(f"Polluant principal: {worst_pol} (niveau {worst_code})") # Résumé des polluants pollutants_summary = format_pollutants_summary(atmo_indice) print(f"Polluants élevés: {pollutants_summary}") # Coordonnées et données réglementaires if atmo_indice.has_coordinates(): print(f"Coordonnées: {atmo_indice.coordinates}") print(f"Type de zone: {atmo_indice.type_zone or 'Non spécifié'}") print(f"Source: {atmo_indice.get_source()}") print() # === ANALYSE POLLEN === display_section_header("INDICES POLLEN", "🌸") print(f"Zone: {pollen_indice.lib_zone}") print(f"Alerte active: {'✅ OUI' if pollen_indice.is_alert_active() else '❌ Non'}") # Pollen le plus élevé highest_pollen, highest_code = pollen_indice.get_highest_pollen() if highest_code > 0: highest_name = pollen_indice.get_pollens_summary()[highest_pollen]['espece'] highest_emoji = pollen_indice.get_pollens_summary()[highest_pollen]['emoji'] print(f"Pollen dominant: {highest_name} {highest_emoji} (niveau {highest_code})") else: print("Pollen dominant: Aucun pollen détecté") # Taxons responsables selon l'API responsible_pollens = pollen_indice.get_responsible_pollens() if responsible_pollens: print(f"Taxons responsables: {', '.join(responsible_pollens)}") # Résumé des pollens élevés pollens_summary = format_pollens_summary(pollen_indice) print(f"Pollens à surveiller: {pollens_summary}") # Concentration la plus élevée highest_conc_taxon, highest_conc_value = pollen_indice.get_highest_concentration() if highest_conc_value > 0: conc_name = pollen_indice.get_pollens_summary()[highest_conc_taxon]['espece'] print(f"Concentration max: {conc_name} ({highest_conc_value:.1f} grains/m³)") print(f"Source: {pollen_indice.get_source()}") print() # === ANALYSE COMBINÉE === display_section_header("RISQUE COMBINÉ", "⚖️") # Calcul du risque combiné combined_risk = get_risk_level_combined( atmo_indice.code_qual, highest_code, pollen_indice.is_alert_active() ) print(f"Niveau de risque: {combined_risk['level']} {combined_risk['emoji']}") print(f"Score global: {combined_risk['risk_score']}/4") print(f" • Contribution air: {combined_risk['atmo_contribution']}/4") print(f" • Contribution pollen: {combined_risk['pollen_contribution']}/4") print(f"Conseil général: {combined_risk['advice']}") print() # === RECOMMANDATIONS === display_section_header("RECOMMANDATIONS", "💡") recommendations = get_detailed_recommendations(atmo_indice, pollen_indice, combined_risk) if recommendations: for i, rec in enumerate(recommendations, 1): print(f"{rec}") else: print("✅ Aucune recommandation particulière") print(" Conditions normales pour toutes activités") print() # === RÉSUMÉ TECHNIQUE === display_section_header("INFORMATIONS TECHNIQUES", "🔬") print("Qualité de l'air:") print(f" • Date échéance: {atmo_indice.date_ech}") print(f" • Date diffusion: {atmo_indice.date_dif}") print(f" • AASQA: {atmo_indice.get_aasqa_name()}") # Concentrations si disponibles concentrations = atmo_indice.get_concentrations() significant_conc = {k: v for k, v in concentrations.items() if v > 0} if significant_conc: print(" • Concentrations (μg/m³):") for polluant, conc in significant_conc.items(): print(f" - {polluant}: {conc}") print() print("Pollen:") all_concentrations = pollen_indice.get_concentrations() significant_pollen = {k: v for k, v in all_concentrations.items() if v > 0} if significant_pollen: print(" • Concentrations (grains/m³):") for taxon, conc in significant_pollen.items(): taxon_name = pollen_indice.get_pollens_summary()[taxon]['espece'] print(f" - {taxon_name}: {conc:.1f}") print() # === FOOTER === print("=" * 70) print("🌱 Données fournies par Atmo France et les AASQA") print("📊 Synthèse générée par Atmo Data Wrapper") print("=" * 70) except Exception as e: print(f"❌ Erreur lors de l'exécution: {e}") import traceback print("\nDétails de l'erreur:") print(traceback.format_exc()) sys.exit(1) if __name__ == "__main__": main()