fix(pct/cc): parse <Prenom> <NOM> et match email SANEF strict (prenom.nom@%)
Cas reel observe: contacts.name = juste le NOM de famille (DUFOUR, DELCOUR), avec plusieurs entrees par nom (Antoine vs Jean-Charles DUFOUR par ex). Le match precedent '%nom%' renvoyait des faux positifs. Solution: - Nettoie suffixes parasites: '(externe)', '- PCT', '- DBA' en fin - Identifie nom de famille = token UPPERCASE longueur >= 3 (DUFOUR, DELCOUR, etc.) - Prenom = 1er token qui n'est pas le nom - Match SQL: LOWER(email) LIKE 'prenom.nom@%' insensible casse - Pas de FROM nom de famille SEUL (qui matcherait des homonymes) Pour 'Laurent DELCOUR' -> match strict laurent.delcour@% Pour 'DUFOUR Antoine - PCT' -> nettoye en 'DUFOUR Antoine' -> match antoine.dufour@%
This commit is contained in:
parent
5e5803afa2
commit
98d0ad0a3d
@ -1178,29 +1178,54 @@ def _fetch_pct_cc_emails(db, row_ids):
|
|||||||
if len(part) >= 4 and re.search(r"[A-Za-zÀ-ÿ]{3}", part):
|
if len(part) >= 4 and re.search(r"[A-Za-zÀ-ÿ]{3}", part):
|
||||||
legacy_names.add(part)
|
legacy_names.add(part)
|
||||||
|
|
||||||
# 4) Match par nom dans la table contacts pour les responsables/référents
|
# 4) Match par parsing du nom complet → email SANEF (format prenom.nom@sanef.com)
|
||||||
# qui n'ont pas d'email côté servers. Match insensible casse + accents.
|
# Le contacts.name en BDD est juste le NOM DE FAMILLE (ex 'DELCOUR'),
|
||||||
|
# et il y a souvent plusieurs DELCOUR — il faut matcher ET le nom ET le prénom.
|
||||||
if legacy_names:
|
if legacy_names:
|
||||||
# On construit une condition ILIKE pour chaque nom + ses variantes simples
|
pairs = [] # liste de (prenom, nom)
|
||||||
names_list = list(legacy_names)[:50] # limite de sécurité
|
for raw in legacy_names:
|
||||||
params = {}
|
# Nettoie suffixes parasites courants : "- PCT", "- DBA", "(externe)", etc.
|
||||||
ors = []
|
s = re.sub(r"\s*[\(\[].*?[\)\]]\s*", " ", raw)
|
||||||
for i, n in enumerate(names_list):
|
s = re.sub(r"\s*-\s*[A-Z]{2,5}\s*$", "", s).strip() # "- PCT" en fin
|
||||||
params[f"n{i}"] = n
|
if not s:
|
||||||
# Match exact (case insensible) ou nom contenu (utile pour 'DUFOUR Antoine' vs 'Antoine Dufour')
|
continue
|
||||||
ors.append(f"LOWER(c.name) = LOWER(:n{i})")
|
tokens = re.split(r"\s+", s)
|
||||||
ors.append(f"LOWER(c.name) LIKE LOWER('%' || :n{i} || '%')")
|
tokens = [t for t in tokens if t and t not in ("PCT", "DBA", "ext", "Ext")]
|
||||||
sql = (
|
if len(tokens) < 2:
|
||||||
"SELECT DISTINCT c.name, c.email FROM contacts c "
|
continue
|
||||||
"WHERE c.email IS NOT NULL AND c.email <> '' "
|
# Identifie le nom de famille = token UPPERCASE de longueur >= 3
|
||||||
f"AND ({' OR '.join(ors)})"
|
nom = None
|
||||||
)
|
for t in tokens:
|
||||||
try:
|
# Match nom de famille : uniquement majuscules ASCII, longueur >= 3
|
||||||
matched = db.execute(text(sql), params).fetchall()
|
# (gère 'DUFOUR', 'DELCOUR', 'GRAFFAGNINO' ; pas 'PCT' qui est trop court)
|
||||||
for c in matched:
|
tt = t.replace("-", "").replace("'", "")
|
||||||
_add(c.name, c.email)
|
if len(tt) >= 3 and tt.isupper():
|
||||||
except Exception as e:
|
nom = t
|
||||||
print(f"[pct_cc] match par nom failed: {e}")
|
break
|
||||||
|
if nom:
|
||||||
|
# Prénom = 1er token qui n'est pas le nom (souvent le 1er token)
|
||||||
|
prenom = next((t for t in tokens if t != nom), None)
|
||||||
|
else:
|
||||||
|
# Pas de UPPERCASE détecté : on suppose <Prénom> <Nom> et prend dernier token
|
||||||
|
prenom = tokens[0]
|
||||||
|
nom = tokens[-1]
|
||||||
|
if prenom and nom and prenom != nom:
|
||||||
|
pairs.append((prenom, nom))
|
||||||
|
|
||||||
|
# Pour chaque (prenom, nom), match strict sur email SANEF
|
||||||
|
for prenom, nom in pairs[:50]: # limite de sécurité
|
||||||
|
# email format SANEF : prenom.nom@... (insensible casse, accents non gérés en BDD)
|
||||||
|
email_pattern = f"{prenom.lower()}.{nom.lower()}@%"
|
||||||
|
try:
|
||||||
|
matched = db.execute(text("""
|
||||||
|
SELECT name, email FROM contacts
|
||||||
|
WHERE email IS NOT NULL AND email <> ''
|
||||||
|
AND LOWER(email) LIKE :pat
|
||||||
|
"""), {"pat": email_pattern}).fetchall()
|
||||||
|
for c in matched:
|
||||||
|
_add(f"{prenom} {nom}", c.email)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[pct_cc] match {prenom}.{nom} failed: {e}")
|
||||||
|
|
||||||
out.sort(key=lambda c: c["name"].lower())
|
out.sort(key=lambda c: c["name"].lower())
|
||||||
return out
|
return out
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user