Ce document décrit le comportement que geocoder-core reproduit : celui de
l'API Adresse, servie depuis 2024 par le géocodeur de la Géoplateforme (IGN,
data.geopf.fr/geocodage, vers lequel api-adresse.data.gouv.fr redirige).
- Configuration :
indexes/address/config/addok.confdu dépôtgeoplateforme/geocodage/geocodeur(miroirgouv-nc-data/gpf-geocodeur). - Versions : addok 1.3 + addok-fr 1.1.0 + addok-france 1.2.0. Le dépôt épingle encore addok 1.1.1 / addok-fr 1.0.1 / addok-france 1.1.3 (2023), mais l'API se comporte comme les versions de fin 2025 : filtres à plusieurs valeurs (addok 1.3), libellés pour les anciennes communes fusionnées (addok-france 1.2), phonétique d'addok-fr 1.1 (vérifié par sondage : des requêtes que seule la 1.1 rapproche sont trouvées).
Chaque point ci-dessous a été vérifié contre le code Python lui-même
(scripts/addok_oracle.py, scripts/diff_pipeline.py).
Attention aux sources :
BaseAdresseNationale/addok-docker(addok 1.0.3) et la configuration par défaut d'addok diffèrent de la production sur plusieurs points (phonétique, arrêt anticipé de l'autocomplete, ordre des collecteurs, champs,MIN_SCORE…).
La requête est d'abord repliée en ASCII (ascii : minuscules, unidecode
1.3.6, tout caractère non-mot → espace, espaces multiples fusionnés, bords
retirés), puis :
check_query_length: 200 caractères maximum ;extract_address: si un motif « numéro [ordinal] type de voie … » est trouvé, on ne garde que la partie qui commence au numéro (« XYZ Ets bâtiment B 22 rue des Fleurs… » → « 22 rue des Fleurs… ») ;clean_query: codes postaux isolés, BP/CS/TSA/CIDEX, cedex (on ne garde alors que le département du code postal), étages,lieu-diten tête ;remove_leading_zeros: « 0003 » → « 3 » (1 à 3 chiffres seulement).
tokenize: suites de caractères de mot au sens Python (str.isalnum()ou_) ;normalize:unidecode(minuscules);glue_ordinal: « 6 bis » → « 6bis » si suivi d'un type de voie ou de rien (jamais un nombre de 5 chiffres, qui est un code postal) ;fold_ordinal: « 6bis » → « 6b » (bis→b, ter→t, quater→q, quinquies→c, sexies→s) ;flag_housenumber: le premier nombre (\d{1,4}[a-z]?) en tête ou juste avant un type de voie devient le numéro ;synonymize: remplacement (jamais ajout) selonsynonyms.txtd'addok-fr, éventuellement en plusieurs mots (« cr » → « chemin rural »). Les nombres sont aussi concernés (« 1 » → « un », « 10 » → « dix ») ;phonemicize: règles d'addok-fr 1.1.0 (« rue » → « ru », « boulevard » → « boulevar », « champs » → « chan », « vingt » → « vin », « georges » → « jorj », « lyon » → « lion »).
Les types de voie reconnus par extract_address et flag_housenumber sont
ceux d'addok-france 1.2.0, abréviations comprises (« ch », « chp », « rdpt »…).
Les mots de numéro ne sont pas cherchés dans l'index : ils servent à sélectionner le numéro dans le document (clé = mots du numéro concaténés).
Champs indexés et poids (FIELDS + EXTRA_FIELDS) :
| Champ | Poids |
|---|---|
name (obligatoire) |
4 |
postcode |
1,2 pour une commune, 1 sinon |
street, city, context, citycode |
1 |
(oldcity, oldcitycode et district, indexés par addok-docker, ne le sont
pas à la Géoplateforme.)
Pour chaque valeur d'un champ découpée en n mots, chaque mot reçoit
(poids + importance × 0,1) / n, et un mot garde le meilleur poids vu dans
le document. Ce poids ne sert qu'à choisir et ordonner les candidats
(intersection des mots, scores sommés, 100 premiers), pas au score final.
S'ajoutent : paires de mots co-présents (fuzzy, autocomplete, relations),
préfixes d'autocomplete de 3 à 20 caractères de tous les mots, numériques
compris (l'import passe par addok batch sur l'entrée standard, qui les
calcule ; « 268 » complète donc le code postal « 26800 »), géohash à 7
caractères du document et de ses numéros, filtres source, type,
citycode, postcode (plus type=housenumber pour les voies numérotées).
Les mots sont classés en fréquents (> 10 000 documents), significatifs
(trouvés), introuvables ou numéro. Puis les 13 collecteurs d'addok 1.1
s'enchaînent (voir src/search/mod.rs), dans cet ordre : intersection des
mots significatifs (plus un mot fréquent s'il n'y en a qu'un), réduction par
les mots fréquents tant qu'il y a plus de 100 candidats, autocomplete sur le
dernier mot (candidats triés par meilleur document pour une requête d'un mot,
par fréquence sinon), voisins fuzzy à une faute (substitutions limitées aux
touches voisines d'un clavier AZERTY, validés par les paires), groupes de
mots liés (les moins fréquents d'abord), puis retrait d'un, voire de deux
mots. Les filtres acceptent jusqu'à 10 valeurs, combinées en OU. Un collecteur peut arrêter
la chaîne, notamment dès qu'un candidat a un score textuel ≥ 0,9 parmi au
plus 10.
SEARCH_RESULT_PROCESSORS :
match_housenumber: si le numéro existe, le résultat prend ses coordonnées et son id, et son type devienthousenumber; avec le filtretype=housenumber, un résultat sans numéro trouvé est écarté ;make_labels(addok-france 1.2.0) : toutes les combinaisons « [numéro] nom [code postal] [commune] » (et « code postal nom » pour une commune), pour chaque commune de la liste : une adresse d'une commune nouvelle se trouve aussi sous le nom de l'ancienne commune ;score_by_importance:importance × 0,1, plafond 0,1 ;score_by_autocomplete_distance(autocomplete actif, défaut de l'API) : sur les libellés repliés en ASCII, 1,0 si égal à la requête, 0,9 s'il commence par elle, 0,7 s'il la contient, sur tous les libellés (addok 1.0 s'arrêtait au premier ≥ 0,9) ; sinon repli sur les n-grammes × 0,9 ;score_by_ngram_distance(autocomplete inactif) : n-grammes × 1,0 ;score_by_geo_distance(si centre donné) :0,1 × exp(-(km/50)²), 0 au-delà de 100 km, plafond 0,1. Distance haversine, rayon 6 367 km.
Chaque composante garde la meilleure valeur sur l'ensemble des libellés.
Score final = Σ valeurs / Σ plafonds. Pas d'adjust_scores à la BAN.
Les résultats sont triés, tronqués, puis ceux sous 0,3 (MIN_SCORE de
la Géoplateforme) sont retirés.
La Géoplateforme demande toujours au moins 10 résultats à addok
(api/indexes/address.js : Math.max(params.limit || 10, 10)), puis ne
renvoie que les limit meilleurs. Ce n'est pas qu'une question
d'affichage : les collecteurs raisonnent sur ce nombre (bucket « plein » ou
« à sec »), donc une requête avec limit=5 peut déclencher un fuzzy qu'addok
seul, avec 5, n'aurait pas tenté. geocoder-core fait de même
(Profile::min_limit).
NGram.compare(libellé, requête, N=2, pad_len=0) (bibliothèque ngram
4.0.3) : bigrammes sans bourrage,
commun = bigrammes partagés, comptés avec multiplicité
score = commun / (bigrammes(libellé) + bigrammes(requête) − commun)
C'est un Jaccard sur multiensembles, pas un Dice. Deux chaînes d'un seul caractère valent 1 si égales ; sans bigramme commun, 0.
« 42 rue de rivoli paris » contre la rue de Rivoli (75004, importance 0,7), numéro 42 présent : le libellé « 42 Rue de Rivoli 75004 Paris » ne matche pas, mais « 42 Rue de Rivoli Paris » est égal à la requête → 1,0. Score = (1,0 + 0,07) / (1,0 + 0,1) = 0,9727.