# 001 — HistoMétéo MVP — Spécification Technique

---

## 0) Contract

- **Source of truth** : ce document (`001-histometeo-mvp.tech.md`)
- **Functional integrity** : aucun critère d'acceptation de `001-histometeo-mvp.md` ne peut être modifié, ignoré ou réinterprété
- **Scope** : fichiers et dossiers autorisés à créer/modifier :
  - `public/` — fichiers frontend (HTML, CSS, JS)
  - `src/` — code backend Python
  - `scripts/` — scripts utilitaires
  - `tests/` — tests
  - Fichiers racine : `requirements.txt`, `.env.example`, `Dockerfile`, `.dockerignore`
- **Forbidden changes** :
  - `docs/` — aucune modification des specs existantes
  - `.github/` — aucune modification du workflow agent
  - `README.md` — ne sera mis à jour qu'après implémentation complète
- **Invariants** :
  - INV-1 : Aucune donnée utilisateur n'est stockée — ni côté serveur, ni côté client (pas de base de données, pas de localStorage de recherches)
  - INV-2 : Toutes les heures affichées sont en fuseau horaire `Europe/Paris` (FN6, AC4)
  - INV-3 : La période maximale par requête est de 31 jours (FN3, AC5)
  - INV-4 : Aucune clé API n'est requise — les deux APIs externes sont publiques et gratuites
  - INV-5 : L'interface est intégralement en français (libellés, messages d'erreur, descriptions météo)
  - INV-6 : L'application est une page unique, sans routeur ni navigation multi-pages (FN11)
- **Done when** :
  - Les 11 critères d'acceptation (AC1–AC11) sont vérifiables
  - Les tests unitaires et d'intégration passent
  - L'application se lance via `uvicorn src.main:app` ou `docker compose up`
  - Le cache serveur est fonctionnel et réduit les appels API

---

## 1) Objectif technique

Livrer une application web monolithique légère composée d'un backend Python (FastAPI) servant à la fois les fichiers statiques du frontend et une API proxy avec cache en mémoire pour deux services externes :

1. **Proxy communes** — relayer les requêtes d'auto-complétion vers `geo.api.gouv.fr` avec cache
2. **Proxy météo** — relayer les requêtes vers `Open-Meteo Historical Archive API` avec cache et traduction des codes météo WMO en descriptions françaises

Le tout déployable en auto-hébergement (Docker ou uvicorn direct), avec migration Vercel envisagée ensuite.

---

## 2) Analyse du brief

### Besoins principaux

| Besoin                                   | Complexité | Risque                                       |
| ---------------------------------------- | ---------- | -------------------------------------------- |
| Auto-complétion commune (FN1–FN2)        | Faible     | Faible — proxy API simple                    |
| Sélection période + validation (FN3–FN4) | Faible     | Faible — logique JS date standard            |
| Affichage tableau horaire (FN5–FN6)      | Moyenne    | Faible — rendu tabulaire, formatage timezone |
| Traduction codes WMO (FN5/AC3)           | Faible     | Faible — table statique ~30 entrées          |
| Cache serveur (contrainte performance)   | Moyenne    | Faible — dict Python in-memory avec TTL      |
| Note de transparence (FN7/AC9)           | Faible     | Nul — contenu statique HTML                  |
| Messages d'erreur explicites (FN8/AC8)   | Faible     | Faible — mapping erreurs HTTP                |
| Responsive mobile (FN9/AC10)             | Faible     | Faible — CSS standard + table scroll         |

### Contraintes structurantes

- **Coût nul** → APIs gratuites sans clé, hébergement auto-hébergé
- **Solo dev** → architecture la plus simple possible, zéro sur-ingénierie
- **Page unique** → pas de routeur, un seul fichier HTML
- **Cache obligatoire** → backend nécessaire (pas de full front-end)

### Risques identifiés

1. **Latence API Open-Meteo** pour les requêtes de 31 jours (potentiellement plusieurs centaines de lignes) — mitigé par le cache
2. **Disponibilité APIs gratuites** (pas de SLA) — mitigé par messages d'erreur explicites, pas de retry automatique
3. **Données les plus récentes** — Open-Meteo Historical a un délai de ~5 jours pour les données ERA5 pures ; les jours les plus récents utilisent un modèle de transition (qualité légèrement inférieure)

