# Foncier narratif - cœur applicatif

Prototype FastAPI + Leaflet + Chart.js pour tester le noyau du futur outil open source :

- carte temporelle du bâti depuis 1970 ;
- bouton **Rejouer depuis 1970** ;
- chiffres clés dynamiques ;
- graphiques de rythme de construction ;
- blocs socio-démo et consommation foncière ;
- connecteurs API publiques préparés sur le périmètre AULA.

## Lancer sous Windows PowerShell

Depuis le dossier `foncier-narratif-core` :

```powershell
python -m venv .venv
.\.venv\Scripts\python.exe -m pip install -r requirements.txt
.\.venv\Scripts\python.exe -m uvicorn app:app --reload
```

Puis ouvrir :

```text
http://127.0.0.1:8000
```

Pour les prochains lancements, il suffit de refaire :

```powershell
cd "C:\chemin\vers\foncier-narratif-core"
.\.venv\Scripts\python.exe -m uvicorn app:app --reload
```

## Lancer sous Linux / macOS

```bash
cd foncier-narratif-core
python -m venv .venv
. .venv/bin/activate
pip install -r requirements.txt
python -m uvicorn app:app --reload
```

## Mode démo

Sans connexion PostgreSQL/PostGIS, l'application utilise :

```text
data/sample_buildings.geojson
```

Le jeu démo contient plusieurs bâtiments, années et usages pour tester :

- l'affichage cumulé ;
- les nouveaux bâtiments de l'année ;
- les compteurs ;
- les graphiques ;
- les blocs socio-démo et consommation.

Les valeurs socio-démo et consommation du mode démo sont fictives. Elles servent uniquement à conserver la structure narrative du POC.

## Mode PostGIS

Copier `.env.example` vers `.env`, puis renseigner les paramètres PostgreSQL.

La vue attendue est :

```text
foncier_poc.v_batiments_historique
```

Contrat minimal :

```text
id_batiment
geom
code_territoire
nom_territoire
annee_apparition
surface_emprise_m2
usage
source_annee
qualite_annee
```

Scripts fournis :

```text
sql/01_views_batiments.sql
sql/02_indexes.sql
```

## Endpoints principaux

```text
GET /
GET /api/health
GET /api/story
GET /api/territoires
GET /api/timeline/stats?territoire=demo
GET /api/batiments?territoire=demo&year=1985&mode=cumul
GET /api/batiments?territoire=demo&year=1985&mode=nouveaux
GET /api/indicators?territoire=demo
```

## Blocs statistiques repris dans l'interface

`/api/indicators` renvoie maintenant :

```text
overview
  bâtiments cumulés
  surface bâtie cumulée
  année de pic

timeline
  évolution annuelle du bâti

decades
  synthèse par décennie

usages
  répartition indicative par usage

sociodemo
  population actuelle
  projection de population
  ménages
  densité
  série observée / projetée

consumption
  consommation totale
  rythme annuel moyen
  consommation récente
  série annuelle

insights
  messages courts d'interprétation
```

## Connecteurs API publiques préparés

Le projet contient une couche `external/` pour préparer les données qui peuvent venir d'API ou de fichiers publics avec cache local :

```text
external/geo_api.py            API Géo : communes, EPCI, rattachement
external/insee_melodi.py       INSEE Melodi : socio-démo, population, recensement
external/artificialisation.py  data.gouv : consommation ENAF
external/sitadel.py            SDES Sitadel : autorisations d'urbanisme
external/georisques.py         Géorisques : risques et PPR par commune
external/cache.py              cache JSON local
```

Périmètre AULA préparé dans :

```text
data/agency_scope.json
```

Endpoints utiles :

```text
GET /api/agency/scope
GET /api/agency/scope?live=true
GET /api/external/sources
GET /api/external/status
GET /api/external/artificialisation/metadata
GET /api/external/sitadel/metadata
GET /api/external/insee/catalog?q=population
GET /api/external/insee/population-reference?epci=246200364
GET /api/external/georisques/commune/62427
```

