# HistoMétéo

Application web mono-page pour consulter la météo passée heure par heure pour une commune française.

## Fonctionnalités MVP

- Recherche de commune avec auto-complétion (API geo.api.gouv.fr)
- Validation de période (date min 1940-01-01, date max hier, limite 31 jours)
- Périodes prédéfinies (Hier, 3, 7, 15, 30 jours)
- Récupération météo horaire (Open-Meteo Historical Archive)
- Traduction des codes météo WMO en descriptions françaises + icônes emoji
- Résumé journalier calculé côté backend (temp. min/max, pluie cumulée, humidité moyenne, vent moyen, condition dominante)
- Normales climatiques 1991-2020 (Open-Meteo Archive) avec anomalies observé vs normale
- Bloc d'informations commune enrichi (population, superficie, département, région, altitude)
- Graphique météo (courbe température + barres précipitations)
- Mode comparaison de deux communes (résumé comparatif, graphique multi-villes, détails séparés)
- Bloc question SEO dynamique selon la recherche
- H1 dynamique en résultats + `document.title` mis à jour (ville + période)
- Format de dates compact en français dans les textes SEO (question, résumé, meta)
- Résumé automatique de période (températures, pluie, condition dominante)
- Contexte saisonnier progressif (si normales disponibles)
- Synthèse textuelle des anomalies climatiques
- Navigation interne sticky vers Résumé, Graphique et Détail horaire
- Onglet actif de navigation mis en évidence selon la section visible
- Liens de décalage de période (jour précédent / jour suivant)
- Comparaison enrichie (delta numérique + mise en évidence de la valeur dominante)
- Bouton Tout déplier / replier par ville en mode comparaison
- Bloc "Climat habituel à {ville} en {mois}" (normales mensuelles)
- URL propre partageable en recherche simple (`/meteo/{slug}-{dept}/{start}/{end}`)
- URL propre partageable en comparaison (`/comparaison/{slug1}-{dept1}/vs/{slug2}-{dept2}/{start}/{end}`)
- Redirection 301 automatique des anciennes URLs query params vers les URLs propres
- Balise canonique `<link rel="canonical">` mise à jour dynamiquement
- Bloc de partage social en mode simple (copie du lien, Facebook, X, WhatsApp, LinkedIn)
- Texte de partage pré-rempli depuis les agrégats météo de la période
- Balises Open Graph et Twitter Card dynamiques injectées côté serveur pour les routes `/meteo/...`
- Génération d'image Open Graph dynamique (`/api/og-image/{slug_dept}/{start}/{end}`)
- Détail horaire regroupé par jour via sections dépliables
- Affichage responsive (desktop/mobile)
- Design system v6 (logo, favicon, palette bleu/vert, typographie Inter, sections en cards)
- Meta description SEO dynamique selon la recherche
- Accessibilité renforcée sur la navigation de période (`aria-label` dynamiques)
- Cache serveur en mémoire avec TTL et éviction FIFO
- Aucune persistance utilisateur

## Prérequis

- Python 3.13+
- pip
- Optionnel : Docker

## Installation locale

1. Se placer à la racine du projet.
2. Installer les dépendances.

```bash
pip install -r requirements.txt
```

La police utilisée pour le rendu des images OG est versionnée dans le projet :

- `src/assets/fonts/Inter-SemiBold.ttf`

## Configuration

Variables d'environnement optionnelles (fichier `.env.example`) :

- `HTTP_TIMEOUT_SECONDS` (défaut : `15`)
- `HISTOMETEO_ENV` (défaut : `dev`) : environnement de tracking (`dev`, `beta`, `prod`)
- `ADMIN_USERNAME` (défaut : `admin`) : login HTTP Basic pour l'admin
- `ADMIN_PASSWORD` (défaut vide) : mot de passe HTTP Basic admin. Si vide, l'admin est désactivée (routes `/admin/*` en 503)

Exemple:

```bash
set HTTP_TIMEOUT_SECONDS=15
set HISTOMETEO_ENV=beta
set ADMIN_USERNAME=admin
set ADMIN_PASSWORD=change-me
```

## Tracking et administration

