first commit

This commit is contained in:
mathieu 2025-07-14 17:56:57 +02:00
commit a233e18c0b
48 changed files with 55300 additions and 0 deletions

3
examples/__init__.py Normal file
View file

@ -0,0 +1,3 @@
"""
Examples package for Atmo Data Wrapper
"""

View file

@ -0,0 +1,276 @@
#!/usr/bin/env python3
"""
Démonstration des fonctions utilitaires AASQA avancées
===================================================
Ce script illustre l'utilisation des nouvelles fonctions utilitaires
avancées pour analyser et rechercher dans les données AASQA.
Fonctionnalités:
- Recherche par nom d'organisme ou région
- Statistiques détaillées sur la couverture
- Validation de l'intégrité des données
- Analyses comparatives
"""
import sys
import os
# 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 (
search_aasqa_by_name,
get_departments_count,
validate_department_coverage,
get_aasqa_statistics,
get_aasqa_info
)
def demo_search_functionality():
"""Démonstration de la recherche par nom"""
print("🔍 RECHERCHE PAR NOM D'ORGANISME OU RÉGION")
print("=" * 55)
search_terms = ['Atmo', 'Air', 'France', 'Sud', 'Grand']
for term in search_terms:
results = search_aasqa_by_name(term)
print(f"🔎 Recherche '{term}'{len(results)} résultat(s)")
for result in results[:3]: # Limiter à 3 résultats pour la démo
print(f"{result['organisme']} ({result['region']})")
if len(results) > 3:
print(f" ... et {len(results) - 3} autre(s)")
print()
def demo_department_statistics():
"""Démonstration des statistiques départementales"""
print("📊 STATISTIQUES DÉPARTEMENTALES")
print("=" * 55)
dept_counts = get_departments_count()
# Tri par nombre de départements (décroissant)
sorted_counts = sorted(dept_counts.items(), key=lambda x: x[1], reverse=True)
print("Classement par nombre de départements couverts:")
print()
for i, (aasqa_code, count) in enumerate(sorted_counts, 1):
aasqa_info = get_aasqa_info(aasqa_code)
organisme = aasqa_info['organisme']
region = aasqa_info['region']
if i <= 5: # Top 5
print(f"{i:2d}. {organisme:<30} : {count:2d} département(s) ({region})")
elif i == 6:
print(" ...")
elif i >= len(sorted_counts) - 2: # Bottom 3
print(f"{i:2d}. {organisme:<30} : {count:2d} département(s) ({region})")
print()
def demo_validation_report():
"""Démonstration du rapport de validation"""
print("✅ RAPPORT DE VALIDATION")
print("=" * 55)
validation = validate_department_coverage()
print("Intégrité des données AASQA:")
print()
print(f"📊 Total d'entrées départements: {validation['total_departments']}")
print(f"📊 Départements uniques: {validation['unique_departments']}")
print(f"📊 Couverture complète: {'✅ Oui' if validation['coverage_complete'] else '❌ Non'}")
print(f"📊 Doublons détectés: {'❌ Oui' if validation['has_duplicates'] else '✅ Non'}")
if validation['duplicates']:
print(f"⚠️ Départements en doublon: {', '.join(validation['duplicates'])}")
print()
def demo_comprehensive_statistics():
"""Démonstration des statistiques complètes"""
print("📈 STATISTIQUES COMPLÈTES")
print("=" * 55)
stats = get_aasqa_statistics()
print("Vue d'ensemble:")
print(f" • Nombre total d'AASQA: {stats['total_aasqa']}")
print(f" • Départements couverts: {stats['total_departments_covered']}")
print(f" • Départements uniques: {stats['unique_departments']}")
print(f" • Couverture moyenne: {stats['average_coverage']:.1f} départements/AASQA")
print()
print("Couverture maximale:")
max_info = stats['max_coverage']
print(f"{max_info['count']} départements")
print(f" • Organisme(s): {', '.join(max_info['aasqa_names'])}")
print()
print("Couverture minimale:")
min_info = stats['min_coverage']
print(f"{min_info['count']} département(s)")
print(f" • Organisme(s): {', '.join(min_info['aasqa_names'])}")
print()
print(f"Anomalies: {'❌ Détectées' if stats['has_anomalies'] else '✅ Aucune'}")
print()
def demo_practical_analysis():
"""Démonstration d'analyses pratiques"""
print("🎯 ANALYSES PRATIQUES")
print("=" * 55)
# Analyse 1: Organismes DOM-TOM
print("1⃣ Analyse DOM-TOM:")
dom_tom_codes = ['971', '972', '973', '974', '976']
from atmo_data_wrapper import get_aasqa_by_department
for code in dom_tom_codes:
aasqa_code = get_aasqa_by_department(code)
if aasqa_code:
aasqa_info = get_aasqa_info(aasqa_code)
print(f"{code}: {aasqa_info['region']}{aasqa_info['organisme']}")
print()
# Analyse 2: Régions métropolitaines les plus étendues
print("2⃣ Grandes régions métropolitaines:")
dept_counts = get_departments_count()
metro_counts = {}
for aasqa_code, count in dept_counts.items():
aasqa_info = get_aasqa_info(aasqa_code)
# Exclure DOM-TOM (codes postaux 97x)
if not any(dept.startswith('97') for dept in aasqa_info['departements']):
metro_counts[aasqa_code] = count
top_metro = sorted(metro_counts.items(), key=lambda x: x[1], reverse=True)[:3]
for i, (aasqa_code, count) in enumerate(top_metro, 1):
aasqa_info = get_aasqa_info(aasqa_code)
print(f" {i}. {aasqa_info['region']}: {count} départements ({aasqa_info['organisme']})")
print()
# Analyse 3: Organismes avec URL atmo vs autres
print("3⃣ Analyse des noms d'organismes:")
from atmo_data_wrapper import AASQA_CODES
atmo_orgs = []
other_orgs = []
for aasqa_code, aasqa_data in AASQA_CODES.items():
if 'atmo' in aasqa_data['organisme'].lower():
atmo_orgs.append(aasqa_data['organisme'])
else:
other_orgs.append(aasqa_data['organisme'])
print(f" • Organismes 'Atmo': {len(atmo_orgs)} ({len(atmo_orgs)/len(AASQA_CODES)*100:.0f}%)")
print(f" • Autres noms: {len(other_orgs)} ({len(other_orgs)/len(AASQA_CODES)*100:.0f}%)")
print(f" • Noms alternatifs: {', '.join(other_orgs[:3])}{'...' if len(other_orgs) > 3 else ''}")
print()
def demo_data_quality_checks():
"""Démonstration des vérifications de qualité"""
print("🔬 VÉRIFICATIONS DE QUALITÉ DES DONNÉES")
print("=" * 55)
from atmo_data_wrapper import AASQA_CODES
print("1⃣ Vérification des URLs:")
urls_https = 0
urls_http = 0
for aasqa_data in AASQA_CODES.values():
url = aasqa_data['site_web']
if url.startswith('https://'):
urls_https += 1
elif url.startswith('http://'):
urls_http += 1
print(f" • URLs HTTPS: {urls_https}/{len(AASQA_CODES)} ({urls_https/len(AASQA_CODES)*100:.0f}%)")
if urls_http > 0:
print(f" ⚠️ URLs HTTP: {urls_http}")
else:
print(f" ✅ Toutes les URLs sont sécurisées (HTTPS)")
print()
print("2⃣ Vérification des codes départementaux:")
valid_formats = 0
invalid_formats = []
for aasqa_code, aasqa_data in AASQA_CODES.items():
for dept in aasqa_data['departements']:
# Vérifier le format (2 chiffres, 3 chiffres pour DOM-TOM, ou 2A/2B pour Corse)
if (dept.isdigit() and len(dept) in [2, 3]) or dept in ['2A', '2B']:
valid_formats += 1
else:
invalid_formats.append(dept)
print(f" • Codes valides: {valid_formats}")
if invalid_formats:
print(f" ⚠️ Codes invalides: {invalid_formats}")
else:
print(f" ✅ Tous les codes départementaux sont valides")
print()
print("3⃣ Cohérence des descriptions:")
consistent = 0
inconsistent = []
for aasqa_code, aasqa_data in AASQA_CODES.items():
expected_desc = f"{aasqa_data['region']} | {aasqa_data['organisme']}"
if aasqa_data['description'] == expected_desc:
consistent += 1
else:
inconsistent.append(aasqa_code)
print(f" • Descriptions cohérentes: {consistent}/{len(AASQA_CODES)}")
if inconsistent:
print(f" ⚠️ Incohérences: {inconsistent}")
else:
print(f" ✅ Toutes les descriptions sont cohérentes")
def main():
"""Fonction principale"""
print("DÉMONSTRATION DES FONCTIONS UTILITAIRES AASQA AVANCÉES")
print("=" * 65)
print("Nouvelles analyses et vérifications de qualité des données")
print()
try:
demo_search_functionality()
demo_department_statistics()
demo_validation_report()
demo_comprehensive_statistics()
demo_practical_analysis()
demo_data_quality_checks()
print("=" * 65)
print("✅ TOUTES LES ANALYSES TERMINÉES AVEC SUCCÈS")
print()
print("🎯 Fonctions utilitaires démontrées:")
print(" • search_aasqa_by_name(): Recherche textuelle")
print(" • get_departments_count(): Comptage par AASQA")
print(" • validate_department_coverage(): Validation intégrité")
print(" • get_aasqa_statistics(): Statistiques complètes")
print(" • Analyses qualité et cohérence des données")
print()
print("📁 Fichiers concernés:")
print(" • constants.py: Données pures (sites web, départements)")
print(" • utils.py: Fonctions utilitaires (nouvelle architecture)")
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())
if __name__ == "__main__":
main()

