---
suivi: 1148
date: 2026-08-03
sujet: Diagnostic — zonages Pinel / social vs communes (faisabilité import matrice FI)
chantier: module-foncier
type: diagnostic
statut: poussé
hash: 75c82b7e
fichiers:
  - tools/diag/diag_zonages_1148.php
  - docs/suivi/SUIVI_1148_diag_zonages.md
---

## PROMPT ENVOYÉ

SUIVI #1148 — PHASE 1 DIAGNOSTIC (NE PAS CODER métier). Source matrice FI trouvée
(onglets ZonagePinel 5 524 / ZonageSocial 6 541, sans code INSEE). Mesurer l’existant
en base (~34 875 communes), le socle #1122, la consommation, les homonymes (point
bloquant), la normalisation de noms. Script tools/diag commit + push. Aucune migration.

## SYNTHÈSE

### Fiches lues (consultation-suivi)

- `SUIVI_1122_foncier_import_zonages_pinel_social.md` — socle import déjà poussé
  (colonnes sur `communes`, service INSEE-only, UI Admin, data.gouv Pinel). Change
  l’approche : #1148 n’invente pas de schéma, il mesure le **trou** entre source
  matrice (nom seul) et import #1122 (INSEE obligatoire).
- Aucune autre fiche pertinente sur homonymes / match par nom.

---

### Q1 — Table `communes` en prod