- La base de tracking SQLite est créée automatiquement au démarrage : `data/tracking.db`
- Les recherches utilisateur (API + SEO) sont journalisées dans `search_logs`
- Les appels HTTP externes réellement exécutés sont journalisés dans `api_call_logs`
- `api_call_logs.search_id` peut être `NULL` pour les appels hors contexte de recherche (routes SPA, OG image, crawlers)
- Chaque entrée `api_call_logs` contient un `service` : `weather`, `normals` ou `communes`
- Les cache hits ne créent pas d'entrée dans `api_call_logs` (comptage quota fidèle)
- Les caches in-memory exposent un snapshot admin avec date de création, expiration et nombre de réutilisations
- L'interface admin est accessible sur `http://localhost:8000/admin/` via HTTP Basic
- Toutes les routes admin retournent le header `X-Robots-Tag: noindex`

### Interface admin

La page admin propose 4 vues :

- `Recherches récentes` : liste paginée, triable, filtrable
- `Détail` : détails d'une recherche + appels API associés (onglet visible après sélection d'une recherche)
- `Synthèse` : KPI journaliers (recherches, appels API totaux + ventilation `weather`/`normals`/`communes`, répartition `attribués`/`non-attribués`, hit ratio cache, taux d'erreur)
- `Cache` : snapshot des caches `weather`, `normals`, `communes_search`, `communes_slug`, `og_image` (clé, mise en cache, expiration, TTL restant, réutilisations)

Dans `Recherches récentes`, un badge `cache` est affiché quand `status = success` et `total_api_calls = 0`.

## Lancer l'application

```bash
uvicorn src.main:app --host 0.0.0.0 --port 8000
```

Puis ouvrir :

- `http://localhost:8000`

## Lancer les tests

```bash
pytest -q
```

## Utilisation

1. Saisir au moins 2 caractères dans le champ commune.
2. Sélectionner une suggestion.
3. Saisir une date de début et une date de fin en format `JJ/MM/AAAA` (formats acceptés: `JJ/MM/AAAA`, `JJ-MM-AAAA`, `AAAA-MM-JJ`), ou utiliser un bouton de période prédéfinie.
4. Optionnel: utiliser l'icône calendrier pour ouvrir le sélecteur natif du navigateur.
5. Optionnel: cliquer sur `Comparer avec une autre ville`, sélectionner la seconde commune, puis lancer la recherche.
6. Cliquer sur `Voir la météo`.
7. Consulter le résumé journalier, le graphique météo, puis le détail horaire regroupé par jour.
8. En mode simple, consulter le bloc `Anomalie climatique` pour comparer la période observée aux normales 1991-2020.
9. Consulter le bloc `Informations sur la commune` (population, superficie, densité, altitude selon disponibilité).
10. Utiliser la navigation interne sticky pour accéder rapidement aux sections de résultats.
11. Utiliser les boutons de période précédente/suivante pour décaler la plage de recherche d'un jour.
12. En mode simple, utiliser le bouton `Tout déplier` ou `Tout replier` pour naviguer rapidement entre les jours.
13. En mode comparaison, chaque bloc ville dispose de son propre bouton `Tout déplier` / `Tout replier`.

## Design System

La version actuelle applique une charte visuelle centrée sur l'identité HistoMétéo :

- Logo principal : `public/assets/logo-histometeo.png`
- Favicon : `public/assets/favicon.png`
- Palette principale : bleu météo (`#2A7DAF`) et vert météo (`#6DBE45`)
- Typographie : Inter (chargée via Google Fonts avec `display=swap`)
- Structure de contenu en cards via `.panel`

## URL partageable

Chaque recherche réussie met à jour l'URL pour permettre le partage direct du résultat via des routes propres.

Format recherche simple :

`/meteo/<slug-commune>-<dept>/<YYYY-MM-DD>/<YYYY-MM-DD>`

Exemple :

`http://localhost:8000/meteo/paris-75/2026-03-02/2026-03-05`

Format comparaison :

`/comparaison/<slug1>-<dept1>/vs/<slug2>-<dept2>/<YYYY-MM-DD>/<YYYY-MM-DD>`

Exemple :

`http://localhost:8000/comparaison/paris-75/vs/lyon-69/2026-03-02/2026-03-05`

Les anciennes URLs en query params restent supportées en entrée et sont redirigées en HTTP 301 vers les routes propres.

## Endpoints API

### `GET /api/communes?q=par`

- 200 : liste de communes normalisées (`nom`, `departement`, `latitude`, `longitude`)
- 400 : requête invalide (ex : moins de 2 caractères)
- 502 : indisponibilité temporaire du service externe

### `GET /api/weather?lat=48.86&lon=2.35&start=2024-01-01&end=2024-01-03`

- 200 : objet JSON avec :
  - `data`: données horaires normalisées (`time`, `temperature`, `precipitation`, `humidity`, `wind_speed`, `icon`, `description`)
  - `daily_summary`: résumé par jour (`date`, `temp_min`, `temp_max`, `precipitation_sum`, `humidity_avg`, `wind_speed_avg`, `icon`, `description`)
- 400 : paramètres invalides (période, bornes date, format)
- 502 : indisponibilité temporaire du service externe

Paramètres optionnels supplémentaires (tracking admin) :

- `commune`
- `slug`
- `search_id` (UUID v4 canonique)

### `GET /api/normals?lat=48.86&lon=2.35&start=2024-03-05&end=2024-03-11`

- 200 : normales climatiques pour la période avec :
  - `elevation`
  - `reference_period`
  - `period_normals` (`temp_avg`, `temp_max_avg`, `temp_min_avg`, `precipitation_daily_avg`, `precipitation_total`)
  - `daily_normals`
  - `month_normals` (`month`, `month_name`, `temp_avg`, `temp_max_avg`, `temp_min_avg`, `precipitation_total`)
- 400 : paramètres invalides
- 502 : indisponibilité temporaire du service de normales climatiques

Paramètre optionnel :

- `search_id` (UUID v4 canonique ; une valeur invalide est ignorée)

### `GET /api/normals/annual?lat=48.85&lon=2.35`

- 200 : normales annuelles par mois
- 400 : paramètres invalides
- 502 : indisponibilité temporaire du service de normales climatiques

Paramètre optionnel :

- `search_id` (UUID v4 canonique ; une valeur invalide est ignorée)

### `GET /api/resolve/{slug_avec_dept}`

- 200 : commune résolue (`nom`, `departement`, `latitude`, `longitude`, `slug`, `population`, `surface_km2`, `departement_nom`, `region_nom`)
- 404 : slug inconnu
- 502 : indisponibilité temporaire du service externe

### `GET /api/og-image/{slug_dept}/{start}/{end}`

- 200 : image PNG Open Graph (1200x630)
- 422 : date invalide (validation FastAPI)
- Header de cache : `Cache-Control: public, max-age=86400`

### `GET /admin/`

- 200 : page HTML admin (authentification HTTP Basic requise)
- 401 : credentials manquants ou invalides
- 503 : admin non configuré (`ADMIN_PASSWORD` vide)

### `GET /admin/admin.js`

- 200 : script JS de l'interface admin

### `GET /admin/api/searches`

Paramètres : `limit`, `offset`, `status`, `environment`, `commune`, `sort`, `order`

- 200 : liste paginée de recherches

### `GET /admin/api/searches/{id}`

- 200 : détail d'une recherche et appels API associés
- 404 : recherche introuvable

### `GET /admin/api/dashboard`

Paramètre : `date=YYYY-MM-DD` (optionnel, défaut = aujourd'hui UTC)

- 200 : indicateurs de synthèse journaliers

### `GET /admin/api/cache`

- 200 : état des 5 caches in-memory (`entries`, `total`, `max_entries`, `ttl_seconds`)

## Docker

Build:

```bash
docker build -t histometeo .
```

Run:

```bash
docker run --rm -p 8000:8000 histometeo
```

## Structure projet

- `public/`: frontend (HTML/CSS/JS)
- `src/`: backend FastAPI + services + cache
- `tests/`: tests unitaires et intégration
- `requirements.txt`: dépendances Python
- `pyproject.toml`: configuration pytest (`asyncio_mode = auto`)
- `Dockerfile`, `.dockerignore`: conteneurisation