View file

@ -0,0 +1,250 @@
#!/usr/bin/env python3
"""
Démonstration des nouvelles fonctionnalités AASQA
==============================================
Ce script illustre l'utilisation des nouvelles données enrichies
des AASQA : sites web, départements, et fonctions utilitaires.
Fonctionnalités démontrées:
- Recherche AASQA par département
- Informations complètes des organismes
- Sites web et contacts
- Nouvelles méthodes des classes de données
"""
import sys
import os
# 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,
get_aasqa_by_department,
get_aasqa_info,
get_aasqa_website,
list_departments_by_aasqa
)
def demo_department_search():
"""Démonstration de la recherche par département"""
print("🔍 RECHERCHE D'AASQA PAR DÉPARTEMENT")
print("=" * 50)
departments_test = ['54', '75', '13', '2A', '974', '99']
for dept in departments_test:
aasqa_code = get_aasqa_by_department(dept)
if aasqa_code:
aasqa_info = get_aasqa_info(aasqa_code)
print(f"📍 Département {dept}: {aasqa_info['organisme']} ({aasqa_info['region']})")
else:
print(f"❌ Département {dept}: Non trouvé")
print()
def demo_aasqa_details():
"""Démonstration des informations détaillées AASQA"""
print("📋 INFORMATIONS DÉTAILLÉES DES AASQA")
print("=" * 50)
# Quelques AASQA représentatives
aasqa_samples = ['44', '11', '93', '01', '94']
for aasqa_code in aasqa_samples:
info = get_aasqa_info(aasqa_code)
if info:
print(f"🏢 {info['organisme']}")
print(f" 📍 Région: {info['region']}")
print(f" 🌐 Site web: {info['site_web']}")
print(f" 📋 Départements: {', '.join(info['departements'])}")
print(f" 📊 Nombre de départements: {len(info['departements'])}")
print()
def demo_website_access():
"""Démonstration de l'accès aux sites web"""
print("🌐 SITES WEB DES AASQA")
print("=" * 50)
all_aasqa = list(AASQA_CODES.keys())
print("Liste des sites web par région:")
print()
for aasqa_code in all_aasqa:
website = get_aasqa_website(aasqa_code)
aasqa_info = get_aasqa_info(aasqa_code)
if website and aasqa_info:
print(f"{aasqa_info['region']:<25}{website}")
print()
def demo_department_coverage():
"""Démonstration de la couverture départementale"""
print("🗺️ COUVERTURE DÉPARTEMENTALE")
print("=" * 50)
# Statistiques sur la couverture
total_departments = 0
largest_coverage = 0
largest_aasqa = ""
print("Couverture par AASQA:")
print()
for aasqa_code in AASQA_CODES.keys():
departments = list_departments_by_aasqa(aasqa_code)
aasqa_info = get_aasqa_info(aasqa_code)
total_departments += len(departments)
if len(departments) > largest_coverage:
largest_coverage = len(departments)
largest_aasqa = aasqa_info['organisme']
print(f"📊 {aasqa_info['organisme']:<30} : {len(departments):2d} département(s)")
print()
print(f"📈 Statistiques:")
print(f" • Total départements couverts: {total_departments}")
print(f" • Plus grande couverture: {largest_aasqa} ({largest_coverage} départements)")
print()
def demo_enhanced_data_models():
"""Démonstration des nouvelles méthodes dans les modèles de données"""
print("🎯 NOUVELLES MÉTHODES DES MODÈLES")
print("=" * 50)
try:
# Connexion et récupération de données
client = AtmoDataClient()
success = client.auto_login()
if not success:
print("❌ Échec de la connexion API")
return
print("✅ Connexion API réussie")
print()
# Récupération d'indices ATMO pour test
print("📥 Récupération de données de test...")
atmo_data = client.get_indices_atmo(aasqa="44", format="geojson") # Grand Est
if atmo_data and len(atmo_data) > 0:
indice = atmo_data[0]
print(f"🎯 Données exemple: {indice.lib_zone}")
print()
print("🆕 Nouvelles méthodes AASQA:")
print(f" • get_aasqa_name(): {indice.get_aasqa_name()}")
print(f" • get_aasqa_website(): {indice.get_aasqa_website()}")
print(f" • get_aasqa_region(): {indice.get_aasqa_region()}")
print(f" • get_aasqa_organisme(): {indice.get_aasqa_organisme()}")
print()
# Trouver l'AASQA depuis un département fictif
dept_example = "54" # Meurthe-et-Moselle
aasqa_found = get_aasqa_by_department(dept_example)
print(f"🔍 Recherche par département {dept_example}: AASQA {aasqa_found}")
if aasqa_found:
deps = list_departments_by_aasqa(aasqa_found)
print(f" • Départements couverts: {', '.join(deps)}")
else:
print("❌ Aucune donnée récupérée")
except Exception as e:
print(f"❌ Erreur: {e}")
print()
def demo_practical_usage():
"""Démonstration d'usage pratique"""
print("💡 EXEMPLES D'USAGE PRATIQUE")
print("=" * 50)
print("1⃣ Trouver l'organisme responsable d'un département:")
print()
# Exemples pratiques
examples = [
("Nancy (54)", "54"),
("Paris (75)", "75"),
("Marseille (13)", "13"),
("Ajaccio (2A)", "2A"),
("Saint-Denis (974)", "974")
]
for ville, dept in examples:
aasqa_code = get_aasqa_by_department(dept)
if aasqa_code:
info = get_aasqa_info(aasqa_code)
print(f" {ville:<15}{info['organisme']}")
print(f" {'':>15} Site: {info['site_web']}")
else:
print(f" {ville:<15} → Non trouvé")
print()
print("2⃣ Vérification de cohérence:")
print()
# Vérification que tous les départements français sont couverts
all_covered_depts = []
for aasqa_code in AASQA_CODES.keys():
all_covered_depts.extend(list_departments_by_aasqa(aasqa_code))
print(f" • Départements uniques couverts: {len(set(all_covered_depts))}")
print(f" • Total d'entrées départements: {len(all_covered_depts)}")
# Recherche de doublons
seen = set()
duplicates = set()
for dept in all_covered_depts:
if dept in seen:
duplicates.add(dept)
seen.add(dept)
if duplicates:
print(f" ⚠️ Départements en doublon: {duplicates}")
else:
print(f" ✅ Aucun doublon détecté")
print()
def main():
"""Fonction principale"""
print("DÉMONSTRATION DES FONCTIONNALITÉS AASQA ENRICHIES")
print("=" * 60)
print("Nouvelles données : sites web, départements, fonctions utilitaires")
print()
try:
demo_department_search()
demo_aasqa_details()
demo_website_access()
demo_department_coverage()
demo_enhanced_data_models()
demo_practical_usage()
print("=" * 60)
print("✅ TOUTES LES DÉMONSTRATIONS TERMINÉES AVEC SUCCÈS")
print()
print("📋 Nouvelles fonctionnalités disponibles:")
print(" • Structure AASQA enrichie (sites web, départements)")
print(" • Fonctions utilitaires de recherche")
print(" • Nouvelles méthodes dans les modèles de données")
print(" • Usage simplifié par département")
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())
if __name__ == "__main__":
main()

