github.com/benoitkugler/goacve@v0.0.0-20201217100549-151ce6e55dc8/server/core/rawdata/matching/similaires.go (about) 1 // Implémente des outils de recherche et de fusion de profil 2 package matching 3 4 import ( 5 "strings" 6 7 rd "github.com/benoitkugler/goACVE/server/core/rawdata" 8 ) 9 10 // Ce fichier implémente un algorithme de comparaison 11 // champ par champ entre deux personnes (une entrante et une existante). 12 // Il est utilisé de façon automatique pour fusionner 13 // une inscription avec une personne déjà existante, 14 // et de façon semi-automatique sur le client pour rapprocher 15 // une personne temporaire d'une personne existante. 16 17 // IdentifieTarget est une union décrivant le résultat 18 // d'une identification ("vers un nouveau profil" ou "rapprochement") 19 type IdentifieTarget interface { 20 isIdentifieTarget() 21 } 22 23 type NouveauProfil struct{} 24 25 func (NouveauProfil) isIdentifieTarget() {} 26 27 type Rattache struct { 28 IdTarget int64 // la personne à laquelle se rattacher 29 Modifications rd.BasePersonne // le résultat de la fusion, à appliquer à la cible 30 } 31 32 func (Rattache) isIdentifieTarget() {} 33 34 // résultat d'une comparaison 35 type diff int 36 37 const ( 38 diffInZero diff = iota // la valeur entrante est vide 39 diffExistZero // la valeur existante est vide 40 diffEqual // les deux valeurs sont similaires 41 diffConflict // les deux valeurs sont vraiments différentes 42 ) 43 44 func equalString(s1, s2 rd.String) diff { 45 if s1 == "" { 46 return diffInZero 47 } 48 if s2 == "" { 49 return diffExistZero 50 } 51 ss1 := strings.ToLower(strings.Replace(RemoveAccents(string(s1)), " ", "", -1)) 52 ss2 := strings.ToLower(strings.Replace(RemoveAccents(string(s2)), " ", "", -1)) 53 if ss1 == ss2 { 54 return diffEqual 55 } 56 return diffConflict 57 } 58 59 func equalTels(t1, t2 rd.Tels) diff { 60 if len(t1) == 0 { 61 return diffInZero 62 } 63 if len(t2) == 0 { 64 return diffExistZero 65 } 66 s1, s2 := make(map[string]bool), make(map[string]bool) 67 for _, t := range t1 { 68 s1[rd.CondenseTel(t)] = true 69 } 70 for _, t := range t2 { 71 t = rd.CondenseTel(t) 72 if !s1[t] { 73 return diffConflict 74 } 75 s2[t] = true 76 } 77 for t := range s1 { 78 if !s2[t] { 79 return diffConflict 80 } 81 } 82 return diffEqual 83 } 84 85 func equalBool(in, out rd.Bool) diff { 86 if !in { 87 return diffInZero 88 } 89 if !out { 90 return diffExistZero 91 } 92 if in == out { 93 return diffEqual 94 } 95 return diffConflict 96 } 97 98 func equalDate(in, out rd.Date) diff { 99 if in.Time().IsZero() { 100 return diffInZero 101 } 102 if out.Time().IsZero() { 103 return diffExistZero 104 } 105 if in.Equals(out) { 106 return diffEqual 107 } 108 return diffConflict 109 } 110 111 func equalSexe(in, out rd.Sexe) diff { 112 if in == "" { 113 return diffInZero 114 } 115 if out == "" { 116 return diffExistZero 117 } 118 if in == out { 119 return diffEqual 120 } 121 return diffConflict 122 } 123 124 // Conflicts indique quels champs n'ont pu être automatiquement fusionnés 125 type Conflicts struct { 126 Nom bool 127 NomJeuneFille bool 128 Prenom bool 129 DateNaissance bool 130 VilleNaissance bool 131 DepartementNaissance bool 132 Sexe bool 133 Tels bool 134 Mail bool 135 Adresse bool 136 CodePostal bool 137 Ville bool 138 Pays bool 139 SecuriteSociale bool 140 Profession bool 141 Etudiant bool 142 Fonctionnaire bool 143 } 144 145 // renvoi l'index d'un tableau [entrant, existant] et un conflit (ou non) 146 // ce serait plus élégant avec des génériques 147 func (d diff) choose() (int, bool) { 148 switch d { 149 case diffInZero: // on garde l'existant 150 return 1, false 151 case diffExistZero: // on ecrase l'existant 152 return 0, false 153 case diffEqual: // on ecrase (c'est plus cohérent avec l'attente utilisateur) 154 return 0, false 155 case diffConflict: // on écrase et on alerte 156 return 0, true 157 default: 158 panic("unknow diff type") 159 } 160 } 161 162 func chooseString(entrant, existant rd.String) (rd.String, bool) { 163 d := equalString(entrant, existant) 164 i, b := d.choose() 165 return [2]rd.String{entrant, existant}[i], b 166 } 167 168 func chooseTels(entrant, existant rd.Tels) (rd.Tels, bool) { 169 d := equalTels(entrant, existant) 170 i, b := d.choose() 171 return [2]rd.Tels{entrant, existant}[i], b 172 } 173 174 func chooseBool(entrant, existant rd.Bool) (rd.Bool, bool) { 175 d := equalBool(entrant, existant) 176 i, b := d.choose() 177 return [2]rd.Bool{entrant, existant}[i], b 178 } 179 180 func chooseDate(entrant, existant rd.Date) (rd.Date, bool) { 181 d := equalDate(entrant, existant) 182 i, b := d.choose() 183 return [2]rd.Date{entrant, existant}[i], b 184 } 185 186 func chooseSexe(entrant, existant rd.Sexe) (rd.Sexe, bool) { 187 d := equalSexe(entrant, existant) 188 i, b := d.choose() 189 return [2]rd.Sexe{entrant, existant}[i], b 190 } 191 192 func chooseDepartement(entrant, existant rd.Departement) (rd.Departement, bool) { 193 d := equalString(rd.String(entrant), rd.String(existant)) // as strings 194 i, b := d.choose() 195 return [2]rd.Departement{entrant, existant}[i], b 196 } 197 198 // Merge compare champs par champs les deux personnes et renvoie 199 // le résultat de la fusion et un crible d'alerte 200 func Merge(entrant rd.BasePersonne, existant rd.BasePersonne) (merged rd.BasePersonne, conflicts Conflicts) { 201 merged.Nom, conflicts.Nom = chooseString(entrant.Nom, existant.Nom) 202 merged.NomJeuneFille, conflicts.NomJeuneFille = chooseString(entrant.NomJeuneFille, existant.NomJeuneFille) 203 merged.Prenom, conflicts.Prenom = chooseString(entrant.Prenom, existant.Prenom) 204 merged.DateNaissance, conflicts.DateNaissance = chooseDate(entrant.DateNaissance, existant.DateNaissance) 205 merged.VilleNaissance, conflicts.VilleNaissance = chooseString(entrant.VilleNaissance, existant.VilleNaissance) 206 merged.DepartementNaissance, conflicts.DepartementNaissance = chooseDepartement(entrant.DepartementNaissance, existant.DepartementNaissance) 207 merged.Sexe, conflicts.Sexe = chooseSexe(entrant.Sexe, existant.Sexe) 208 merged.Tels, conflicts.Tels = chooseTels(entrant.Tels, existant.Tels) 209 merged.Mail, conflicts.Mail = chooseString(entrant.Mail, existant.Mail) 210 merged.Adresse, conflicts.Adresse = chooseString(entrant.Adresse, existant.Adresse) 211 merged.CodePostal, conflicts.CodePostal = chooseString(entrant.CodePostal, existant.CodePostal) 212 merged.Ville, conflicts.Ville = chooseString(entrant.Ville, existant.Ville) 213 merged.Pays, conflicts.Pays = chooseString(entrant.Pays, existant.Pays) 214 merged.SecuriteSociale, conflicts.SecuriteSociale = chooseString(entrant.SecuriteSociale, existant.SecuriteSociale) 215 merged.Profession, conflicts.Profession = chooseString(entrant.Profession, existant.Profession) 216 merged.Etudiant, conflicts.Etudiant = chooseBool(entrant.Etudiant, existant.Etudiant) 217 merged.Fonctionnaire, conflicts.Fonctionnaire = chooseBool(entrant.Fonctionnaire, existant.Fonctionnaire) 218 return merged, conflicts 219 }