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  }