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):
|
||||
legacy_names.add(part)
|
||||
|
||||
# 4) Match par nom dans la table contacts pour les responsables/référents
|
||||
# qui n'ont pas d'email côté servers. Match insensible casse + accents.
|
||||
# 4) Match par parsing du nom complet → email SANEF (format prenom.nom@sanef.com)
|
||||
# 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:
|
||||
# On construit une condition ILIKE pour chaque nom + ses variantes simples
|
||||
names_list = list(legacy_names)[:50] # limite de sécurité
|
||||
params = {}
|
||||
ors = []
|
||||
for i, n in enumerate(names_list):
|
||||
params[f"n{i}"] = n
|
||||
# Match exact (case insensible) ou nom contenu (utile pour 'DUFOUR Antoine' vs 'Antoine Dufour')
|
||||
ors.append(f"LOWER(c.name) = LOWER(:n{i})")
|
||||
ors.append(f"LOWER(c.name) LIKE LOWER('%' || :n{i} || '%')")
|
||||
sql = (
|
||||
"SELECT DISTINCT c.name, c.email FROM contacts c "
|
||||
"WHERE c.email IS NOT NULL AND c.email <> '' "
|
||||
f"AND ({' OR '.join(ors)})"
|
||||
)
|
||||
try:
|
||||
matched = db.execute(text(sql), params).fetchall()
|
||||
for c in matched:
|
||||
_add(c.name, c.email)
|
||||
except Exception as e:
|
||||
print(f"[pct_cc] match par nom failed: {e}")
|
||||
pairs = [] # liste de (prenom, nom)
|
||||
for raw in legacy_names:
|
||||
# Nettoie suffixes parasites courants : "- PCT", "- DBA", "(externe)", etc.
|
||||
s = re.sub(r"\s*[\(\[].*?[\)\]]\s*", " ", raw)
|
||||
s = re.sub(r"\s*-\s*[A-Z]{2,5}\s*$", "", s).strip() # "- PCT" en fin
|
||||
if not s:
|
||||
continue
|
||||
tokens = re.split(r"\s+", s)
|
||||
tokens = [t for t in tokens if t and t not in ("PCT", "DBA", "ext", "Ext")]
|
||||
if len(tokens) < 2:
|
||||
continue
|
||||
# Identifie le nom de famille = token UPPERCASE de longueur >= 3
|
||||
nom = None
|
||||
for t in tokens:
|
||||
# Match nom de famille : uniquement majuscules ASCII, longueur >= 3
|
||||
# (gère 'DUFOUR', 'DELCOUR', 'GRAFFAGNINO' ; pas 'PCT' qui est trop court)
|
||||
tt = t.replace("-", "").replace("'", "")
|
||||
if len(tt) >= 3 and tt.isupper():
|
||||
nom = t
|
||||
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())
|
||||
return out
|
||||
|
||||
Loading…
Reference in New Issue
Block a user