---

## 3) Design minimal proposé

### 3.1 Architecture globale

```
┌──────────────────────────────────────────────────────┐
│                  Docker / uvicorn                      │
│                                                        │
│  ┌──────────────────────────────────────────────────┐ │
│  │              FastAPI (Python 3.12)                 │ │
│  │                                                    │ │
│  │  GET /                → StaticFiles (public/)      │ │
│  │  GET /api/communes    → Proxy geo.api.gouv.fr      │ │
│  │  GET /api/weather     → Proxy Open-Meteo + WMO     │ │
│  │                                                    │ │
│  │  ┌───────────────┐  ┌───────────────┐            │ │
│  │  │ commune_svc   │  │ weather_svc   │            │ │
│  │  │ (proxy+cache) │  │ (proxy+cache  │            │ │
│  │  │               │  │  +WMO decode) │            │ │
│  │  └───────┬───────┘  └───────┬───────┘            │ │
│  │          │                  │                      │ │
│  │    geo.api.gouv.fr    Open-Meteo Archive           │ │
│  └──────────────────────────────────────────────────┘ │
│                                                        │
│  ┌────────────────┐                                   │
│  │  public/        │  HTML + CSS + JS vanilla          │
│  │  (StaticFiles)  │  Chargé directement par FastAPI   │
│  └────────────────┘                                   │
└──────────────────────────────────────────────────────┘
```

### 3.2 Stack technique

| Couche          | Choix                          | Justification                                                                                                       |
| --------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------- |
| **Backend**     | FastAPI + uvicorn              | Cohérent avec les projets du workspace (RAG_MGI, cv_ia, Strava-Analyzer). Async natif pour les appels API externes. |
| **Frontend**    | HTML + CSS + JS vanilla        | Cohérent avec tous les projets du workspace. Zéro dépendance, chargement instantané.                                |
| **HTTP client** | `httpx` (async)                | Client HTTP async pour FastAPI. Appels non bloquants vers les APIs externes.                                        |
| **Cache**       | Dict Python in-memory avec TTL | Zéro dépendance (pas de Redis). Suffisant pour le volume MVP.                                                       |
| **Python**      | 3.12+                          | Stable, compatible avec toutes les dépendances. Module `zoneinfo` natif.                                            |

### 3.3 Structure des fichiers

```
HistoMeteo/
├── public/
│   ├── index.html              # Page unique
│   ├── style.css               # Styles responsive, mobile-first
│   └── app.js                  # Auto-complétion, validation, appels API, rendu tableau
│
├── src/
│   ├── main.py                 # App FastAPI, routes, montage static
│   ├── config.py               # Constantes (URLs API, limites, TTL cache)
│   ├── commune_service.py      # Proxy geo.api.gouv.fr + cache
│   ├── weather_service.py      # Proxy Open-Meteo + cache + traduction WMO
│   └── cache.py                # Cache TTL générique in-memory
│
├── tests/
│   ├── conftest.py             # Fixtures partagées (client FastAPI, mocks httpx)
│   ├── test_commune_service.py # Tests proxy communes
│   ├── test_weather_service.py # Tests proxy météo + WMO
│   ├── test_cache.py           # Tests cache TTL
│   └── test_api.py             # Tests endpoints HTTP (intégration)
│
├── requirements.txt
├── Dockerfile
└── .dockerignore
```

### 3.4 APIs externes — Contrats d'appel

#### geo.api.gouv.fr — Auto-complétion communes

**Endpoint** : `GET https://geo.api.gouv.fr/communes`

**Paramètres utilisés** :

| Paramètre | Valeur                                         | Rôle                          |
| --------- | ---------------------------------------------- | ----------------------------- |
| `nom`     | saisie utilisateur                             | Recherche par nom             |
| `fields`  | `nom,code,codeDepartement,codesPostaux,centre` | Champs retournés              |
| `boost`   | `population`                                   | Priorise les grandes communes |
| `limit`   | `10`                                           | Maximum de suggestions        |