View file

@ -0,0 +1,367 @@
#!/usr/bin/env python3
"""
Exemples d'utilisation des nouveaux modèles de données typés
"""
from atmo_data_wrapper import AtmoDataClient
from atmo_data_wrapper import Coordinates
def example_indices_atmo():
"""Exemple d'utilisation des indices ATMO avec objets typés"""
print("=== Exemple Indices ATMO avec objets typés ===\n")
client = AtmoDataClient()
# Connexion automatique avec credentials.json
# Décommentez les lignes suivantes pour une vraie connexion API :
# try:
# client.auto_login()
# indices = client.get_indices_atmo(aasqa="11") # Données réelles
# except Exception as e:
# print(f"Erreur de connexion: {e}")
# print("Utilisation de données de test...")
# # Fallback sur données de test...
# Récupération des indices ATMO - maintenant retourne AtmoDataCollection
try:
# indices = client.get_indices_atmo(aasqa="11") # Île-de-France
# Pour la démonstration, créons des données de test
test_data = {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [2.3522, 48.8566]
},
"properties": {
"aasqa": "11",
"code_qual": 3,
"lib_qual": "Moyen",
"coul_qual": "#FFFF00",
"lib_zone": "Paris Centre",
"date_dif": "2024-07-07",
"code_no2": 2,
"code_so2": 1,
"code_o3": 3,
"code_pm10": 2,
"code_pm25": 3
}
},
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [2.4522, 48.9566]
},
"properties": {
"aasqa": "11",
"code_qual": 5,
"lib_qual": "Mauvais",
"coul_qual": "#FF0000",
"lib_zone": "Banlieue Nord",
"date_dif": "2024-07-07",
"code_no2": 4,
"code_so2": 2,
"code_o3": 5,
"code_pm10": 4,
"code_pm25": 5
}
}
]
}
# Créer une collection typée
from atmo_data_wrapper import AtmoDataCollection
indices = AtmoDataCollection(test_data, 'indices')
print(f"Collection: {len(indices)} indices récupérés")
print(f"Résumé: {indices.to_summary()}\n")
# Parcourir les indices avec les objets typés
for i, indice in enumerate(indices):
print(f"Indice {i+1}:")
print(f" Zone: {indice.lib_zone}")
print(f" AASQA: {indice.get_aasqa_name()}")
print(f" Qualité: {indice.get_qualificatif()} (code: {indice.code_qual})")
# Utiliser les méthodes helper
hex_color, rgb_color = indice.get_color()
print(f" Couleur: {hex_color} (RGB: {rgb_color})")
print(f" Bonne qualité: {'Oui' if indice.is_good_quality() else 'Non'}")
print(f" Mauvaise qualité: {'Oui' if indice.is_poor_quality() else 'Non'}")
# Polluant le plus problématique
worst_pol, worst_code = indice.get_worst_pollutant()
print(f" Pire polluant: {worst_pol} (code: {worst_code})")
# Coordonnées
if indice.has_coordinates():
print(f" Coordonnées: {indice.coordinates}")
print()
# Statistiques sur la collection
stats = indices.get_statistics()
print("Statistiques de la collection:")
for key, value in stats.items():
print(f" {key}: {value}")
print()
# Filtrage par qualité
print("=== Filtrage et analyse ===")
mauvaise_qualite = [indice for indice in indices if indice.is_poor_quality()]
print(f"Zones avec mauvaise qualité: {len(mauvaise_qualite)}")
for indice in mauvaise_qualite:
print(f" - {indice.lib_zone}: {indice.get_qualificatif()}")
except Exception as e:
print(f"Erreur: {e}")
def example_episodes_pollution():
"""Exemple d'utilisation des épisodes de pollution avec objets typés"""
print("\n=== Exemple Épisodes de Pollution avec objets typés ===\n")
# Données de test pour épisodes
test_data = {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "MultiPolygon",
"coordinates": [[[2.0, 48.0], [3.0, 48.0], [3.0, 49.0], [2.0, 49.0], [2.0, 48.0]]]
},
"properties": {
"aasqa": "11",
"code_pol": "5",
"lib_pol": "PM10",
"lib_zone": "Île-de-France",
"date_dif": "2024-07-07",
"etat": "INFORMATION ET RECOMMANDATIONS"
}
},
{
"type": "Feature",
"geometry": {
"type": "MultiPolygon",
"coordinates": [[[1.0, 47.0], [2.0, 47.0], [2.0, 48.0], [1.0, 48.0], [1.0, 47.0]]]
},
"properties": {
"aasqa": "24",
"code_pol": "3",
"lib_pol": "Ozone",
"lib_zone": "Centre-Val de Loire",
"date_dif": "2024-07-07",
"etat": "ALERTE"
}
}
]
}
from atmo_data_wrapper import AtmoDataCollection
episodes = AtmoDataCollection(test_data, 'episodes')
print(f"Collection: {len(episodes)} épisodes récupérés")
print(f"Résumé: {episodes.to_summary()}\n")
for i, episode in enumerate(episodes):
print(f"Épisode {i+1}:")
print(f" Zone: {episode.lib_zone}")
print(f" Polluant: {episode.lib_pol} (code: {episode.get_polluant_code()})")
print(f" État: {episode.etat}")
print(f" Alerte active: {'Oui' if episode.is_alert_active() else 'Non'}")
print(f" Niveau d'alerte: {episode.get_alert_level()}")
print(f" Géométrie complexe: {'Oui' if episode.is_geometry_complex() else 'Non'}")
print()
def example_emissions_data():
"""Exemple d'utilisation des données d'émissions avec objets typés"""
print("=== Exemple Données d'Émissions avec objets typés ===\n")
# Données de test pour émissions
test_data = {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [2.3522, 48.8566]
},
"properties": {
"aasqa": "11",
"code": "75056",
"name": "Paris",
"population": 2165423,
"superficie": 105.4,
"nox": 15420.5,
"pm10": 890.2,
"pm25": 623.8,
"ges": 12345678.9,
"code_pcaet": "7"
}
}
]
}
from atmo_data_wrapper import AtmoDataCollection
emissions = AtmoDataCollection(test_data, 'emissions')
print(f"Collection: {len(emissions)} données d'émissions récupérées\n")
for emission in emissions:
print(f"Territoire: {emission.name}")
print(f" Population: {emission.population:,} habitants")
print(f" Superficie: {emission.superficie} km²")
print(f" Secteur: {emission.get_secteur_name()}")
print()
# Émissions totales
total_emissions = emission.get_total_emissions()
print(" Émissions totales:")
for polluant, valeur in total_emissions.items():
print(f" {polluant}: {valeur:,.1f} t/an")
print()
# Densités et per capita
print(" Densités d'émission (t/km²):")
for polluant in ['nox', 'pm10', 'pm25']:
density = emission.get_emission_density(polluant)
print(f" {polluant.upper()}: {density:.2f}")
print(" Émissions par habitant (kg/hab):")
for polluant in ['nox', 'pm10', 'pm25']:
per_capita = emission.get_emission_per_capita(polluant) * 1000 # Convert to kg
print(f" {polluant.upper()}: {per_capita:.2f}")
print()
def example_pollen_indices():
"""Exemple d'utilisation des indices pollen avec objets typés"""
print("=== Exemple Indices Pollen avec objets typés ===\n")
# Données de test pour pollens
test_data = {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [2.3522, 48.8566]
},
"properties": {
"aasqa": "11",
"alerte": True,
"code_ambr": 5,
"code_arm": 2,
"code_aul": 1,
"code_boul": 4,
"code_gram": 3,
"code_oliv": 1
}
}
]
}
from atmo_data_wrapper import AtmoDataCollection
pollens = AtmoDataCollection(test_data, 'pollens')
print(f"Collection: {len(pollens)} indices pollen récupérés\n")
for pollen in pollens:
print(f"Station pollen:")
print(f" Alerte active: {'Oui' if pollen.is_alert_active() else 'Non'}")
# Pollen le plus élevé
highest_pollen, highest_code = pollen.get_highest_pollen()
print(f" Plus haut niveau: {highest_pollen} (code: {highest_code})")
# Résumé de tous les pollens
summary = pollen.get_pollens_summary()
print(" Détail par espèce:")
for code, info in summary.items():
print(f" {info['espece']}: {info['qualificatif']} (code: {info['code']})")
# Pollens dangereux
dangerous = pollen.get_dangerous_pollens()
if dangerous:
print(f" Pollens à risque élevé: {', '.join(dangerous)}")
else:
print(" Aucun pollen à risque élevé")
print()
def example_geographic_filtering():
"""Exemple de filtrage géographique"""
print("=== Exemple Filtrage Géographique ===\n")
# Créer des données avec différentes localisations
test_data = {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [2.3522, 48.8566] # Paris
},
"properties": {
"aasqa": "11",
"code_qual": 3,
"lib_zone": "Paris Centre"
}
},
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [2.2945, 48.8584] # Proche de Paris
},
"properties": {
"aasqa": "11",
"code_qual": 2,
"lib_zone": "Boulogne"
}
},
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [5.3698, 43.2965] # Marseille (loin)
},
"properties": {
"aasqa": "93",
"code_qual": 4,
"lib_zone": "Marseille"
}
}
]
}
from atmo_data_wrapper import AtmoDataCollection, Coordinates
indices = AtmoDataCollection(test_data, 'indices')
print(f"Collection initiale: {len(indices)} éléments")
# Définir un point central (Paris)
paris_center = Coordinates(2.3522, 48.8566)
# Filtrer dans un rayon de 10 km autour de Paris
nearby = indices.filter_by_coordinates(paris_center, 10.0)
print(f"Dans un rayon de 10km de Paris: {len(nearby)} éléments")
for item in nearby:
if item.has_coordinates():
distance = item.coordinates.distance_to(paris_center)
print(f" {item.lib_zone}: {distance:.2f} km de Paris")
print()
if __name__ == "__main__":
example_indices_atmo()
example_episodes_pollution()
example_emissions_data()
example_pollen_indices()
example_geographic_filtering()