**Schéma (code / migrations #1073 + #1079 + #1082) — colonnes réelles :**

| Colonne | Rôle |
|---------|------|
| `code_insee` (PK string 5) | Clé administrative |
| `nom` | Libellé officiel |
| `nom_ascii` | Recherche accent-insensible (#1079) |
| `code_postal`, `departement` | Localisation |
| `code_epci`, `nom_epci` | EPCI |
| `zonage_pinel` (nullable string 32) | Zone ABC Pinel — **déjà prévue** |
| `zonage_social` (nullable string 32) | Zonage social — **déjà prévue** |
| `date_import`, timestamps | Traçabilité référentiel |

**Volumes / remplissage :** coller la sortie `php tools/diag/diag_zonages_1148.php`
section Q1 (nb_communes, nb_zonage_pinel_renseigne, nb_zonage_social_renseigne,
distributions). Attendu si jamais importé : pinel≈0, social≈0, communes≈34 875.

Aucune autre colonne de type `zone_a` / `zone_b1` hors `zonage_pinel` / `zonage_social`.
Le `zonage_plu` vit sur **`parcelles`**, pas sur `communes` (saisie manuelle PLU).

---

### Q2 — Socle zonage déjà présent (#1122)

Oui — socle **complet côté code**, pas une table dédiée :

| Élément | Présent |
|---------|---------|
| Colonnes `communes.zonage_pinel` / `zonage_social` | Oui (#1073) |
| Modèle `App\Models\Commune` | Oui |
| `CommunesZonageImportService` | Oui — upsert **par code_insee uniquement** |
| Import fichier CSV/XLSX Admin | Oui — **exige** colonne INSEE |
| Import Pinel data.gouv (ABC) | Oui — WEB/PHP-FPM uniquement |
| `ReferentielCommunes.vue` + routes | Oui |
| Tests Feature import | Oui |
| Table `zonages*` séparée | **Non** |

**Écart bloquant pour la matrice FI :** les onglets ZonagePinel / ZonageSocial n’ont
pas de code INSEE. `CommunesZonageImportService::mapperColonnes()` lève
`Colonne code INSEE introuvable` → **impossible d’ingérer la matrice telle quelle**
avec le code #1122.

---

### Q3 — Consommation aujourd’hui

| Consommateur | Forme | Défaut si manquant |
|--------------|-------|--------------------|
| Admin Référentiel communes | Compteurs + import | 0 / NULL |
| `CommuneSearchService` | Filtres query + suffixe label `· Pinel X · Social Y` | Zones absentes du label (pas de valeur inventée) |
| `CommuneSelect` | Affiche le `label` enrichi si fourni | Idem |
| Fiche / formulaire parcelle | `zonage_plu` (PLU) | NULL / vide — **autre métier** |
| Scénario faisabilité / grille / bilan | **Aucune** lecture `zonage_pinel`\|`social` | N/A |

Conclusion : Pinel/social sont un **enrichissement référentiel + recherche**, pas
encore un input de calcul métier. Remplir les colonnes est sans régression calcul.

---

### Q4 — Homonymes (faisabilité import par NOM) — POINT DÉCISION

Le script mesure en prod :

1. Nombre de **noms exacts** avec ≥ 2 `code_insee` distincts
2. Nombre de **lignes** concernées
3. **Top 20** avec exemples INSEE / département / CP
4. Même mesure sur `nom_ascii` (après normalisation)

**Règle :** les homonymes sont COMPTÉS ET RAPPORTÉS — jamais ignorés.

**Verdict structurel (indépendant du run prod) :** un match **strict par nom seul**
est **ambigu** dès qu’il existe au moins un homonyme (ex. connus en France :
Sainte-Colombe, Saint-Rémy, Beaumont…). Coller Q4 du script pour le chiffre exact
sur le référentiel geo.api actuel.

**Conséquence :** l’import matrice FI ne peut pas être « silent match by name » ;
il faut une stratégie à deux niveaux (voir recommandation).

---

### Q5 — Normalisation de nom

Existant : `App\Support\Foncier\CommuneNomNormalizer` (+ colonne `nom_ascii`).

| Traitement | Oui / Non |
|------------|-----------|
| Accents → ASCII | Oui (`transliterator` / `iconv`) |
| Lowercase | Oui |
| Conservation espaces / tirets | Oui |
| Suppression apostrophes / ponctuation | Oui (strip `[^a-z0-9\s\-]`) |
| SAINT ↔ ST / SAINTE ↔ STE | **Non** |
| Articles LE / LA / LES | **Non** (conservés tels quels) |

Exemples réels : coller Q5 du script (échantillons accent / Saint- / St- /
article / tiret / apostrophe + paires Saint↔St détectées).

---

## RECOMMANDATION (structure cible + rapprochement)

### Structure cible

**Garder les colonnes sur `communes`** (`zonage_pinel`, `zonage_social`) — déjà
migrées, déjà branchées Admin + recherche. **Ne pas** créer de table dédiée pour
ce lot : double source de vérité inutile.

### Stratégie d’import (lot suivant, hors #1148)

1. **Pinel (prioritaire) :** source ouverte data.gouv ABC **par INSEE** via le bouton
   déjà livré (#1122) — couvre ~34 875 communes (Abis/A/B1/B2/C), pas seulement les
   5 524 de la matrice historique. La matrice Pinel reste une **vérification**, pas
   la source primaire.
2. **Social :** matrice FI obligatoire (pas de jeu ouvert fiable commune→zone).
3. **Rapprochement social (et contrôle Pinel matrice) :**
   - **Niveau A — match unique** sur `nom` exact **ou** `nom_ascii` exact → 1 INSEE
     → appliquer.
   - **Niveau B — ambigu** (≥ 2 INSEE pour le nom) → **rapport d’exceptions**
     (nom, liste INSEE/dép/CP, zone proposée) — **aucune écriture** automatique.
   - **Niveau C — 0 match** → rapport « inconnus » (orthographe / fusions / Saint↔St).
4. **Ne pas** étendre silencieusement `CommunesZonageImportService` pour accepter un
   fichier sans INSEE sans rapport d’ambiguïtés.
5. Option renforcée (si le fichier Excel a un département / CP dans une autre
   colonne non encore audité) : matcher `(nom_ascii, departement)` pour réduire
   les B — à confirmer en ouvrant les onglets (PHASE 2 lecture fichier, pas ici).

### Ce que #1148 ne fait pas

Aucune migration, aucune écriture, aucun import, aucun build front.

## DÉPLOIEMENT-TEST

```bash
git pull origin main
# pas de migrate, pas de journal:sync, pas de npm
php tools/diag/diag_zonages_1148.php
```

Coller Q1–Q5 dans le ticket. Vérifier surtout Q4 (homonymes) avant tout lot d’import.

## LEÇON

- #1122 a livré le **tuyau INSEE** et les colonnes ; la matrice FI sans INSEE est un
  **autre problème de rapprochement**, pas un manque de schéma.
- « Import par nom » sans compter les homonymes = corruption silencieuse du
  référentiel (même classe d’erreur que les scripts diag non commités #1121/#1128).
- `zonage_plu` (parcelle) ≠ `zonage_pinel`/`zonage_social` (commune) : ne pas fusionner.

## CORRECTION (2026-08-03) — invalidée par #1149

**Erreur de diagnostic :** la conclusion « les onglets ZonagePinel / ZonageSocial n’ont
pas de code INSEE » et la recommandation de rapprochement par NOM (niveaux A/B/C) ont
été rédigées **sans ouvrir le fichier source**. C’est faux.

**Réalité vérifiée (exports Robin / `MATRICE_ETUDE_FI_MAI_2025.xlsx`) :**
- ZonagePinel : colonnes `CODGEO` | `LIBCOM_2014` | zonage A/B/C — **5 523** lignes utiles
- ZonageSocial : `Code Géographique` | libellé | zonage I/II/III (`02`/`03`) — **6 540** lignes
- Les CSV livrés sont déjà normalisés : `code_insee;libelle_source;zonage`

**Conséquence :** abandon total de la stratégie par nom. Lot #1149 = rapprochement
**uniquement par code INSEE** (chaîne 5 car., zéro de tête significatif), réutilisation
de `CommunesZonageImportService` (exigence INSEE conservée). Les mesures Q4/Q5
(homonymes / normalisation de noms) restent informatives pour d’autres imports, mais
**ne s’appliquent pas** à l’ingestion de ces deux onglets.
)
