Skip to content

Commit d407b9f

Browse files
THIOYE97claude
andcommitted
Dédoublonnage : neutraliser la casse dans la comparaison des graphies
L'analyse en production annonçait 0 doublon supprimable — un résultat trop absolu pour être vrai. Vérification sur un doublon connu : les noms bruts sont identiques, seule la normalisation diffère. L'ancien agent écrivait en minuscules (« vladimir putin »), le moteur d'ingestion en majuscules (« VLADIMIR PUTIN »). Le critère d'inclusion comparait donc des graphies qui ne pouvaient jamais coïncider : il était vrai par construction, et le zéro n'avait aucun sens. Le rapprochement, lui, n'en souffrait pas : pg_trgm ignore la casse. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 35b0a79 commit d407b9f

2 files changed

Lines changed: 32 additions & 3 deletions

File tree

app/scripts/dedupe_entities.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,12 +69,16 @@ def _sql_candidats(db) -> str:
6969
FROM entities e
7070
WHERE NOT ({ref})
7171
),
72-
-- Libellés portés par chaque entité, sous forme d'ensemble normalisé.
72+
-- Libellés portés par chaque entité, sous forme d'ensemble.
73+
-- La casse est neutralisée : l'ancien agent normalisait en minuscules, le
74+
-- moteur d'ingestion en majuscules. Comparer sans en tenir compte revenait à
75+
-- ne jamais rien trouver — le critère était vrai par construction, donc vide
76+
-- de sens. (Le rapprochement, lui, n'en souffre pas : pg_trgm ignore la casse.)
7377
-- Compter ne suffit pas : deux copies peuvent porter autant de libellés tout
7478
-- en couvrant des graphies différentes. Seule l'INCLUSION garantit qu'aucune
7579
-- graphie ne disparaît.
7680
libelles AS (
77-
SELECT entity_id, ARRAY_AGG(DISTINCT name_normalized) AS jeu
81+
SELECT entity_id, ARRAY_AGG(DISTINCT UPPER(name_normalized)) AS jeu
7882
FROM entity_names GROUP BY entity_id
7983
),
8084
officielles AS (
@@ -119,7 +123,7 @@ def analyser(db) -> dict:
119123
# version rattachée à une source.
120124
plus_riches = db.execute(text(f"""
121125
WITH libelles AS (
122-
SELECT entity_id, ARRAY_AGG(DISTINCT name_normalized) AS jeu
126+
SELECT entity_id, ARRAY_AGG(DISTINCT UPPER(name_normalized)) AS jeu
123127
FROM entity_names GROUP BY entity_id
124128
),
125129
orph AS (

tests/test_lbcft_modules.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1091,3 +1091,28 @@ def alias(eid, valeur):
10911091
dedupe_entities.supprimer(db)
10921092
assert db.execute(text("SELECT COUNT(*) FROM entities WHERE id = CAST(:i AS uuid)"),
10931093
{"i": copie}).scalar() == 1
1094+
1095+
1096+
@pytest.mark.integration
1097+
def test_dedupe_compare_les_graphies_sans_tenir_compte_de_la_casse(db):
1098+
"""L'ancien agent normalisait en minuscules, le moteur d'ingestion en
1099+
majuscules. Comparer sans neutraliser la casse rendait le critère vrai par
1100+
construction : AUCUN doublon n'était jamais détecté, alors que les noms
1101+
bruts étaient identiques."""
1102+
from sqlalchemy import text
1103+
from app.scripts import dedupe_entities
1104+
from app.services.matching import tokenize
1105+
1106+
officielle = _entite(db, "IBRAHIMA SOW", avec_source=True)
1107+
copie = _entite(db, "Ibrahima Sow")
1108+
# On rejoue l'écart : la copie porte la graphie en minuscules.
1109+
db.execute(text("UPDATE entity_names SET name_normalized = LOWER(name_normalized) "
1110+
"WHERE entity_id = CAST(:i AS uuid)"), {"i": copie})
1111+
db.commit()
1112+
1113+
assert dedupe_entities.analyser(db)["candidats"] >= 1
1114+
dedupe_entities.supprimer(db)
1115+
assert db.execute(text("SELECT COUNT(*) FROM entities WHERE id = CAST(:i AS uuid)"),
1116+
{"i": copie}).scalar() == 0
1117+
assert db.execute(text("SELECT COUNT(*) FROM entities WHERE id = CAST(:i AS uuid)"),
1118+
{"i": officielle}).scalar() == 1

0 commit comments

Comments
 (0)