Rafraîchissement des caches :

```bash
python scripts/refresh_external_cache.py --source geo --force
python scripts/refresh_external_cache.py --source artificialisation --level epci --force
python scripts/refresh_external_cache.py --source sitadel --sitadel-kind logements --force
python scripts/refresh_external_cache.py --source georisques --force
python scripts/refresh_external_cache.py --source insee --force
```

## Prochaine étape technique

La V0 utilise GeoJSON + Leaflet. Pour un volume important de bâtiments, il faudra basculer vers :

```text
PostGIS -> tuiles vectorielles ou PMTiles -> MapLibre
```

Le principe métier reste identique : chaque bâtiment porte une `annee_apparition`, et l'interface filtre l'affichage par année.


## Filtrage commune / EPCI

La V0.5 démarre par défaut au niveau `commune`. Le sélecteur situé dans le header pilote automatiquement toute la page : carte, frise, KPI, graphiques, socio-démo, consommation et sources.

Les endpoints acceptent le paramètre `niveau=commune|epci` :

```text
GET /api/territoires?niveau=commune
GET /api/timeline/stats?niveau=commune&territoire=62001
GET /api/batiments?niveau=commune&territoire=62001&year=1980&mode=cumul
GET /api/indicators?niveau=commune&territoire=62001
```

En PostGIS, la vue `v_batiments_historique` doit donc contenir au minimum `code_insee`, `commune`, `code_territoire` et `nom_territoire` pour permettre le double niveau de filtre.

## Justification des projections

Le bloc socio-démo distingue maintenant population observée et projection. La projection est affichée avec une carte méthodologique. En production, il faut préciser : source, millésime, scénario, horizon, échelle et limites.

Règle proposée :

- population observée : INSEE RP à la commune ;
- projection : OMPHALE uniquement à l'échelle compatible, typiquement EPCI ou zone d'étude de plus de 50 000 habitants ;
- si l'utilisateur filtre une commune, ne pas présenter la valeur comme une projection OMPHALE communale officielle. Afficher soit une projection contextualisée issue de la zone de référence, soit seulement l'observé communal.

## Branchement Cerema / Portail national de l'artificialisation

Le bloc `Consommation ENAF` peut maintenant être alimenté par les données Cerema du Portail national de l'artificialisation.

Deux modes sont prévus :

```text
1. cache CSV data.gouv filtré sur le périmètre AULA ;
2. lecture directe optionnelle d'un territoire si `live=true`.
```

Endpoints utiles :

```text
GET  /api/external/artificialisation/metadata
GET  /api/external/artificialisation/status
GET  /api/external/artificialisation/consumption?territoire=246200364&niveau=epci
GET  /api/external/artificialisation/consumption?territoire=62001&niveau=commune&live=true
GET  /api/indicators?territoire=246200364&niveau=epci
GET  /api/indicators?territoire=62001&niveau=commune&conso_live=true
POST /api/external/artificialisation/refresh?level=epci&force=true
```

Mode de consommation dans l'application :

```text
FONCIER_CONSUMPTION_SOURCE=cache  # cache Cerema puis fallback démo, sans appel live automatique
FONCIER_CONSUMPTION_SOURCE=demo   # force la démo locale
FONCIER_CEREMA_AUTO_LIVE=false    # éviter les appels live automatiques
```

Rafraîchir les caches conseillés :

```powershell
.\.venv\Scripts\python.exe scripts\refresh_external_cache.py --source geo --force
.\.venv\Scripts\python.exe scripts\refresh_external_cache.py --source artificialisation --level epci --force
```

Le cache communal peut être beaucoup plus lourd. À lancer seulement après validation :

```powershell
.\.venv\Scripts\python.exe scripts\refresh_external_cache.py --source artificialisation --level commune --force
```

La consommation ENAF est distinguée de l'emprise bâtie : elle mesure une création ou extension effective d'espaces urbanisés/artificialises, pas uniquement la surface des bâtiments.


## Brancher les données Cerema du portail de l’artificialisation

