github.com/benoitkugler/goacve@v0.0.0-20201217100549-151ce6e55dc8/server/core/datamodel/search.go (about)

     1  package datamodel
     2  
     3  import (
     4  	"regexp"
     5  	"sort"
     6  	"strings"
     7  
     8  	rd "github.com/benoitkugler/goACVE/server/core/rawdata"
     9  	"github.com/benoitkugler/goACVE/server/core/rawdata/matching"
    10  )
    11  
    12  // Fonctions de recherche rapide (par string)
    13  
    14  func matchSearch(regexps []*regexp.Regexp, dataString string) bool {
    15  	dataString = matching.RemoveAccents(dataString)
    16  	for _, r := range regexps {
    17  		if !r.MatchString(dataString) {
    18  			return false
    19  		}
    20  	}
    21  	return true
    22  }
    23  
    24  func prepareSearch(pattern string) (out []*regexp.Regexp, err error) {
    25  	if pattern == "*" {
    26  		return out, nil
    27  	}
    28  
    29  	for _, s := range strings.Split(pattern, " ") {
    30  		if strings.TrimSpace(s) == "" {
    31  			continue
    32  		}
    33  		r, err := regexp.Compile("(?i)" + matching.RemoveAccents(s))
    34  		if err != nil {
    35  			return out, err
    36  		}
    37  		out = append(out, r)
    38  	}
    39  	return out, nil
    40  }
    41  
    42  func personneToString(raw rd.Personne) string {
    43  	return raw.NomPrenom().String()
    44  }
    45  
    46  func organismeToString(raw rd.Organisme) string {
    47  	return raw.Nom.String()
    48  }
    49  
    50  func aideToString(raw rd.Aide, base *BaseLocale) string {
    51  	part := base.Participants[raw.IdParticipant]
    52  	struc := base.Structureaides[raw.IdStructureaide]
    53  	return participantToString(part, base) + " " + structureaideToString(struc)
    54  }
    55  
    56  func campToString(raw rd.Camp) string {
    57  	return string(raw.Nom) + " " + raw.DateDebut.String() + " " + raw.DateFin.String()
    58  }
    59  
    60  func groupeToString(raw rd.Groupe, base *BaseLocale) string {
    61  	return string(raw.Nom) + " " + campToString(base.Camps[raw.IdCamp])
    62  }
    63  
    64  func participantToString(raw rd.Participant, base *BaseLocale) string {
    65  	pers := base.Personnes[raw.IdPersonne]
    66  	camp := base.Camps[raw.IdCamp]
    67  	return personneToString(pers) + " " + string(camp.Nom) + " " + camp.DateDebut.String()
    68  }
    69  
    70  func structureaideToString(raw rd.Structureaide) string {
    71  	return string(raw.Nom + " " + raw.Immatriculation)
    72  }
    73  
    74  func factureToString(raw rd.Facture, base *BaseLocale) string {
    75  	pers := base.Personnes[raw.IdPersonne]
    76  	return personneToString(pers)
    77  }
    78  
    79  func (b *BaseLocale) RechercheRapidePersonnes(withOrganismes bool, pattern string) (out rd.Table) {
    80  	rs, err := prepareSearch(pattern)
    81  	if err != nil {
    82  		return
    83  	}
    84  
    85  	for i, v := range b.Personnes {
    86  		if !bool(v.IsTemporaire) && matchSearch(rs, personneToString(v)) {
    87  			out = append(out, b.NewPersonne(i).AsItem(0))
    88  		}
    89  	}
    90  
    91  	if !withOrganismes {
    92  		return out
    93  	}
    94  
    95  	for i, o := range b.Organismes {
    96  		if matchSearch(rs, organismeToString(o)) {
    97  			out = append(out, b.NewOrganisme(i).AsItem())
    98  		}
    99  	}
   100  
   101  	return
   102  }
   103  
   104  func (b *BaseLocale) RechercheRapideAides(pattern string) (out rd.Table) {
   105  	rs, err := prepareSearch(pattern)
   106  	if err != nil {
   107  		return
   108  	}
   109  
   110  	for i, v := range b.Aides {
   111  		if matchSearch(rs, aideToString(v, b)) {
   112  			out = append(out, b.NewAide(i).AsItem())
   113  		}
   114  	}
   115  	return
   116  }
   117  
   118  func (b *BaseLocale) RechercheRapideCamps(pattern string) (out rd.Table) {
   119  	rs, err := prepareSearch(pattern)
   120  	if err != nil {
   121  		return
   122  	}
   123  
   124  	_, cache1 := b.ResoudParticipants()
   125  	cache2 := b.ResoudParticipantsimples()
   126  	for i, v := range b.Camps {
   127  		if matchSearch(rs, campToString(v)) {
   128  			out = append(out, b.NewCamp(i).AsItem(cache1, cache2))
   129  		}
   130  	}
   131  	return
   132  }
   133  
   134  func (b *BaseLocale) RechercheRapideGroupes(pattern string) (out rd.Table) {
   135  	rs, err := prepareSearch(pattern)
   136  	if err != nil {
   137  		return
   138  	}
   139  
   140  	for i, v := range b.Groupes {
   141  		if matchSearch(rs, groupeToString(v, b)) {
   142  			out = append(out, b.NewGroupe(i).AsItem())
   143  		}
   144  	}
   145  	return
   146  }
   147  
   148  // RechercheRapideParticipants ignore les équipiers et les participants simples
   149  func (b *BaseLocale) RechercheRapideParticipants(pattern string) (out rd.Table) {
   150  	rs, err := prepareSearch(pattern)
   151  	if err != nil {
   152  		return
   153  	}
   154  
   155  	var tmp []rd.Participant
   156  	for _, v := range b.Participants {
   157  		if matchSearch(rs, participantToString(v, b)) {
   158  			tmp = append(tmp, v)
   159  		}
   160  	}
   161  	_, cache := b.ResoudParticipants()
   162  	getDate := func(i int) int {
   163  		return b.Camps[tmp[i].IdCamp].DateDebut.Time().Year()
   164  	}
   165  	hasDoss := func(i int) bool {
   166  		return tmp[i].IdFacture.Valid
   167  	}
   168  	// Tri par date de participation
   169  	sort.Slice(tmp, func(i, j int) bool { return getDate(i) > getDate(j) })
   170  	// Participant libre d'abord
   171  	sort.SliceStable(tmp, func(i, j int) bool { return !hasDoss(i) && hasDoss(j) })
   172  	out = make(rd.Table, len(tmp))
   173  	for index, rawPart := range tmp {
   174  		out[index] = b.NewParticipant(rawPart.Id).AsItem(cache)
   175  	}
   176  	return out
   177  }
   178  
   179  func (b *BaseLocale) RechercheRapideStructureaides(pattern string) rd.Table {
   180  	rs, err := prepareSearch(pattern)
   181  	if err != nil {
   182  		return nil
   183  	}
   184  
   185  	var tmp []int64
   186  	for i, v := range b.Structureaides {
   187  		if matchSearch(rs, structureaideToString(v)) {
   188  			tmp = append(tmp, i)
   189  		}
   190  	}
   191  
   192  	// Tri par utilisations des structures
   193  	frequences := make(map[int64]int, len(b.Structureaides))
   194  	for _, aide := range b.Aides {
   195  		frequences[aide.IdStructureaide] += 1
   196  	}
   197  	sort.Slice(tmp, func(i, j int) bool { return frequences[tmp[i]] > frequences[tmp[j]] })
   198  
   199  	out := make(rd.Table, len(tmp))
   200  	for index, idStructure := range tmp {
   201  		out[index] = b.NewStructureaide(idStructure).AsItem()
   202  	}
   203  	return out
   204  }
   205  
   206  func (b *BaseLocale) RechercheRapideFactures(pattern string) (out rd.Table) {
   207  	rs, err := prepareSearch(pattern)
   208  	if err != nil {
   209  		return
   210  	}
   211  	cache1, _ := b.ResoudParticipants()
   212  	cache2 := b.ResoudMessages()
   213  	cache3 := b.ResoudPaiements()
   214  	for i, v := range b.Factures {
   215  		if matchSearch(rs, factureToString(v, b)) {
   216  			ac := b.NewFacture(i)
   217  			out = append(out, ac.AsItem(cache1, cache2, cache3))
   218  		}
   219  	}
   220  	return
   221  }
   222  
   223  // RechercheDetailleeChilds renvoie la liste filtrée, avec les champs Bold mis à jour
   224  // `in` peut aussi être modifiée et devrait être ignorée après l'appel
   225  func RechercheDetailleeChilds(in []rd.ItemChilds, pattern string, entete []rd.Header) (out []rd.ItemChilds) {
   226  	rs, err := prepareSearch(pattern)
   227  	if err != nil || len(rs) == 0 {
   228  		return
   229  	}
   230  	// alloue un cache une fois pour toute
   231  	cacheStrings := make([]string, len(entete))
   232  
   233  	var groupeMatch, rowMatch bool
   234  	for _, row := range in {
   235  		groupeMatch = false // il suffit d'une seule ligne (parent ou enfant) qui match
   236  
   237  		// on analyse le parent ...
   238  		row.Item, rowMatch = checkItem(entete, row.Item, cacheStrings, rs)
   239  		if rowMatch {
   240  			groupeMatch = true
   241  		}
   242  
   243  		// ... puis les enfants
   244  		for indexChild, itemChild := range row.Childs {
   245  			row.Childs[indexChild], rowMatch = checkItem(entete, itemChild, cacheStrings, rs)
   246  			if rowMatch {
   247  				groupeMatch = true
   248  			}
   249  		}
   250  
   251  		if groupeMatch {
   252  			out = append(out, row)
   253  		}
   254  	}
   255  	return out
   256  }
   257  
   258  func RechercheDetaillee(in rd.Table, pattern string, entete []rd.Header) rd.Table {
   259  	// on transforme temporairement les Items en ItemChilds ...
   260  	tmp := make([]rd.ItemChilds, len(in))
   261  	for i, v := range in {
   262  		tmp[i] = rd.ItemChilds{Item: v}
   263  	}
   264  	tmp = RechercheDetailleeChilds(tmp, pattern, entete)
   265  	// ... et on reconvertit dans l'autre sens
   266  	out := make(rd.Table, len(tmp))
   267  	for i, v := range tmp {
   268  		out[i] = v.Item
   269  	}
   270  	return out
   271  }
   272  
   273  // renvoie l'item avec les champs Bolds mis à jour, et `true` si l'item passe la recherche
   274  func checkItem(entete []rd.Header, item rd.Item, cacheStrings []string, rs []*regexp.Regexp) (rd.Item, bool) {
   275  	rowMatch := true
   276  
   277  	// processe Data une seule fois
   278  	for indexE, fieldE := range entete {
   279  		cacheStrings[indexE] = matching.RemoveAccents(item.Fields.Data(fieldE.Field).String())
   280  	}
   281  
   282  	// init & reset
   283  	item.Bolds = make(rd.B)
   284  
   285  	for _, re := range rs {
   286  		reFound := false
   287  		for index, field := range entete {
   288  			if re.MatchString(cacheStrings[index]) {
   289  				reFound = true
   290  				item.Bolds[field.Field] = true
   291  			}
   292  		}
   293  
   294  		// toutes les regexps doivent matcher (au moins une case)
   295  		if !reFound {
   296  			rowMatch = false
   297  			break
   298  		}
   299  	}
   300  
   301  	return item, rowMatch
   302  }
   303  
   304  // ChercheSimilaires applique la recherche de similarité sur toutes
   305  // la base
   306  func (b *BaseLocale) ChercheSimilaires(in matching.PatternsSimilarite) rd.Table {
   307  	pers := b.GetRawPersonnes()
   308  	sm, res := matching.ChercheSimilaires(pers, in)
   309  	out := make(rd.Table, len(res))
   310  	for i, r := range res {
   311  		ac := b.NewPersonne(r.Personne.Id)
   312  		pertinence := float64(r.Score) / float64(sm)
   313  		out[i] = ac.AsItem(pertinence)
   314  	}
   315  	return out
   316  }