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  }