Le bloc **Consommation foncière** peut désormais lire les données Cerema / Portail national de l’artificialisation.

Commandes utiles sous Windows PowerShell :

```powershell
.\.venv\Scripts\python.exe scripts/refresh_external_cache.py --source geo --force
.\.venv\Scripts\python.exe scripts/refresh_external_cache.py --source artificialisation --level epci --force
.\.venv\Scripts\python.exe scripts/refresh_external_cache.py --source artificialisation --level commune --force
```

Endpoints utiles :

```text
GET /api/external/artificialisation/status
GET /api/external/artificialisation/metadata
GET /api/external/artificialisation/consumption?territoire=62001&niveau=commune
GET /api/external/artificialisation/consumption?territoire=62001&niveau=commune&live=true
GET /api/indicators?territoire=62001&niveau=commune&conso_live=true
```

La documentation détaillée est dans `docs/CEREMA_ARTIFICIALISATION.md`.

## Trame narrative V1

La version actuelle n'est plus un simple tableau de bord : elle suit une trame en 7 étapes.

```text
1. Observer     carte du bâti depuis 1970, rythme annuel, décennies, usages
2. Comprendre   socio-démo, ménages, densité, justification des projections
3. Mesurer      consommation ENAF Cerema / Portail national de l'artificialisation
4. Comparer     comparaison visuelle OCS2D et mutations d'occupation du sol
5. Anticiper    projection Omphale EPCI, besoins logements, pression foncière
6. Agir         densité, renouvellement urbain, friches recensées AULA
7. Prioriser    profils de vigilance et potentiel d'action
```

Chaque chapitre suit la même logique :

```text
question narrative -> chiffres clés -> visualisation -> message d'interprétation -> source / méthode / limites
```

Endpoint de méthode :

```text
GET /api/narrative/methodology
```

## Nouveaux blocs de données à brancher

`/api/indicators` renvoie aussi :

```text
ocs2d
  millésime ancien / récent
  comparaison des grands postes
  mutations NAF -> urbain
  message de méthode

omphale
  projection EPCI
  scénario
  horizon
  estimation de besoin logements
  pression foncière tendancielle

action_levers
  densité récente
  cible indicative
  friches recensées
  potentiel de recyclage
  pistes d'amélioration

priorities
  score expérimental de potentiel d'action
  profils de territoire
  avertissement méthodologique
```

En mode démo, ces blocs utilisent des valeurs fictives. En production, les sources prévues sont :

```text
OCS2D             millésimes régionaux harmonisés
Omphale           fichier interne AULA à l'échelle EPCI
Friches           recensements AULA, datés et qualifiés
Densité           calculs PostGIS sur bâti / foncier consommé / opérations
Priorisation      vues calculées et règles métier validées
```

## Contrat de données futur recommandé

Pour aller vers la V1 opérationnelle, prévoir des vues ou tables :

```text
foncier_poc.v_batiments_historique
foncier_poc.v_ocs2d_millesimes
foncier_poc.v_ocs2d_mutations
foncier_poc.v_friches_recensement
foncier_poc.v_density_levers
foncier_poc.v_omphale_epci
foncier_poc.v_profils_communes
```

Le prototype peut fonctionner sans ces vues, mais c'est la cible à stabiliser pour remplacer les valeurs fictives.

## Option production : BDNB + Fichiers fonciers + 3D

Si les polygones BDNB sont chargés dans PostGIS, le projet peut utiliser ces géométries pour remplacer l'affichage parcellaire issu des Fichiers fonciers.

1. Exécuter d'abord le script Fichiers fonciers :

```sql
sql/10_setup_fichiers_fonciers_pnb10.sql
```

2. Adapter puis exécuter le script BDNB :

```sql
sql/20_setup_bdnb_polygones.sql
```

3. Configurer `.env` :