**Réponse attendue** (tableau JSON) :

```json
[
  {
    "nom": "Paris",
    "code": "75056",
    "codeDepartement": "75",
    "codesPostaux": ["75001", "75002"],
    "centre": {
      "type": "Point",
      "coordinates": [2.347, 48.8589]
    }
  }
]
```

> ⚠️ `centre.coordinates` est en format GeoJSON : `[longitude, latitude]` (inversé par rapport à la convention lat/lon).

#### Open-Meteo Historical Archive — Données météo horaires

**Endpoint** : `GET https://archive-api.open-meteo.com/v1/archive`

**Paramètres utilisés** :

| Paramètre    | Valeur                                                                          | Rôle                                    |
| ------------ | ------------------------------------------------------------------------------- | --------------------------------------- |
| `latitude`   | coordonnée GPS                                                                  | Latitude de la commune                  |
| `longitude`  | coordonnée GPS                                                                  | Longitude de la commune                 |
| `start_date` | `YYYY-MM-DD`                                                                    | Début de période                        |
| `end_date`   | `YYYY-MM-DD`                                                                    | Fin de période                          |
| `hourly`     | `temperature_2m,precipitation,relative_humidity_2m,wind_speed_10m,weather_code` | Variables horaires demandées            |
| `timezone`   | `Europe/Paris`                                                                  | Fuseau horaire des timestamps retournés |

**Réponse attendue** :

```json
{
  "hourly": {
    "time": ["2024-01-15T00:00", "2024-01-15T01:00", "..."],
    "temperature_2m": [2.1, 1.8, "..."],
    "precipitation": [0.0, 0.1, "..."],
    "relative_humidity_2m": [85, 87, "..."],
    "wind_speed_10m": [12.5, 14.2, "..."],
    "weather_code": [3, 61, "..."]
  }
}
```

**Profondeur historique** : données disponibles à partir du **1er janvier 1940** (dataset ERA5). La date de fin la plus récente est **la veille** (les jours les plus récents, ~5 derniers jours, utilisent un modèle de transition ERA5T légèrement moins précis).

### 3.5 API interne — Contrats des endpoints

#### `GET /api/communes?q={search_term}`

**Paramètres** :

| Paramètre | Type   | Requis | Validation           |
| --------- | ------ | ------ | -------------------- |
| `q`       | string | oui    | Minimum 2 caractères |

**Réponse 200** :

```json
[
  {
    "nom": "Paris",
    "departement": "75",
    "latitude": 48.8589,
    "longitude": 2.347
  }
]
```

> Le backend inverse les coordonnées GeoJSON (`[lon, lat]` → `lat`, `lon` séparés) pour que le front-end n'ait pas à le faire.

**Réponse 400** :

```json
{ "error": "Le paramètre de recherche doit contenir au moins 2 caractères." }
```

**Réponse 502** :

```json
{
  "error": "Le service de recherche de communes est temporairement indisponible. Réessayez dans quelques instants."
}
```

#### `GET /api/weather?lat={latitude}&lon={longitude}&start={start_date}&end={end_date}`

**Paramètres** :

| Paramètre | Type   | Requis | Validation                                             |
| --------- | ------ | ------ | ------------------------------------------------------ |
| `lat`     | float  | oui    | -90 ≤ lat ≤ 90                                         |
| `lon`     | float  | oui    | -180 ≤ lon ≤ 180                                       |
| `start`   | string | oui    | Format `YYYY-MM-DD`, ≥ 1940-01-01, ≤ hier              |
| `end`     | string | oui    | Format `YYYY-MM-DD`, ≥ start, ≤ hier, écart ≤ 31 jours |

**Réponse 200** :

```json
{
  "data": [
    {
      "time": "2024-01-15T00:00",
      "temperature": 2.1,
      "precipitation": 0.0,
      "humidity": 85,
      "wind_speed": 12.5,
      "description": "Couvert"
    }
  ]
}
```

- `time` : timestamp ISO en fuseau `Europe/Paris` (tel que retourné par Open-Meteo)
- `temperature` : °C (1 décimale)
- `precipitation` : mm (1 décimale)
- `humidity` : % (entier)
- `wind_speed` : km/h (1 décimale)
- `description` : texte français traduit du code WMO

