Liste des mairies sur data.gouv.fr 

## Processus

1. **Extraction des mairies** (`annuaire.py`) : lecture du fichier brut `input/2026-06-22_053126-data.gouv_local.json` (annuaire service-public.gouv.fr, 86 212 services). Filtrage sur les entrées dont le champ `pivot[].type_service_local == "mairie"` et dont le `siren` est renseigné. Cas particulier de Paris, Lyon et Marseille : chaque arrondissement y est une entrée distincte partageant le SIREN de la ville ; une seule ligne est conservée par ville, avec le code INSEE de la commune entière (75056, 69123, 13055) plutôt qu'un code d'arrondissement. Résultat : 23 920 mairies.

2. **Jointure avec data.gouv.fr** (`join.py`) : croisement avec l'export des organisations data.gouv.fr (`input/export-organization-20260622-061308.csv`) sur la clé `siren` (annuaire) = 9 premiers caractères du `business_number_id` (SIRET, export). 839 correspondances trouvées. 18 SIREN correspondent à plusieurs organisations (doublons de lignes dans l'export).

***

## Fichiers produits

### `data/annuaire_mairies.csv`

Toutes les mairies avec SIREN extraites de l'annuaire service-public.

| Colonne              | Description               |
| :------------------- | :------------------------ |
| `nom`                | Nom de la mairie          |
| `siren`              | Numéro SIREN (9 chiffres) |
| `code_insee_commune` | Code INSEE de la commune  |

### `data/annuaire_mairies_join.csv`

Mairies présentes à la fois dans l'annuaire service-public et sur data.gouv.fr.

| Colonne              | Description                                |
| :------------------- | :----------------------------------------- |
| `id`                 | Identifiant de l'organisation data.gouv.fr |
| `name`               | Nom de l'organisation data.gouv.fr         |
| `annuaire_nom`       | Nom dans l'annuaire service-public         |
| `siren`              | Numéro SIREN                               |
| `code_insee_commune` | Code INSEE de la commune                   |
| `metric.datasets`    | Nombre de jeux de données publiés          |

### `data/annuaire_mairies_join_duplicates.csv`

Même schéma que `annuaire_mairies_join.csv`. Contient uniquement les lignes pour lesquelles un même SIREN correspond à plusieurs organisations dans l'export data.gouv.fr.

###

## Code

### Extrait de l'annuaire des services publics

A partir de la base locale de l'annuaire des services publics en JSON, produit un CSV `nom,siren,code_insee_commune` :

```
import csv
import json

INPUT_FILE = "input/2026-06-22_053126-data.gouv_local.json"
OUTPUT_FILE = "data/annuaire_mairies.csv"
FIELDNAMES = ["nom", "siren", "code_insee_commune"]

# Paris, Lyon, Marseille each expose one service per arrondissement, all
# sharing the city's siren. Collapse those down to the whole-city code.
PLM_CODES = {"75056", "69123", "13055"}


def is_mairie(service: dict) -> bool:
    return any(
        p.get("type_service_local") == "mairie"
        for p in service.get("pivot", [])
    )


def get_code_insee(service: dict) -> str:
    for p in service.get("pivot", []):
        if p.get("type_service_local") == "mairie":
            codes = p.get("code_insee_commune", [])
            return codes[0] if codes else ""
    return ""


def main():
    print(f"Loading {INPUT_FILE}...")
    with open(INPUT_FILE, encoding="utf-8") as f:
        data = json.load(f)

    services = data.get("service", [])
    print(f"{len(services)} services total")

    entries_by_siren = {}
    for svc in services:
        if not is_mairie(svc):
            continue
        siren = svc.get("siren", "").strip()
        if not siren:
            continue
        entries_by_siren.setdefault(siren, []).append(
            (svc.get("nom", ""), get_code_insee(svc))
        )

    count = 0
    with open(OUTPUT_FILE, "w", newline="", encoding="utf-8") as f:
        writer = csv.DictWriter(f, fieldnames=FIELDNAMES)
        writer.writeheader()
        for siren, entries in entries_by_siren.items():
            plm_code = next((c for _, c in entries if c in PLM_CODES), None)
            if plm_code:
                nom = next(n for n, c in entries if c == plm_code)
                writer.writerow({
                    "nom": nom,
                    "siren": siren,
                    "code_insee_commune": plm_code,
                })
                count += 1
            else:
                for nom, code in entries:
                    writer.writerow({
                        "nom": nom,
                        "siren": siren,
                        "code_insee_commune": code,
                    })
                    count += 1

    print(f"Done. {count} mairies with siren written to {OUTPUT_FILE}")


if __name__ == "__main__":
    main()
```

### Jointure annuaire des services publics X data.gouv.fr

A partir du CSV extrait de l'annuaire plus haut et du **catalogue des ressources de data.gouv.fr**, créé les fichiers de jointure et des doublons :

```
import csv
import os

ANNUAIRE_FILE = "data/annuaire_mairies.csv"
EXPORT_FILE = "input/export-organization-20260622-061308.csv"
OUTPUT_FILE = "data/annuaire_mairies_join.csv"
DUPLICATES_FILE = "data/annuaire_mairies_join_duplicates.csv"


def main():
    # Index annuaire by siren
    annuaire: dict[str, dict] = {}
    with open(ANNUAIRE_FILE, newline="", encoding="utf-8") as f:
        for row in csv.DictReader(f):
            annuaire[row["siren"]] = row

    print(f"Annuaire: {len(annuaire)} mairies")

    os.makedirs("data", exist_ok=True)

    matched = 0
    siren_hits: dict[str, list[str]] = {}
    rows_by_siren: dict[str, list[dict]] = {}

    with open(EXPORT_FILE, newline="", encoding="utf-8") as fin, \
         open(OUTPUT_FILE, "w", newline="", encoding="utf-8") as fout:

        reader = csv.DictReader(fin, delimiter=";")
        fieldnames = ["id", "name", "annuaire_nom", "siren", "code_insee_commune", "metric.datasets"]
        writer = csv.DictWriter(fout, fieldnames=fieldnames)
        writer.writeheader()

        for row in reader:
            siren = (row.get("business_number_id") or "").strip()[:9]
            if siren in annuaire:
                siren_hits.setdefault(siren, []).append(row.get("business_number_id", "").strip())
                out_row = {
                    "id": row["id"],
                    "name": row["name"],
                    "annuaire_nom": annuaire[siren]["nom"],
                    "siren": siren,
                    "code_insee_commune": annuaire[siren]["code_insee_commune"],
                    "metric.datasets": row.get("metric.datasets", ""),
                }
                rows_by_siren.setdefault(siren, []).append(out_row)
                writer.writerow(out_row)
                matched += 1

    duplicates = {siren: rows for siren, rows in rows_by_siren.items() if len(rows) > 1}
    if duplicates:
        print(f"WARNING: {len(duplicates)} siren(s) matched multiple orgs")
        with open(DUPLICATES_FILE, "w", newline="", encoding="utf-8") as fdup:
            writer_dup = csv.DictWriter(fdup, fieldnames=["id", "name", "annuaire_nom", "siren", "code_insee_commune", "metric.datasets"])
            writer_dup.writeheader()
            for rows in duplicates.values():
                writer_dup.writerows(rows)
        print(f"Duplicates written to {DUPLICATES_FILE}")

    print(f"Done. {matched} rows written to {OUTPUT_FILE}")


if __name__ == "__main__":
    main()
```