```env
FONCIER_SCHEMA=foncier_poc
FONCIER_BUILDINGS_VIEW=v_batiments_historique_bdnb_ff
FONCIER_BDNB_VIEW=v_batiments_historique_bdnb_ff
FONCIER_BDNB_HEIGHT_COL=hauteur_m
FONCIER_BDNB_YEAR_COL=annee_apparition
FONCIER_COUNT_COL=
```

4. Ouvrir la page 3D :

```text
http://127.0.0.1:8000/3d?territoire=62001&niveau=commune&year=2025
```

La documentation détaillée est dans :

```text
docs/BRANCHEMENT_BDNB.md
```


## Branchement BDNB + Fichiers fonciers

Pour passer d’une lecture parcellaire FF à une lecture bâtimentaire, le projet contient maintenant :

```text
sql/20_bdnb_croisement_fichiers_fonciers.sql
docs/BRANCHEMENT_BDNB_FF_3D.md
```

Ordre conseillé dans pgAdmin :

```sql
-- 1. Préparer les vues Fichiers fonciers
-- Copier-coller le contenu de sql/10_fichiers_fonciers_pnb10_parcelle.sql

-- 2. Croiser les polygones BDNB avec les parcelles FF
-- Copier-coller le contenu de sql/20_bdnb_croisement_fichiers_fonciers.sql
```

Avec la BDNB, une ligne correspond à un bâtiment. Dans `.env`, laissez donc `FONCIER_COUNT_COL` vide :

```env
FONCIER_SCHEMA=foncier_poc
FONCIER_BUILDINGS_VIEW=v_batiments_historique
FONCIER_GEOM_COL=geom
FONCIER_COUNT_COL=
```

La vue finale expose aussi `hauteur_m`, `altitude_sol_m`, `source_geom`, `fictive_geom_cstr` et `match_ff_quality`, pour préparer un futur affichage 3D avec MapLibre GL JS.


## Table BDNB source

La table bâtimentaire importée dans PostGIS est attendue sous ce nom :

```text
foncier_poc.bdnb
```

Les scripts BDNB / Fichiers fonciers utilisent donc directement `foncier_poc.bdnb` comme table source.

Contrôle rapide dans pgAdmin :

```sql
SELECT
  count(*) AS nb_objets,
  count(geom) AS nb_geometries,
  min(hauteur) AS hauteur_min,
  max(hauteur) AS hauteur_max
FROM foncier_poc.bdnb;
```


## Ordre SQL production PNC

Ne pas exécuter `sql/01_views_batiments.sql` pour la production : ce fichier est un ancien gabarit de démonstration. Il est maintenant neutralisé pour éviter l'erreur `foncier_poc.batiments_source`.

Ordre recommandé dans pgAdmin :

1. `sql/10_setup_fichiers_fonciers_pnb10.sql`
2. `sql/11_bdnb_x_fichiers_fonciers.sql`

Les tables sources attendues sont :

- `foncier_poc.fichier_foncier`
- `foncier_poc.bdnb`

La vue finale à renseigner dans `.env` est :

```env
FONCIER_SCHEMA=foncier_poc
FONCIER_BUILDINGS_VIEW=v_batiments_historique_bdnb_ff
FONCIER_GEOM_COL=geom
FONCIER_COUNT_COL=
```

## Note production PNC - géométrie Fichiers fonciers

Dans `foncier_poc.fichier_foncier`, la géométrie peut s'appeler `geom` ou `geompar` selon le mode d'import.
Les scripts SQL créent maintenant une vue adaptatrice `foncier_poc.v_ff_source_adapted` qui détecte automatiquement la colonne disponible et expose une colonne normalisée `geom`.

À exécuter dans cet ordre :

```sql
-- 1. Fichiers fonciers
-- sql/10_setup_fichiers_fonciers_pnb10.sql

-- 2. BDNB x Fichiers fonciers
-- sql/11_bdnb_x_fichiers_fonciers.sql
```

Ne pas exécuter les anciens scripts génériques de démonstration pour la production.

### Note SQL - anciennes vues

Si PostgreSQL renvoie `ne peut pas supprimer les colonnes d'une vue`, lancez d'abord :

