github.com/benoitkugler/goacve@v0.0.0-20201217100549-151ce6e55dc8/server/core/rawdata/matching/search.go (about) 1 package matching 2 3 import ( 4 "fmt" 5 "sort" 6 "strings" 7 "unicode" 8 9 rd "github.com/benoitkugler/goACVE/server/core/rawdata" 10 "golang.org/x/text/runes" 11 "golang.org/x/text/transform" 12 "golang.org/x/text/unicode/norm" 13 ) 14 15 var ( 16 PoidsRechercheSimilaire = struct { 17 Nom, Prenom, Mail, NomJeuneFille, DateNaissance, Sexe int 18 }{3, 2, 4, 2, 3, 1} 19 SEUIL_RECHERCHE_SIMILAIRE = 2 20 21 noAccent = transform.Chain(norm.NFD, runes.Remove(runes.In(unicode.Mn)), norm.NFC) 22 ) 23 24 func RemoveAccents(s string) string { 25 output, _, e := transform.String(noAccent, s) 26 if e != nil { 27 return s 28 } 29 return output 30 } 31 32 // Fonction de recherche par similarité dans les personnes 33 type PatternsSimilarite struct { 34 Nom rd.String `json:"nom,omitempty"` 35 Prenom rd.String `json:"prenom,omitempty"` 36 Sexe rd.Sexe `json:"sexe,omitempty"` 37 DateNaissance rd.Date `json:"date_naissance,omitempty"` 38 Mail rd.String `json:"mail,omitempty"` 39 NomJeuneFille rd.String `json:"nom_jeune_fille,omitempty"` 40 } 41 42 // SelectAllPatternSimilaires ne charge que les champs requis 43 // par `PatternsSimilarite` 44 func SelectAllPatternSimilaires(tx rd.DB) ([]rd.Personne, error) { 45 // Champs utilisés par la recherche de profil : on évite de charger tous les champs 46 const selectPatternSimilarites = "id, nom, prenom, sexe, date_naissance, mail, nom_jeune_fille, is_temporaire" 47 48 rows, err := tx.Query(fmt.Sprintf("SELECT %s FROM personnes", selectPatternSimilarites)) 49 if err != nil { 50 return nil, err 51 } 52 defer rows.Close() 53 54 var personnes []rd.Personne 55 for rows.Next() { 56 var item rd.Personne 57 err := rows.Scan( 58 &item.Id, 59 &item.Nom, 60 &item.Prenom, 61 &item.Sexe, 62 &item.DateNaissance, 63 &item.Mail, 64 &item.NomJeuneFille, 65 &item.IsTemporaire, 66 ) 67 if err != nil { 68 return nil, err 69 } 70 personnes = append(personnes, item) 71 } 72 if err := rows.Err(); err != nil { 73 return nil, err 74 } 75 76 return personnes, nil 77 } 78 79 // Enlève les accents de full et renvoie true si subs est dans full 80 // Renvoi false si une des deux chaines est vide. 81 func isIn(subs rd.String, full rd.String) bool { 82 return subs != "" && full != "" && strings.Contains( 83 RemoveAccents(strings.TrimSpace(strings.ToLower(string(full)))), 84 strings.TrimSpace(strings.ToLower(string(subs))), 85 ) 86 } 87 88 func comparaison(p rd.Personne, in PatternsSimilarite) (score int) { 89 if in.Mail != "" && strings.Contains(strings.TrimSpace(string(p.Mail)), strings.TrimSpace(string(in.Mail))) { 90 score += PoidsRechercheSimilaire.Mail 91 } 92 if !p.DateNaissance.Time().IsZero() && in.DateNaissance.Equals(p.DateNaissance) { 93 score += PoidsRechercheSimilaire.DateNaissance 94 } 95 if p.Sexe != "" && p.Sexe == in.Sexe { 96 score += PoidsRechercheSimilaire.Sexe 97 } 98 99 if isIn(in.Nom, p.Nom) { 100 score += PoidsRechercheSimilaire.Nom 101 } 102 if isIn(in.Prenom, p.Prenom) { 103 score += PoidsRechercheSimilaire.Prenom 104 } 105 if isIn(in.NomJeuneFille, p.NomJeuneFille) { 106 score += PoidsRechercheSimilaire.NomJeuneFille 107 } 108 return score 109 } 110 111 type scoredPersonne struct { 112 Score int 113 Personne rd.Personne 114 } 115 116 // ChercheSimilaires effectue la recherche, et peut être utilisée directement (sur le serveur). 117 // Les personnes temporaires sont ignorées. 118 func ChercheSimilaires(personnes []rd.Personne, in PatternsSimilarite) (scoreMax int, out []scoredPersonne) { 119 if in.Nom != "" { 120 scoreMax += PoidsRechercheSimilaire.Nom 121 } 122 if in.Prenom != "" { 123 scoreMax += PoidsRechercheSimilaire.Prenom 124 } 125 if in.NomJeuneFille != "" { 126 scoreMax += PoidsRechercheSimilaire.NomJeuneFille 127 } 128 if in.Mail != "" { 129 scoreMax += PoidsRechercheSimilaire.Mail 130 } 131 if in.Sexe != "" { 132 scoreMax += PoidsRechercheSimilaire.Sexe 133 } 134 if !in.DateNaissance.Time().IsZero() { 135 scoreMax += PoidsRechercheSimilaire.DateNaissance 136 } 137 if scoreMax == 0 { 138 return 139 } 140 in.Nom = rd.String(RemoveAccents(string(in.Nom))) 141 in.Prenom = rd.String(RemoveAccents(string(in.Prenom))) 142 in.NomJeuneFille = rd.String(RemoveAccents(string(in.NomJeuneFille))) 143 out = make([]scoredPersonne, 0, len(personnes)) 144 for _, p := range personnes { 145 if p.IsTemporaire { // une personne temporaire doit d'abord être validée. 146 continue 147 } 148 score := comparaison(p, in) 149 if score >= SEUIL_RECHERCHE_SIMILAIRE { 150 out = append(out, scoredPersonne{score, p}) 151 } 152 } 153 154 sort.Slice(out, func(i, j int) bool { return out[i].Score > out[j].Score }) // decroissant 155 return scoreMax, out 156 }