View file

@ -0,0 +1,185 @@
#!/usr/bin/env python3
"""
Exemples d'utilisation de la fonctionnalité de sauvegarde du wrapper AtmoDataClient
"""
from atmo_data_wrapper import AtmoDataClient, AtmoDataException
from atmo_data_wrapper import AASQA_CODES, POLLUANTS
from datetime import datetime, timedelta
import os
def main():
"""Exemples de sauvegarde de données"""
print("=== Exemples de sauvegarde de données API Atmo ===\n")
# Initialisation du client
client = AtmoDataClient()
# Note: Pour ces exemples, nous utilisons des données factices
# car nous n'avons pas d'authentification réelle
# Données d'exemple au format GeoJSON (structure typique de l'API)
sample_geojson_data = {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [2.3522, 48.8566] # Paris
},
"properties": {
"code_zone": "75056",
"nom_zone": "Paris",
"aasqa": "11",
"nom_aasqa": "Airparif",
"date": "2024-07-07",
"code_qualificatif": "2",
"qualificatif": "Moyen",
"polluant_principal": "PM10",
"valeur": 45.2
}
},
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [2.2945, 48.8584] # Boulogne
},
"properties": {
"code_zone": "92012",
"nom_zone": "Boulogne-Billancourt",
"aasqa": "11",
"nom_aasqa": "Airparif",
"date": "2024-07-07",
"code_qualificatif": "3",
"qualificatif": "Dégradé",
"polluant_principal": "NO2",
"valeur": 52.8
}
}
]
}
# Exemple 1: Sauvegarde en JSON
print("1. Sauvegarde en format JSON...")
try:
json_file = client.save_to_file(
data=sample_geojson_data,
filename="data/indices_atmo",
file_format="json"
)
print(f"✅ Fichier JSON sauvegardé: {json_file}")
except Exception as e:
print(f"❌ Erreur JSON: {e}")
# Exemple 2: Sauvegarde en GeoJSON
print("\n2. Sauvegarde en format GeoJSON...")
try:
geojson_file = client.save_to_file(
data=sample_geojson_data,
filename="data/indices_atmo_geo",
file_format="geojson"
)
print(f"✅ Fichier GeoJSON sauvegardé: {geojson_file}")
except Exception as e:
print(f"❌ Erreur GeoJSON: {e}")
# Exemple 3: Sauvegarde en CSV
print("\n3. Sauvegarde en format CSV...")
try:
csv_file = client.save_to_file(
data=sample_geojson_data,
filename="data/indices_atmo",
file_format="csv"
)
print(f"✅ Fichier CSV sauvegardé: {csv_file}")
except Exception as e:
print(f"❌ Erreur CSV: {e}")
# Exemple 4: Sauvegarde avec chemin complet
print("\n4. Sauvegarde avec chemin personnalisé...")
try:
custom_file = client.save_to_file(
data=sample_geojson_data,
filename="exports/qualite_air/paris_2024",
file_format="json"
)
print(f"✅ Fichier personnalisé sauvegardé: {custom_file}")
except Exception as e:
print(f"❌ Erreur sauvegarde personnalisée: {e}")
# Exemple 5: Gestion d'erreurs - format invalide
print("\n5. Test de validation - format invalide...")
try:
client.save_to_file(
data=sample_geojson_data,
filename="test",
file_format="xml" # Format non supporté
)
print("❌ Validation échouée - format invalide accepté")
except ValueError as e:
print(f"✅ Validation réussie: {e}")
# Exemple 6: Workflow complet avec récupération de données
print("\n6. Exemple de workflow complet (simulation)...")
try:
# Simulation d'un appel API réel
print(" - Récupération des indices ATMO (simulé)...")
# indices = client.get_indices_atmo(aasqa="11", date="2024-07-07")
# Utilisation des données d'exemple
print(" - Sauvegarde des données...")
workflow_file = client.save_to_file(
data=sample_geojson_data,
filename=f"exports/daily/indices_atmo_{datetime.now().strftime('%Y%m%d')}",
file_format="csv"
)
print(f"✅ Workflow terminé: {workflow_file}")
# Affichage des informations sur le fichier
if os.path.exists(workflow_file):
size = os.path.getsize(workflow_file)
print(f" - Taille du fichier: {size} bytes")
print(f" - Nombre de lignes: {len(sample_geojson_data['features']) + 1}") # +1 pour l'en-tête
except Exception as e:
print(f"❌ Erreur workflow: {e}")
# Exemple 7: Données sans géométrie (pour test CSV)
print("\n7. Test CSV sans coordonnées...")
sample_data_no_geom = {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": None,
"properties": {
"code_zone": "75056",
"nom_zone": "Paris",
"date": "2024-07-07",
"indice_atmo": 3,
"qualificatif": "Dégradé"
}
}
]
}
try:
no_geom_file = client.save_to_file(
data=sample_data_no_geom,
filename="data/indices_sans_coordonnees",
file_format="csv"
)
print(f"✅ CSV sans géométrie sauvegardé: {no_geom_file}")
except Exception as e:
print(f"❌ Erreur CSV sans géométrie: {e}")
print("\n=== Exemples terminés ===")
print("\nFichiers créés dans:")
print("- ./data/")
print("- ./exports/")
if __name__ == "__main__":
main()