```sql
-- sql/00_reset_generated_objects.sql
```

Puis relancez :

```sql
-- sql/10_setup_fichiers_fonciers_pnb10.sql
-- sql/11_bdnb_x_fichiers_fonciers.sql
```

Les scripts récents intègrent déjà ce nettoyage et ne suppriment pas les tables sources `foncier_poc.fichier_foncier` et `foncier_poc.bdnb`.


## Mise à jour : couleurs par décennie et animation 3D

Cette version colore les bâtiments selon leur décennie de construction dans la carte 2D et dans la vue 3D BDNB. La classe `1970` correspond au stock initial : bâtiments déjà présents au début de la frise.

La vue 3D charge les bâtiments jusqu’à 2025 puis anime leur apparition côté navigateur avec le curseur et le bouton **Animer depuis 1970**. Elle utilise les propriétés renvoyées par l’API :

- `annee_apparition` ;
- `decennie` ;
- `decennie_label` ;
- `hauteur_m`.

Endpoints concernés :

```text
GET /api/batiments?territoire=62498&niveau=commune&year=2025&mode=cumul
GET /api/bdnb/buildings3d?territoire=62498&niveau=commune&year=2025
```

La vue d’application doit toujours exposer `geom_web` en EPSG:4326.

## Socio-démographie INSEE

Le chapitre **Comprendre** peut désormais afficher : population, évolution de population, ménages, densité et taille moyenne des ménages selon le même filtre que la carte (`niveau=commune|epci` et `territoire=<code>`).

Lecture des données, par ordre de priorité :

1. vue PostGIS `foncier_poc.v_sociodemo_insee` si elle existe ;
2. cache JSON `data/cache/insee_sociodemo_<niveau>_<code>.json` ;
3. API Géo pour population/surface actuelle uniquement ;
4. fallback de démonstration explicite.

Endpoint de contrôle :

```text
/api/external/insee/sociodemo?territoire=62498&niveau=commune
```

Voir `docs/SOCIODEMO_INSEE.md`.

## Correctif socio-démo V4

- La section Comprendre affiche uniquement des données observées INSEE RP.
- Les mentions de projection ont été retirées de la carte de sources.
- Les KPI avec unités (`hab./km²`, `pers./ménage`) ont été reformattés pour éviter les débordements en mobile.
- Le KPI Logements 2022 est alimenté par la colonne `housing` de `foncier_poc.v_sociodemo_insee`, elle-même construite depuis `p22_log` dans `foncier_poc.insee`.

Après mise à jour, relancer dans pgAdmin :

```sql
-- sql/30_create_sociodemo_from_insee.sql
```

Puis vérifier :

```sql
SELECT *
FROM foncier_poc.v_sociodemo_insee
WHERE niveau = 'commune'
  AND code_territoire = '62498'
  AND year = 2022;
```

La colonne `housing` doit être remplie pour que la carte Logements 2022 apparaisse dans l'application.


## Lot 3 - Consommation ENAF Cerema

Après import de la table `foncier_poc.conso_cerema`, lancer :

```sql
sql/40_create_conso_cerema_from_table.sql
```

Puis ajouter au `.env` :

```env
FONCIER_CONSUMPTION_VIEW=v_conso_enaf_cerema
FONCIER_CONSUMPTION_USE_CEREMA=true
FONCIER_CONSUMPTION_RECENT_YEARS=5
FONCIER_CONSUMPTION_SOURCE=postgis
```

Test API :

```text
/api/consumption?territoire=62498&niveau=commune
/api/consumption?territoire=200072460&niveau=epci
```


## Lien dynamique BANATIC

La page principale ajoute un lien `En savoir plus` pour les filtres de niveau commune.
Le lien est construit automatiquement à partir du code INSEE et du nom de la commune sélectionnée :

```text
https://www.banatic.interieur.gouv.fr/commune/{code_insee}-{nom_slug}#niveau_vie
```

Le lien est masqué lorsque le niveau EPCI est sélectionné.