**Réponse 400** — message contextuel selon le problème :

```json
{ "error": "La période est limitée à 31 jours maximum." }
```

```json
{ "error": "Seules les dates passées sont disponibles." }
```

```json
{ "error": "La date de début ne peut pas être antérieure au 1er janvier 1940." }
```

**Réponse 502** :

```json
{
  "error": "Le service de données météo est temporairement indisponible. Réessayez dans quelques instants."
}
```

### 3.6 Traduction des codes météo WMO

Table de correspondance côté backend (`weather_service.py`). Dictionnaire Python statique.

| Code WMO | Description française     |
| -------- | ------------------------- |
| 0        | Ciel dégagé               |
| 1        | Principalement dégagé     |
| 2        | Partiellement nuageux     |
| 3        | Couvert                   |
| 45       | Brouillard                |
| 48       | Brouillard givrant        |
| 51       | Bruine légère             |
| 53       | Bruine modérée            |
| 55       | Bruine forte              |
| 56       | Bruine verglaçante légère |
| 57       | Bruine verglaçante forte  |
| 61       | Pluie légère              |
| 63       | Pluie modérée             |
| 65       | Pluie forte               |
| 66       | Pluie verglaçante légère  |
| 67       | Pluie verglaçante forte   |
| 71       | Chute de neige légère     |
| 73       | Chute de neige modérée    |
| 75       | Chute de neige forte      |
| 77       | Grains de neige           |
| 80       | Averses légères           |
| 81       | Averses modérées          |
| 82       | Averses violentes         |
| 85       | Averses de neige légères  |
| 86       | Averses de neige fortes   |
| 95       | Orage                     |
| 96       | Orage avec grêle légère   |
| 99       | Orage avec grêle forte    |

Tout code non listé → `"Conditions non disponibles"`.

### 3.7 Cache en mémoire

**Module `cache.py`** : implémentation d'un cache dictionnaire thread-safe avec TTL.

| Domaine  | Clé de cache                                | TTL     | Taille max  |
| -------- | ------------------------------------------- | ------- | ----------- |
| Communes | `communes:{q}` (q en lowercase)             | 24h     | 500 entrées |
| Météo    | `weather:{lat:.2f}:{lon:.2f}:{start}:{end}` | 7 jours | 500 entrées |

**Stratégie d'invalidation** : TTL-based uniquement. Les données météo historiques sont immuables (sauf les ~5 derniers jours, qualité ERA5T vs ERA5 — impact négligeable). Quand le cache atteint la taille max, les entrées les plus anciennes sont supprimées (FIFO).

**Justification du TTL météo long (7 jours)** : les données historiques ne changent pas. Un utilisateur qui recherche la météo du 14 juillet 2023 obtiendra toujours la même réponse. Le seul cas de variation est sur les jours très récents (transition ERA5T → ERA5), ce qui est marginal.

### 3.8 Frontend — Comportement détaillé

#### Structure HTML

```
<body>
  <header>          → Titre + sous-titre de l'application
  <main>
    <section#search>   → Formulaire : champ commune + sélecteur dates + bouton
    <section#results>   → Tableau des résultats (masqué initialement)
    <section#info>      → Note de transparence (réanalyse)
  </main>
  <footer>          → Crédits (APIs utilisées)
</body>
```

#### Composants interactifs

1. **Champ auto-complétion commune**
   - Type `<input type="text">` avec dropdown custom (pas de `<datalist>` — contrôle insuffisant)
   - Seuil de déclenchement : **2 caractères minimum** (décision OD5)
   - Debounce : **300ms** (décision OD5)
   - Affichage : `{nom} ({codeDepartement})` — ex: "Paris (75)"
   - Navigation clavier : flèches haut/bas + Entrée pour sélectionner

2. **Sélecteur de dates**
   - Deux `<input type="date">` : début et fin
   - Désactivés tant qu'aucune commune n'est sélectionnée
   - Attribut `min` : `1940-01-01`
   - Attribut `max` : date d'hier (calculée dynamiquement en JS)
   - Validation JS côté client avant envoi : écart ≤ 31 jours, date fin ≥ date début
   - Messages d'erreur affichés sous les champs