View file

@ -0,0 +1,324 @@
#!/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()

166
examples/example_usage.py Normal file
View file

@ -0,0 +1,166 @@
#!/usr/bin/env python3
"""
Exemples d'utilisation du wrapper AtmoDataClient
"""
from atmo_data_wrapper import AtmoDataClient, AtmoDataException
from atmo_data_wrapper import AASQA_CODES, POLLUANTS
from datetime import datetime, timedelta
def main():
# Initialisation du client
client = AtmoDataClient()
# Authentification automatique avec credentials.json
try:
success = client.auto_login()
if not success:
print("Échec de l'authentification")
return
print("Connexion réussie avec credentials.json !")
except AtmoDataException as e:
print(f"Erreur d'authentification: {e}")
print("💡 Assurez-vous d'avoir créé le fichier credentials.json")
print(" Utilisez credentials.json.example comme modèle")
return
# Exemple 1: Récupération des indices ATMO d'aujourd'hui
print("\n=== Indices ATMO d'aujourd'hui ===")
try:
today = datetime.now().strftime("%Y-%m-%d")
indices = client.get_indices_atmo(
date=today,
aasqa="11", # Spécifier une région pour éviter erreur serveur
format="geojson"
)
print(f"Nombre d'indices récupérés: {len(indices)}")
print(f"Résumé: {indices.to_summary()}")
# Exemples avec objets typés
if len(indices) > 0:
first_indice = indices[0]
print(f"Premier indice - Zone: {first_indice.lib_zone}")
print(f"Qualité: {first_indice.get_qualificatif()}")
except AtmoDataException as e:
print(f"Erreur: {e}")
# Exemple 2: Récupération des épisodes de pollution en cours
print("\n=== Épisodes de pollution en cours ===")
try:
episodes = client.get_episodes_3jours(
format="geojson",
aasqa="11", # Île-de-France
polluant=POLLUANTS[2] # PM10
)
print(f"Nombre d'épisodes: {len(episodes)}")
# Analyser les alertes
alerts_actives = [ep for ep in episodes if ep.is_alert_active()]
print(f"Alertes actives: {len(alerts_actives)}")
if alerts_actives:
for alert in alerts_actives[:3]: # Max 3 exemples
print(f" - {alert.lib_zone}: {alert.get_alert_level()}")
except AtmoDataException as e:
print(f"Erreur: {e}")
# Exemple 3: Données d'émissions pour l'Île-de-France
print(f"\n=== Émissions {AASQA_CODES['11']} ===")
try:
emissions = client.get_emissions(
aasqa="11", # Île-de-France
echelle="region",
format="geojson"
)
print(f"Données d'émissions récupérées: {len(emissions)}")
# Analyser les émissions
if len(emissions) > 0:
em = emissions[0]
print(f"Territoire: {em.name}")
print(f"Population: {em.population:,.0f} habitants")
total_em = em.get_total_emissions()
print(f"Émissions NOx: {total_em['NOx']:,.1f} t/an")
except AtmoDataException as e:
print(f"Erreur: {e}")
# Exemple 4: Indices pollen avec alerte
print("\n=== Indices pollen avec alerte ===")
try:
pollens = client.get_indices_pollens(
format="geojson",
aasqa="11", # Île-de-France
alerte=True,
with_geom=True
)
print(f"Nombre d'alertes pollen: {len(pollens)}")
# Analyser les pollens dangereux
if len(pollens) > 0:
dangerous_pollens = []
for pollen in pollens[:5]: # Max 5 exemples
dangerous = pollen.get_dangerous_pollens()
if dangerous:
dangerous_pollens.extend(dangerous)
if dangerous_pollens:
print(f"Pollens à risque élevé: {', '.join(set(dangerous_pollens))}")
except AtmoDataException as e:
print(f"Erreur: {e}")
# Exemple 5: Recherche dans une zone géographique (bounding box)
print("\n=== Recherche dans une zone géographique ===")
try:
# Bounding box approximative de Paris
bbox = "2.2 48.8 2.4 48.9"
indices_paris = client.get_indices_atmo(
bounding_box=bbox,
date=today,
format="geojson"
)
print(f"Indices dans la zone Paris: {len(indices_paris)}")
# Analyser la qualité
if len(indices_paris) > 0:
stats = indices_paris.get_statistics()
qs = stats['quality_stats']
print(f"Qualité moyenne: {qs['moyenne']:.1f}/7")
print(f"Bonne qualité: {qs['bon_pourcentage']:.1f}%")
except AtmoDataException as e:
print(f"Erreur: {e}")
# Exemple 6: Données historiques
print("\n=== Données historiques ===")
try:
# Episodes d'il y a 30 jours
date_historique = (datetime.now() - timedelta(days=30)).strftime("%Y-%m-%d")
episodes_historiques = client.get_episodes_historique(
date=today,
date_historique=date_historique,
aasqa="11", # Île-de-France
format="geojson"
)
print(f"Épisodes sur 30 jours: {len(episodes_historiques)}")
# Analyser l'évolution
if len(episodes_historiques) > 0:
alerts_historiques = [ep for ep in episodes_historiques if ep.is_alert_active()]
print(f"Alertes historiques: {len(alerts_historiques)}")
# Compter par polluant
polluants_hist = {}
for ep in episodes_historiques:
pol = ep.lib_pol
polluants_hist[pol] = polluants_hist.get(pol, 0) + 1
print("Répartition par polluant:")
for pol, count in polluants_hist.items():
print(f" {pol}: {count} épisodes")
except AtmoDataException as e:
print(f"Erreur: {e}")
if __name__ == "__main__":
main()

