Application Android de comparaison multi-modèles météorologiques (AROME, ARPEGE, ICON, GFS, HRRR, ECMWF, UKMO, AIFS, GEM, MET Nordic, HARMONIE KNMI/DMI, ICON-CH2, ACCESS, GRAPES…) basée sur l’API Open-Meteo.
L’app se concentre sur les données brutes et l’incertitude : au lieu d’agréger silencieusement les modèles en une seule prévision, elle expose les désaccords entre modèles pour que l’utilisateur puisse juger lui-même du niveau de confiance à accorder à la prévision.
Depuis la v1.0, l’app suit aussi le biais historique de chaque modèle sur chaque ville favorite — comparaison des prévisions J+1 passées à une référence de réanalyse historique Open-Meteo. Cette référence combine observations assimilées et modélisation : elle ne doit pas être confondue avec une station au point exact. Les écarts systématiques sont signalés d’une pastille discrète dans les tableaux, sans jamais modifier la donnée brute.
WeatherConditionConsensus consolide les codes WMO par grandes familles météorologiques (précipitation / non-précipitation, puis ciel/brouillard et liquide/neige/verglas/orage) avant de choisir la condition précise. La branche ciel utilise la nébulosité centrale V3 pour éviter de sur-représenter « Couvert ».⋮ lorsqu’une localité est éligible au mode côtier, puis une icône 🌊 près du nom uniquement lorsque l’option est réellement activée. La page Détails affiche alors vagues, houle, température de mer et marées estimées. La décision d’éligibilité est mise en cache 6 h avec revalidation ; les données restent indicatives et ne sont pas destinées à la navigation.app/src/main/java/com/meteocompare/app/
├── di/ ← Modules Hilt (Network, Repository, Dispatchers)
├── core/
│ ├── locale/ ← LocaleUtils / applyPersistedLocale — SharedPreferences dédié, source unique app + widgets
│ └── network/ ← ApiResult, NetworkMonitor, error mapping
├── data/
│ ├── remote/ ← Interfaces Retrofit + DTOs + BatchedForecastSplitter
│ │ (+ PreviousRunsApi pour le bootstrap J+1 du biais)
│ ├── mapper/ ← DTO ↔ domain
│ ├── local/ ← Room (ForecastCache, ClimateNormals, ForecastSample, ObservationSample)
│ ├── preferences/ ← DataStore
│ ├── repository/ ← Implémentations (dont le coalescing des fetches concurrents)
│ └── worker/ ← BiasRefreshWorker + Scheduler (WorkManager, fenêtre 24h)
├── domain/
│ ├── model/ ← Modèles métier (City, ForecastSeries, WeatherModel, ModelFamily,
│ │ Coverage, DayNormals, HourlyConfidenceBand, WeatherCondition,
│ │ BiasSample, ModelBias, BiasVariable, BiasSignificance…)
│ ├── repository/ ← Interfaces (dont BiasSampleRepository)
│ └── usecase/ ← ConfidenceCalculator, ComputeBiasUseCase,
│ FetchBiasObservationsUseCase, BootstrapBiasHistoryUseCase,
│ weighting strategies
├── ui/
│ ├── citylist/ ← Accueil : cartes favorites compactes, scénarios repliables, heatmap 12 h
│ ├── citydetail/ ← Détail d'une ville : cartes, chart, tableaux
│ │ └── confidence/ ← Écran "Pourquoi cette confiance ?"
│ ├── enginecomparison/ ← Comparaison des quatre moteurs V3
│ ├── settings/ ← Paramètres (modèles, moteur V3, thème, langue)
│ ├── components/ ← Composables réutilisables (WeatherIcon, WindArrow, ShimmerBox…)
│ ├── accessibility/ ← Formatage des descriptions TalkBack
│ ├── theme/ ← Couleurs, typographie, tokens M3
│ └── navigation/ ← Routes et NavHost
└── widget/ ← MeteoWidget (Glance), config activity, splitter loader
Listés dans WeatherModel.kt avec leur résolution native (km), leur horizon, leur zone de couverture, et leur institution source (ModelFamily).
| Modèle | Résolution | Couverture | Horizon | Institution | Par défaut |
|---|---|---|---|---|---|
| AROME France HD | 1.5 km | France | 2 j (48 h) | Météo-France | ✓ |
| AROME France | 2.5 km | France | 2 j (48 h) | Météo-France | |
| ARPEGE Europe | 11 km | Europe | 4 j | Météo-France | ✓ |
| ARPEGE World | 25 km | Global | 4 j | Météo-France | |
| ICON-EU | 7 km | Europe | 5 j | DWD (Allemagne) | ✓ |
| ICON | 11 km | Global | 7,5 j | DWD | |
| ICON-D2 | 2 km | Europe centrale | 2 j | DWD | |
| GFS | 13 km | Global | 16 j | NOAA (USA) | ✓ |
| ECMWF | 25 km | Global | 15 j | ECMWF (UE) | ✓ |
| ECMWF AIFS | 28 km | Global (IA) | 15 j | ECMWF | ✓ |
| UKMO Global | 10 km | Global | 7 j | UK Met Office | ✓ |
| GEM Global | 15 km | Global | 10 j | ECCC (Canada) | |
| HRRR | 3 km | USA continental | 18 h standard (48 h sur 00/06/12/18Z) | NOAA | |
| MET Nordic | 1 km | Scandinavie | 2,5 j | MET Norway | |
| HARMONIE KNMI | 5.5 km | Europe | 2,5 j | KNMI (Pays-Bas) | |
| HARMONIE DMI | 2 km | Europe | 2,5 j | DMI (Danemark) | |
| ICON-CH2 | 2 km | Suisse / Europe centrale | 5 j | MeteoSwiss | |
| BOM ACCESS | 15 km | Global | 10 j | Bureau of Meteorology (Australie) | |
| CMA GRAPES | 15 km | Global | 10 j | China Meteorological Administration |
Les modèles marqués “Par défaut” sont activés dès la première ouverture ; les autres sont activables dans les Settings, désormais triables par zone, par famille ou par finesse (résolution native).
À propos du groupe « France » : il désigne les domaines natifs Météo-France AROME. Il ne représente pas tous les modèles utilisables sur le territoire français. ARPEGE Europe, ICON-EU, HARMONIE DMI/KNMI et, selon la position, ICON-D2 ou ICON-CH2 peuvent aussi couvrir une ville française mais restent classés dans « Europe ». Les variantes AROME 15 minutes d’Open-Meteo ne sont pas ajoutées comme modèles indépendants : elles utilisent la même lignée AROME et leur horizon n’est que de quelques heures.
Diversité éditoriale du catalogue :
Autres modèles régionaux pouvant intéresser la France : Open-Meteo expose également MeteoSwiss ICON-CH1 (1 km), CHMI ALADIN Central Europe (~2,3 km), GeoSphere AROME Austria (2,5 km) et ItaliaMeteo ICON-2I (2 km). Leur domaine est plus régional : ils ne sont pas activés dans MeteoCompare 1.9.0 afin d’éviter d’ajouter des modèles souvent hors couverture selon la ville.
AROME France HD conserve un jeu de champs natifs plus réduit que certains autres modèles. La documentation Open-Meteo définit weather_code comme une variable dérivée pour AROME/ARPEGE, mais le client tolère également les réponses opérationnelles où weather_code ou cloud_cover total sont absents pour AROME HD.
Le client utilise donc en priorité le weather_code fourni par l’API. S’il manque, il infère d’abord pluie/neige depuis les précipitations et la température du même modèle. Pour les situations sèches où cloud_cover total manque aussi, les couches cloud_cover_low, cloud_cover_mid et cloud_cover_high servent à construire un indicateur de nébulosité de secours (maximum des couches) uniquement pour choisir l’icône clair/nuageux/couvert. Cette icône reste visuellement marquée comme inférée. Les données d’un modèle voisin ne sont jamais copiées pour « compléter » une cellule.
Les badges “%” restent conditionnés à la présence réelle de leur variable : une probabilité de précipitation absente n’est jamais transformée en 0 %, et une couverture nuageuse absente n’est jamais inventée.
ConfidenceCalculator agrège les prédictions multi-modèles en un score 0-100 par variable pour chaque jour ET pour chaque heure.
Algorithme :
ForecastConsensus déduplique les variantes apparentées par lignée numérique et leur partage une masse de vote, afin d’éviter qu’une famille disposant de plusieurs variantes ne domine artificiellement le résultat.PRECIPITATION / NON_PRECIPITATION, puis SKY / FOG ou LIQUID / FROZEN / FREEZING_RAIN / THUNDERSTORM, puis la feuille WMO précise. À chaque niveau, les variantes de modèles restent équilibrées par lignée. Cette structure évite aussi bien la fragmentation CLEAR / MAINLY_CLEAR / PARTLY_CLOUDY / OVERCAST que des cas DRIZZLE / RAIN_SHOWERS / RAIN ou SNOW_SHOWERS / SNOW.SKY gagne, la feuille affichée est dérivée de la nébulosité centrale V3 avec des seuils conservateurs : <20 % clair, <45 % plutôt clair, <85 % partiellement nuageux, puis couvert. Sans nébulosité exploitable, le consensus descend jusqu’aux feuilles WMO du ciel.ModelWeightingStrategy reste un point d’extension injectable, mais la production utilise des poids égaux. La résolution de grille n’est pas utilisée comme proxy de qualité ; une pondération différente ne serait légitime qu’après un backtest vérifié.val daily = calculator.dayConfidence(forecast, LocalDate.now())
val hourlyTemp: List<HourlyConfidenceBand> = calculator.hourlyTemperatureConfidence(forecast)
val hourlyPrecip: List<HourlyConfidenceBand> = calculator.hourlyPrecipitationConfidence(forecast)
val hourlyWind: List<HourlyConfidenceBand> = calculator.hourlyWindConfidence(forecast)
val currentCondition: WeatherCondition? = calculator.currentWeatherCondition(forecast)
val currentCloudCover: Int? = calculator.currentCloudCover(forecast)
val matrix: List<DayConditionsRow> = calculator.dailyConditionsByModel(forecast)
DayConditionsRow.extrasByModel porte les métadonnées par cellule (probabilité de pluie max journalière, couverture nuageuse moyenne journalière) qui alimentent les badges “%” sous les icônes.
La TodaySummaryCard conserve un résumé immédiatement lisible mais détaille chaque variable dans quatre mini-cartes de même hauteur. La valeur centrale provient du moteur V3 sélectionné (Multi-consensus, Calibration, Scénarios ou Adaptatif), tandis que la plage, le spread et les indicateurs de convergence restent calculés exclusivement sur les sorties brutes des modèles.
Bande de confiance multi-métriques : le composant ConfidenceBandSection encapsule un sélecteur segmenté à 3 états (Température / Précipitations / Vent) au-dessus d’un graphe unique. Les 3 séries de bandes sont pré-calculées dans le ViewModel — la transition entre métriques est instantanée. La bande température superpose les repères ERA5 Tmax/Tmin sur 10 ans, chargés depuis l’API archive d’Open-Meteo et cachés 180 jours dans Room. Les graphes pluie/vent n’affichent pas de repère journalier sur une série horaire afin d’éviter de comparer des fenêtres temporelles différentes. Le graphique est zoomable au pincement sur l’axe temps (pinch à 2 doigts + pan) et réinitialisable au double-tap.
Les seuils actuels sont des heuristiques de présentation, pas une calibration scientifique ni une probabilité de justesse. Une future calibration prédictive devrait s’appuyer sur un corpus de vérification par variable, zone et échéance.
Un clic sur le badge de confiance (en haut à droite de la carte “Aujourd’hui”) ouvre une explication détaillée qui compose l’edge éditorial de l’app :
L’app évalue en continu la précision de chaque modèle sur chaque ville favorite en croisant :
_previous_day1) ; le bootstrap manuel tente jusqu’à 21 jours, le cycle quotidien ne recharge qu’une courte fenêtre pour rester idempotent et peu coûteuxBiasRefreshWorker depuis l’Historical Weather API d’Open-Meteo et stockée dans Room. C’est une réanalyse / donnée historique modélisée et assimilée, pas une observation de station au point exactPour chaque paire (modèle, variable), ComputeBiasUseCase calcule sur une fenêtre glissante 30 jours :
Trois états visuels dans le tableau prévisions, footprint vertical identique pour préserver l’alignement des noms de modèle entre colonnes :
| État | Rendu | Sémantique |
|---|---|---|
| Biais significatif | Chip coloré rouge/bleu + flèche + valeur signée | Le modèle sur/sous-estime — utile de le corriger mentalement |
| Biais faible | Chip gris neutre + coche + petite valeur signée | Le biais moyen signé est faible sur la fenêtre observée ; cela ne garantit pas la fiabilité globale du modèle |
| En attente | Pastille vide avec dash | < 14 jours de recouvrement, pas assez de données |
Clic sur un chip (les deux premiers états) ouvre une sheet avec sparkline 30 jours superposant la prévision et l’observation, une grille de stats et un texte contextuel adapté à l’état. Pour un biais signé faible, la sheet reste volontairement prudente : elle ne transforme pas l’absence de biais moyen en garantie de précision générale.
Coalescing des fetches : le ForecastRepositoryImpl dédoublonne les requêtes HTTPS concurrentes pour la même (city, models, forecastDays) via un registre Deferred sur un SupervisorJob du repo. Quand CityList, CityDetail et le widget cold-start-refreshent Paris en parallèle, une seule requête part réellement.
Widget Glance redimensionnable en 4 tailles :
Les modes confidence rendent une heatmap horizontale colorée par la confiance sur 7 jours, avec sous chaque cellule la valeur agrégée et le jour de la semaine. C’est le rendu widget de la bande de confiance de l’écran détail.
Localisation widget : l’écran de config et le rendu du widget utilisent tous deux le helper applyPersistedLocale (dans core/locale/) pour respecter la préférence de langue de l’app, indépendamment de la locale système.
Depuis la refonte réseau, l’app fait 1 seule requête HTTPS pour récupérer les N modèles activés, au lieu de N requêtes parallèles. OpenMeteoApi.getForecastBatched demande ?models=meteofrance_arome_france_hd,meteofrance_arpege_europe,ncep_gfs_seamless,… et Open-Meteo répond avec les variables suffixées par la clé du modèle (temperature_2m_meteofrance_arome_france_hd, temperature_2m_meteofrance_arpege_europe, …).
Le BatchedForecastSplitter décompose la réponse en un ForecastResponseDto par modèle, transparent pour le reste de la chaîne (mapper et cache Room inchangés — chaque modèle a toujours sa propre ligne cache). Un log DEBUG dédié MeteoCompare/Net permet de vérifier l’invariant en développement via Logcat.
Le batching réduit surtout le nombre de connexions, handshakes TLS et réveils radio. Aucun gain chiffré n’est annoncé sans benchmark reproductible.
Aucune clé API n’est nécessaire — Open-Meteo est gratuit pour usage non commercial.
./gradlew testDebugUnitTest # tests JVM rapides
./gradlew connectedDebugAndroidTest # UI, navigation, Room et DataStore sur appareil
./gradlew lintDebug assembleDebug # analyse statique + compilation APK
La suite instrumentée utilise des repositories Hilt factices : aucune requête
Open-Meteo n’est effectuée pendant androidTest. Elle couvre les parcours de
navigation, les états Compose, l’accessibilité, la configuration widget, les
DAO Room en mémoire, DataStore et la locale persistée.
La stratégie complète, les règles de stabilité et les commandes Windows sont
documentées dans TESTING.md.
Toutes les zones interactives ont des contentDescription lisibles par TalkBack :
A11yFormatter qui résument les données clés. La bande de confiance annonce en plus son état de zoom (“Graphique zoomé, double-tap pour réinitialiser”) pour rester compréhensible quand l’utilisateur zoome sans voir l’écran.heading() pour permettre la navigation par titre.Le module ui/accessibility/A11yFormatter.kt centralise les chaînes pour garder une terminologie cohérente.
Le fichier PRIVACY.md à la racine est conforme aux exigences Play Store : zéro collecte de données, déclaration explicite des permissions, des services tiers (Open-Meteo) et du stockage local.
À héberger sur GitHub Pages ou un Gist public, puis fournir l’URL dans Play Console.
L’app est gratuite et open-source. Plusieurs options pour soutenir :
Aucun privilège n’est accordé aux donateurs — l’app et le code source restent identiques pour tous. Voir DONATIONS.md pour plus de détails.
Fait :
Apache License 2.0 — vous pouvez utiliser, modifier et redistribuer le code librement, à condition de conserver la notice de copyright.
Les données météo sont fournies par Open-Meteo (également open-source, AGPL-3.0). Les modèles eux-mêmes sont produits par leurs organismes respectifs : Météo-France (AROME, ARPEGE), DWD (ICON, ICON-D2), NOAA (GFS, HRRR), ECMWF (IFS et AIFS), UK Met Office (UKMO), Environnement et Changement climatique Canada (GEM), MET Norway (MET Nordic), KNMI et DMI (HARMONIE), MeteoSwiss (ICON-CH2), Bureau of Meteorology Australie (ACCESS), China Meteorological Administration (GRAPES).