3. **Bouton Rechercher**
   - Désactivé tant que commune + dates ne sont pas toutes valides
   - Déclenche `GET /api/weather`
   - Pendant le chargement : bouton désactivé, spinner ou texte "Chargement…"

4. **Tableau des résultats**
   - Masqué initialement
   - En-têtes : `Date/Heure | Température (°C) | Précipitations (mm) | Humidité (%) | Vent (km/h) | Conditions`
   - Formatage de la colonne date/heure côté JS : `"Lun. 15/01/2024 14h00"` via `Intl.DateTimeFormat('fr-FR')` ou formatage manuel
   - Sur mobile : conteneur scrollable horizontalement (`overflow-x: auto`)

5. **Note de transparence**
   - Toujours visible (pas dans un tooltip) — AC9
   - Texte statique dans le HTML
   - Contenu indicatif : _"Les données présentées proviennent d'une reconstitution météorologique par modèle numérique (réanalyse ERA5), et non de mesures directes d'une station locale. La précision peut varier, en particulier pour les phénomènes très localisés (orages, microclimats)."_

#### Gestion des erreurs côté front

Le front-end affiche les messages d'erreur retournés par le backend dans une zone dédiée (au-dessus du tableau). Pas de retry automatique — l'utilisateur relance manuellement (décision QA6).

---

## 4) Plan d'implémentation

### Étape 1 — Squelette backend + cache

**Fichiers** : `src/main.py`, `src/config.py`, `src/cache.py`, `requirements.txt`

- Créer l'application FastAPI avec montage de `public/` en static files
- Implémenter le module `cache.py` (dict TTL, FIFO eviction)
- Définir les constantes dans `config.py` (URLs API, TTL, limites)
- Fichier `requirements.txt` : `fastapi`, `uvicorn[standard]`, `httpx`

**Testable** : `uvicorn src.main:app` lance le serveur et sert `public/index.html`

### Étape 2 — Proxy communes

**Fichiers** : `src/commune_service.py`, route dans `src/main.py`

- Implémenter le service proxy vers `geo.api.gouv.fr`
- Transformation de la réponse : extraction `nom`, `codeDepartement`, inversion coordonnées GeoJSON
- Câblage du cache (clé = query normalisée, TTL 24h)
- Validation du paramètre `q` (≥ 2 caractères)

**Testable** : `GET /api/communes?q=paris` retourne les communes correspondantes

### Étape 3 — Proxy météo + WMO

**Fichiers** : `src/weather_service.py`, route dans `src/main.py`

- Implémenter le service proxy vers Open-Meteo Archive
- Transformation de la réponse : tableau d'objets horaires avec description WMO en français
- Câblage du cache (clé = lat/lon/start/end, TTL 7 jours)
- Validation des paramètres (dates, écart ≤ 31j, pas de futur, ≥ 1940)

**Testable** : `GET /api/weather?lat=48.86&lon=2.35&start=2024-01-01&end=2024-01-03` retourne le tableau horaire

### Étape 4 — Frontend : page HTML + CSS responsive

**Fichiers** : `public/index.html`, `public/style.css`

- Créer la structure HTML : header, formulaire, zone résultats, note de transparence, footer
- Implémenter le CSS responsive mobile-first (breakpoint 768px)
- Table scrollable horizontalement sur mobile
- États visuels : loading, erreur, résultats

**Testable** : la page s'affiche correctement sur desktop et mobile (largeur 360px)

### Étape 5 — Frontend : logique JS

**Fichiers** : `public/app.js`

- Auto-complétion avec debounce 300ms et dropdown
- Gestion d'état (commune sélectionnée → activer dates → activer bouton)
- Validation dates côté client
- Appel API météo + rendu du tableau
- Formatage date/heure en français
- Affichage des erreurs API

**Testable** : le flow complet fonctionne — recherche commune → sélection → dates → résultats

### Étape 6 — Tests

