py_atmo_data_wrapper/examples/example_synthese_nancy.py
2025-07-14 17:56:57 +02:00

324 lines
13 KiB
Python
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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()