View file

@ -0,0 +1,126 @@
#!/usr/bin/env python3
"""
Script d'aide à la configuration des credentials
"""
import json
import os
import shutil
from getpass import getpass
def setup_credentials():
"""Guide l'utilisateur pour configurer ses credentials"""
print("=== Configuration des credentials Atmo Data ===\n")
credentials_file = "credentials.json"
example_file = "credentials.json.example"
# Vérifier si le fichier existe déjà
if os.path.exists(credentials_file):
response = input(f"Le fichier {credentials_file} existe déjà. Le remplacer ? (y/n): ")
if response.lower() != 'y':
print("Configuration annulée.")
return
# Vérifier la présence du fichier exemple
if not os.path.exists(example_file):
print(f"❌ Fichier exemple {example_file} manquant.")
print("Création du fichier exemple...")
example_content = {
"username": "votre_nom_utilisateur",
"password": "votre_mot_de_passe",
"api_url": "https://api.atmo-data.org"
}
with open(example_file, 'w', encoding='utf-8') as f:
json.dump(example_content, f, indent=2, ensure_ascii=False)
print(f"✅ Fichier {example_file} créé.")
print("\nSaisie des credentials :")
print("(Laissez vide pour utiliser la valeur par défaut)")
# Charger les valeurs par défaut depuis l'exemple
try:
with open(example_file, 'r', encoding='utf-8') as f:
defaults = json.load(f)
except:
defaults = {}
# Saisie interactive
username = input(f"Nom d'utilisateur [{defaults.get('username', '')}]: ").strip()
if not username:
username = defaults.get('username', '')
password = getpass(f"Mot de passe [{defaults.get('password', '')}]: ").strip()
if not password:
password = defaults.get('password', '')
api_url = input(f"URL de l'API [{defaults.get('api_url', 'https://api.atmo-data.org')}]: ").strip()
if not api_url:
api_url = defaults.get('api_url', 'https://api.atmo-data.org')
# Vérifier que les champs obligatoires sont remplis
if not username or not password:
print("❌ Le nom d'utilisateur et le mot de passe sont obligatoires.")
return False
# Créer le fichier credentials
credentials = {
"username": username,
"password": password,
"api_url": api_url
}
try:
with open(credentials_file, 'w', encoding='utf-8') as f:
json.dump(credentials, f, indent=2, ensure_ascii=False)
print(f"\n✅ Fichier {credentials_file} créé avec succès !")
print("\nPour tester votre configuration :")
print(" python test_real_connection.py")
# Vérifier que le fichier est dans .gitignore
gitignore_file = ".gitignore"
if os.path.exists(gitignore_file):
with open(gitignore_file, 'r') as f:
content = f.read()
if credentials_file not in content:
print(f"\n⚠️ Attention: {credentials_file} n'est pas dans .gitignore")
add_to_gitignore = input("Ajouter à .gitignore ? (y/n): ")
if add_to_gitignore.lower() == 'y':
with open(gitignore_file, 'a') as f:
f.write(f"\n# Fichiers de credentials\n{credentials_file}\n")
print("✅ Ajouté à .gitignore")
return True
except Exception as e:
print(f"❌ Erreur lors de la création du fichier: {e}")
return False
def test_configuration():
"""Test rapide de la configuration"""
print("\n=== Test de la configuration ===")
try:
from atmo_data_wrapper import AtmoDataClient
client = AtmoDataClient()
credentials = client._load_credentials()
print("✅ Fichier credentials.json lu avec succès")
print(f" Nom d'utilisateur: {credentials['username']}")
print(f" URL API: {credentials.get('api_url', 'URL par défaut')}")
# Test de connexion (simulation)
print("\nPour tester la connexion réelle à l'API, exécutez :")
print(" python test_real_connection.py")
except Exception as e:
print(f"❌ Erreur lors du test: {e}")
if __name__ == "__main__":
success = setup_credentials()
if success:
test_configuration()