**Fichiers** : `tests/conftest.py`, `tests/test_cache.py`, `tests/test_commune_service.py`, `tests/test_weather_service.py`, `tests/test_api.py`

- Tests unitaires du cache (TTL, eviction, thread-safety)
- Tests unitaires des services (mocks httpx)
- Tests d'intégration des endpoints (TestClient FastAPI)

**Testable** : `pytest` passe au vert

### Étape 7 — Docker

**Fichiers** : `Dockerfile`, `.dockerignore`

- Image Python 3.12-slim
- `COPY` requirements + `pip install` + `COPY` sources
- `CMD uvicorn src.main:app --host 0.0.0.0 --port 8000`

**Testable** : `docker build -t histometeo . && docker run -p 8000:8000 histometeo` lance l'application

---

## 5) Guide pour le Développeur

### Pièges fréquents

- **Coordonnées GeoJSON inversées** : `geo.api.gouv.fr` retourne `centre.coordinates` en `[longitude, latitude]`. Ne pas confondre avec `[lat, lon]`. Le backend doit inverser avant de renvoyer au front.
- **Fuseau horaire** : passer `timezone=Europe/Paris` à Open-Meteo. Les timestamps retournés seront déjà en heure française — ne pas appliquer de conversion supplémentaire.
- **Code WMO `null`** : Open-Meteo peut retourner `null` pour `weather_code` sur certaines heures anciennes. Gérer ce cas → `"Conditions non disponibles"`.
- **Debounce** : ne pas oublier d'annuler le timer précédent à chaque frappe. Sans cela, les requêtes s'accumulent.

### Zones de dérive à éviter

- **Ne pas ajouter de persistence** (fichier, SQLite, localStorage des recherches). Le MVP est sans état.
- **Ne pas implémenter de graphiques** — hors scope explicite.
- **Ne pas ajouter de retry automatique** sur les appels API. Si l'API est down, message d'erreur + l'utilisateur relance.
- **Ne pas créer de système de composants** côté front. Vanilla JS, rendu direct dans le DOM.
- **Ne pas ajouter de gestion de favoris** ou d'historique de recherche.

### Simplifications autorisées

- Le cache in-memory est perdu au redémarrage du serveur → acceptable pour le MVP.
- Pas de minification CSS/JS → volume négligeable pour une SPA simple.
- Pas de service worker ni de mode offline.
- Le debounce peut être implémenté avec un simple `setTimeout`/`clearTimeout`, pas besoin de librairie.

### Décisions explicitement interdites

- **Interdiction d'introduire un framework JS** (React, Vue, Svelte…).
- **Interdiction d'ajouter une base de données** (SQLite, Redis…).
- **Interdiction de stocker des données utilisateur** (cookies de tracking, analytics tiers).
- **Interdiction de modifier le fuseau horaire d'affichage** — c'est `Europe/Paris` pour toutes les communes, y compris DOM-TOM (AC4 fait foi).

---

## 6) Stratégie de tests

### Tests unitaires

| Fichier                   | Cible                | Cas testés                                                                                                                            |
| ------------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `test_cache.py`           | `cache.py`           | Insertion/lecture, expiration TTL, eviction FIFO à taille max, clé inexistante retourne `None`                                        |
| `test_commune_service.py` | `commune_service.py` | Transformation correcte de la réponse geo.api (inversion coordonnées), gestion réponse vide, gestion erreur HTTP distante             |
| `test_weather_service.py` | `weather_service.py` | Transformation correcte de la réponse Open-Meteo, traduction WMO complète, gestion `weather_code: null`, gestion erreur HTTP distante |

### Tests d'intégration

| Fichier       | Cible          | Cas testés                                                            |
| ------------- | -------------- | --------------------------------------------------------------------- |
| `test_api.py` | Endpoints HTTP | `GET /api/communes?q=par` → 200 + structure correcte                  |
|               |                | `GET /api/communes?q=a` → 400 (trop court)                            |
|               |                | `GET /api/communes` (sans q) → 400                                    |
|               |                | `GET /api/weather` avec paramètres valides → 200 + structure correcte |
|               |                | `GET /api/weather` période > 31 jours → 400                           |
|               |                | `GET /api/weather` date future → 400                                  |
|               |                | `GET /api/weather` date < 1940-01-01 → 400                            |
|               |                | `GET /api/weather` date fin < date début → 400                        |

### Edge cases critiques

- Requête commune avec caractères accentués (`"saint-étienne"`, `"île-de-france"`)
- Période d'exactement 31 jours (limite inclusive)
- Commune d'outre-mer (coordonnées hors France métropolitaine)
- `weather_code` null dans la réponse Open-Meteo
- Réponse Open-Meteo avec tableaux de longueurs différentes (données manquantes)

---

## 7) Risques techniques

| #   | Risque                                   | Probabilité | Impact                                                              | Mitigation                                                                                                                                                                                           |
| --- | ---------------------------------------- | ----------- | ------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 1   | **API Open-Meteo indisponible ou lente** | Faible      | Fort — l'application est inutilisable sans données météo            | Cache agressif (TTL 7 jours). Message d'erreur explicite. Timeout httpx à 15s.                                                                                                                       |
| 2   | **Mémoire du cache non bornée**          | Moyenne     | Moyen — consommation mémoire croissante en production               | Taille max 500 entrées par domaine. Eviction FIFO. Monitoring via log au démarrage.                                                                                                                  |
| 3   | **Données très récentes indisponibles**  | Certaine    | Faible — les ~5 derniers jours peuvent être absents ou moins précis | La date max du sélecteur est "hier". Si l'API retourne une erreur pour des dates récentes, le message d'erreur sera clair. Pas de promise sur la fraîcheur des données dans la note de transparence. |

---

## Annexe — Décisions architecturales (réponses aux questions ouvertes)

| Question                                   | Décision                                                                     | Justification                                                                                                                                            |
| ------------------------------------------ | ---------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **OD1** — Mécanisme de cache               | Dict Python in-memory, TTL-based, FIFO eviction, 500 entrées max par domaine | Zéro dépendance. Suffisant pour le volume MVP. Perdu au redémarrage = acceptable.                                                                        |
| **OD2** — Backend ou front-end pur         | Backend Python FastAPI                                                       | Le cache serveur est une contrainte explicite. Le backend permet aussi de masquer les détails des APIs externes (inversion coordonnées, traduction WMO). |
| **OD3** — Traduction WMO                   | Table de correspondance côté backend (dict Python)                           | Centralisé, testable unitairement, pas de logique de traduction dans le front.                                                                           |
| **OD4** — DOM-TOM et fuseau horaire        | `Europe/Paris` pour tous                                                     | FN6 et AC4 sont explicites. Application cohérente même si non optimale pour les DOM-TOM.                                                                 |
| **OD5** — Seuil auto-complétion + debounce | 2 caractères minimum, 300ms debounce                                         | Standard UX éprouvé. 2 caractères = bon compromis pertinence/réactivité.                                                                                 |
| **QA1** — Backend léger avec cache         | Oui, FastAPI + cache in-memory                                               | Cf. OD2.                                                                                                                                                 |
| **QA2** — Traduction WMO                   | Côté backend                                                                 | Cf. OD3.                                                                                                                                                 |
| **QA3** — Profondeur historique            | 1940-01-01 codé en dur                                                       | ERA5 couvre 1940→présent. Pas d'API de détection dynamique. Si Open-Meteo étend la couverture, mise à jour manuelle dans `config.py`.                    |
| **QA4** — Format date/heure                | `"Lun. 15/01/2024 14h00"`                                                    | Formatage côté front via `Intl.DateTimeFormat('fr-FR')`. Lisible, compact, français.                                                                     |
| **QA5** — DOM-TOM                          | Europe/Paris                                                                 | Cf. OD4.                                                                                                                                                 |
| **QA6** — Retry automatique                | Non                                                                          | Simplicité MVP. L'utilisateur relance manuellement. Pas de logique de retry/backoff.                                                                     |
| **QA7** — Structure projet                 | Mono-repo. Dockerfile fourni.                                                | Cohérent avec la structure existante du repo. Auto-hébergement via Docker ou `uvicorn` direct.                                                           |
