Loading module/Oscar/src/Oscar/Strategy/Search/ElasticActivitySearch.php +114 −40 Original line number Diff line number Diff line Loading @@ -27,6 +27,57 @@ class ElasticActivitySearch extends ElasticSearchEngine implements IActivitySear return $this->addItem($activity); } protected function indicesCreateBody(): array { return [ 'settings' => [ 'analysis' => [ 'analyzer' => [ 'folding_analyzer' => [ 'tokenizer' => 'standard', 'filter' => ['lowercase', 'asciifolding'] ] ] ] ], /**** * 'label' => $activity->getLabel(), * 'description' => $activity->getDescription(), * 'saic' => $activity->getCentaureId(), * 'numerotation' => $activity->getNumbers(), * 'oscar' => $activity->getOscarNum(), * 'activitytype' => $activity->getActivityType() ? (string)$activity->getActivityType() : '', * 'numbers' => implode(" ", $activity->getNumbersValues()), * 'eotp' => $activity->getCodeEOTP(), * 'acronym' => $activity->getAcronym(), * 'activity_id' => $activity->getId(), * 'disciplines' => $activity->getDisciplinesArray(), * 'motscles' => $activity->getMotsclesArray(), * 'members' => $members, * 'partners' => $partners, * 'project_id' => $project_id, * 'project' => $project_body, * /****/ 'mappings' => [ 'date_detection' => false, 'numeric_detection' => false, 'properties' => [ 'id' => ['type' => 'keyword'], 'acronym' => [ 'type' => 'text', 'analyzer' => 'folding_analyzer'], 'eotp' => [ 'type' => 'text', 'analyzer' => 'folding_analyzer'], 'oscar' => [ 'type' => 'text', 'analyzer' => 'folding_analyzer'], 'saic' => [ 'type' => 'text', 'analyzer' => 'folding_analyzer'], 'numerotation' => [ 'type' => 'text', 'analyzer' => 'folding_analyzer'], 'numbers' => [ 'type' => 'text', 'analyzer' => 'folding_analyzer'], 'label' => [ 'type' => 'text', 'analyzer' => 'folding_analyzer'], 'description' => [ 'type' => 'text', 'analyzer' => 'folding_analyzer'], ] ] ]; } /** * @param mixed $object * @return array Loading @@ -36,8 +87,7 @@ class ElasticActivitySearch extends ElasticSearchEngine implements IActivitySear { if (!$object instanceof Activity) { throw new OscarException("L'objet indexé doit être une activité"); } else { } else { $activity = $object; } Loading Loading @@ -150,44 +200,68 @@ class ElasticActivitySearch extends ElasticSearchEngine implements IActivitySear ] ]; // TODO si plusieurs mots, ajouter une règle spécifique if ($wordsNbr > 1) { $query["bool"]["should"][] = [ "query_string" => [ "query" => $andQuery, "fields" => [ 'oscar^20', 'eotp^20', 'acronym^15', 'numbers^10', 'motscles^7', 'disciplines^7', 'label^5', 'project^5', 'activitytype^3', 'description^2', 'partners', 'members' ] ] ]; } else { $query["bool"]["should"] = [ ["match" => ["oscar" => ["query" => $andQuery, "boost" => 20]]], ["match" => ["eotp" => ["query" => $andQuery, "boost" => 20]]], ["match" => ["acronym" => ["query" => $andQuery, "boost" => 15]]], ["match" => ["numbers" => ["query" => $andQuery, "boost" => 10]]], ["match" => ["motscles" => ["query" => $andQuery, "boost" => 7]]], ["match" => ["disciplines" => ["query" => $andQuery, "boost" => 7]]], ["match" => ["label" => ["query" => $andQuery, "boost" => 5]]], ["match" => ["activitytype" => ["query" => $andQuery, "boost" => 3]]], ["match" => ["project" => ["query" => $andQuery, "boost" => 5]]], ["match" => ["description" => ["query" => $andQuery, "boost" => 1]]], ["match" => ["partners" => ["query" => $andQuery, "boost" => 1]]], ["match" => ["members" => ["query" => $andQuery, "boost" => 1]]], ]; } //// // Champs Oscar // $query["bool"]["should"][] = [ "match" => [ "oscar" => [ "query" => $search, "boost" => 20 ]]]; // $query["bool"]["should"][] = [ "prefix" => [ "oscar" => [ "value" => $search, "boost" => 5]]]; //// //// // Champ EOTP // $query["bool"]["should"][] = [ "match" => [ "eotp" => [ "query" => $search, "boost" => 20 ]]]; // $query["bool"]["should"][] = [ "prefix" => [ "eotp" => [ "value" => $search, "boost" => 5]]]; // // Champ ACRONYM // $query["bool"]["should"][] = [ "match" => [ "acronym" => [ "query" => $search, "fuzziness" => "auto", "boost" => 20 ]]]; // $query["bool"]["should"][] = [ "prefix" => [ "acronym" => [ "value" => strtolower($search), "boost" => 5]]]; // Champ LABEL/PROJECT LABEL $query["bool"]["should"][] = [ "match" => [ "label" => [ "query" => $search, "fuzziness" => "auto", "boost" => 4 ]]]; $query["bool"]["should"][] = [ "match" => [ "project" => [ "query" => $search, "fuzziness" => "auto", "boost" => 3 ]]]; // Champ NUMBERS $query["bool"]["should"][] = [ "match" => [ "numbers" => [ "query" => $search, "fuzziness" => "auto", "boost" => 5 ]]]; /** * "query_string" => [ * "query" => $andQuery, * "fields" => [ * 'oscar^20', * 'eotp^20', * 'acronym^15', * 'numbers^10', * 'motscles^7', * 'disciplines^7', * 'label^5', * 'project^5', * 'activitytype^3', * 'description^2', * 'partners', * 'members' * ] * ] * ]; * var_dump($query); die(); * } * else { * $query["bool"]["should"] = [ * ["match" => ["oscar" => ["query" => $andQuery, "boost" => 20]]], * ["match" => ["eotp" => ["query" => $andQuery, "boost" => 20]]], * ["match" => ["acronym" => ["query" => $andQuery, "boost" => 15]]], * ["match" => ["numbers" => ["query" => $andQuery, "boost" => 10]]], * ["match" => ["motscles" => ["query" => $andQuery, "boost" => 7]]], * ["match" => ["disciplines" => ["query" => $andQuery, "boost" => 7]]], * ["match" => ["label" => ["query" => $andQuery, "boost" => 5]]], * ["match" => ["activitytype" => ["query" => $andQuery, "boost" => 3]]], * ["match" => ["project" => ["query" => $andQuery, "boost" => 5]]], * ["match" => ["description" => ["query" => $andQuery, "boost" => 1]]], * ["match" => ["partners" => ["query" => $andQuery, "boost" => 1]]], * ["match" => ["members" => ["query" => $andQuery, "boost" => 1]]], * ]; * } * ****/ return $query; } Loading module/Oscar/src/Oscar/Strategy/Search/ElasticSearchEngine.php +13 −19 Original line number Diff line number Diff line Loading @@ -57,27 +57,11 @@ abstract class ElasticSearchEngine ->build(); } if( !$this->elasticSearchClient->indices()->exists(['index' => $this->getIndex()]) ){ $this->loggerService->debug("INDEX CREATE " . $this->getIndex()); $this->loggerService->info("INDEX CREATE " . $this->getIndex()); $this->elasticSearchClient->indices()->create( [ 'index' => $this->getIndex(), 'body' => [ 'mappings' => [ // Suppression de la détection des dates/nombres 'date_detection' => false, 'numeric_detection' => false, "dynamic_templates"=> [ [ "numerique_en_texte"=> [ "match_mapping_type"=> "long", "mapping"=> [ "type"=> "text" ] ] ] ] ] ] 'body' => $this->indicesCreateBody() ] ); } Loading @@ -89,6 +73,16 @@ abstract class ElasticSearchEngine } } protected function indicesCreateBody(): array { return [ 'mappings' => [ // Suppression de la détection des dates/nombres 'date_detection' => false, 'numeric_detection' => false ] ]; } public function getMapping() :array { return $this->getClient()->indices()->getMapping(['index' => $this->getIndex()]); } Loading Loading @@ -194,7 +188,7 @@ abstract class ElasticSearchEngine ] ]; $this->loggerService->debug("----query elastic : \n" . json_encode($query['body']['query']) ."\n---- "); $this->loggerService->info("----query elastic : \n" . json_encode($query['body']['query']) ."\n---- "); return $query; } Loading module/Oscar/src/Oscar/Strategy/Search/PersonElasticSearch.php +84 −10 Original line number Diff line number Diff line Loading @@ -38,6 +38,50 @@ class PersonElasticSearch extends ElasticSearchEngine implements IPersonISearchS return $this->addItem($person); } protected function indicesCreateBody(): array { return [ 'settings' => [ 'analysis' => [ 'analyzer' => [ 'folding_analyzer' => [ 'tokenizer' => 'standard', 'filter' => ['lowercase', 'asciifolding'] ] ] ] ], 'mappings' => [ 'properties' => [ 'id' => ['type' => 'keyword'], 'firstname' => [ 'type' => 'text', 'analyzer' => 'folding_analyzer' ], 'lastname' => [ 'type' => 'text', 'analyzer' => 'folding_analyzer' ], 'fullname' => [ 'type' => 'text', 'analyzer' => 'folding_analyzer' ], 'email' => [ 'type' => 'text', 'analyzer' => 'folding_analyzer' ], 'affectation' => ['type' => 'text'], 'location' => ['type' => 'text'], 'organizations' => ['type' => 'text'], 'activities' => ['type' => 'text'], 'connectors' => [ 'type' => 'text', 'analyzer' => 'folding_analyzer' ] ] ] ]; } public function getIndexableDatas(mixed $item) :array { if( !$item instanceof Person ){ Loading Loading @@ -100,11 +144,12 @@ class PersonElasticSearch extends ElasticSearchEngine implements IPersonISearchS return [ "bool" => [ "should" => [ // ---- Fuzzy matches ---- [ "match" => [ "connectors" => [ "query" => $search, "boost" => 6 // Favoriser les correspondances "boost" => 6 ] ] ], Loading @@ -112,8 +157,8 @@ class PersonElasticSearch extends ElasticSearchEngine implements IPersonISearchS "match" => [ "lastname" => [ "query" => $search, 'fuzziness' => "AUTO", // "Tolérance" aux fautes "boost" => 5 // Favoriser les correspondances "fuzziness" => "AUTO", "boost" => 5 ] ] ], Loading @@ -121,7 +166,7 @@ class PersonElasticSearch extends ElasticSearchEngine implements IPersonISearchS "match" => [ "fullname" => [ "query" => $search, 'fuzziness' => "AUTO", // "Tolérance" aux fautes, "fuzziness" => "AUTO", "boost" => 3 ] ] Loading @@ -130,7 +175,7 @@ class PersonElasticSearch extends ElasticSearchEngine implements IPersonISearchS "match" => [ "firstname" => [ "query" => $search, 'fuzziness' => "AUTO", // "Tolérance" aux fautes, "fuzziness" => "AUTO", "boost" => 2 ] ] Loading @@ -139,7 +184,7 @@ class PersonElasticSearch extends ElasticSearchEngine implements IPersonISearchS "match" => [ "email" => [ "query" => $search, 'fuzziness' => "AUTO", // "Tolérance" aux fautes, "fuzziness" => "AUTO", "boost" => 1 ] ] Loading @@ -148,7 +193,7 @@ class PersonElasticSearch extends ElasticSearchEngine implements IPersonISearchS "match" => [ "location" => [ "query" => $search, 'fuzziness' => "AUTO", // "Tolérance" aux fautes, "fuzziness" => "AUTO" ] ] ], Loading @@ -156,18 +201,47 @@ class PersonElasticSearch extends ElasticSearchEngine implements IPersonISearchS "match" => [ "affectation" => [ "query" => $search, 'fuzziness' => "AUTO", // "Tolérance" aux fautes, "fuzziness" => "AUTO" ] ] ], // ---- Commence par (prefix) ---- [ "prefix" => [ "lastname" => $search, // Documents qui commencent par l'expression "lastname" => [ "value" => $search, "boost" => 10 ] ] ], [ "prefix" => [ "connectors" => $search, // Documents qui commencent par l'expression "connectors" => [ "value" => $search, "boost" => 8 ] ] ], // ---- Autocomplétion et Prénom + Nom ---- [ "match_phrase_prefix" => [ "fullname" => [ "query" => $search, "slop" => 2, // tolère inversion prénom/nom "max_expansions" => 20, "boost" => 12 ] ] ], [ "multi_match" => [ "query" => $search, "type" => "cross_fields", "fields" => ["firstname^2", "lastname^3"], "operator" => "and", "boost" => 9 ] ] ], Loading Loading
module/Oscar/src/Oscar/Strategy/Search/ElasticActivitySearch.php +114 −40 Original line number Diff line number Diff line Loading @@ -27,6 +27,57 @@ class ElasticActivitySearch extends ElasticSearchEngine implements IActivitySear return $this->addItem($activity); } protected function indicesCreateBody(): array { return [ 'settings' => [ 'analysis' => [ 'analyzer' => [ 'folding_analyzer' => [ 'tokenizer' => 'standard', 'filter' => ['lowercase', 'asciifolding'] ] ] ] ], /**** * 'label' => $activity->getLabel(), * 'description' => $activity->getDescription(), * 'saic' => $activity->getCentaureId(), * 'numerotation' => $activity->getNumbers(), * 'oscar' => $activity->getOscarNum(), * 'activitytype' => $activity->getActivityType() ? (string)$activity->getActivityType() : '', * 'numbers' => implode(" ", $activity->getNumbersValues()), * 'eotp' => $activity->getCodeEOTP(), * 'acronym' => $activity->getAcronym(), * 'activity_id' => $activity->getId(), * 'disciplines' => $activity->getDisciplinesArray(), * 'motscles' => $activity->getMotsclesArray(), * 'members' => $members, * 'partners' => $partners, * 'project_id' => $project_id, * 'project' => $project_body, * /****/ 'mappings' => [ 'date_detection' => false, 'numeric_detection' => false, 'properties' => [ 'id' => ['type' => 'keyword'], 'acronym' => [ 'type' => 'text', 'analyzer' => 'folding_analyzer'], 'eotp' => [ 'type' => 'text', 'analyzer' => 'folding_analyzer'], 'oscar' => [ 'type' => 'text', 'analyzer' => 'folding_analyzer'], 'saic' => [ 'type' => 'text', 'analyzer' => 'folding_analyzer'], 'numerotation' => [ 'type' => 'text', 'analyzer' => 'folding_analyzer'], 'numbers' => [ 'type' => 'text', 'analyzer' => 'folding_analyzer'], 'label' => [ 'type' => 'text', 'analyzer' => 'folding_analyzer'], 'description' => [ 'type' => 'text', 'analyzer' => 'folding_analyzer'], ] ] ]; } /** * @param mixed $object * @return array Loading @@ -36,8 +87,7 @@ class ElasticActivitySearch extends ElasticSearchEngine implements IActivitySear { if (!$object instanceof Activity) { throw new OscarException("L'objet indexé doit être une activité"); } else { } else { $activity = $object; } Loading Loading @@ -150,44 +200,68 @@ class ElasticActivitySearch extends ElasticSearchEngine implements IActivitySear ] ]; // TODO si plusieurs mots, ajouter une règle spécifique if ($wordsNbr > 1) { $query["bool"]["should"][] = [ "query_string" => [ "query" => $andQuery, "fields" => [ 'oscar^20', 'eotp^20', 'acronym^15', 'numbers^10', 'motscles^7', 'disciplines^7', 'label^5', 'project^5', 'activitytype^3', 'description^2', 'partners', 'members' ] ] ]; } else { $query["bool"]["should"] = [ ["match" => ["oscar" => ["query" => $andQuery, "boost" => 20]]], ["match" => ["eotp" => ["query" => $andQuery, "boost" => 20]]], ["match" => ["acronym" => ["query" => $andQuery, "boost" => 15]]], ["match" => ["numbers" => ["query" => $andQuery, "boost" => 10]]], ["match" => ["motscles" => ["query" => $andQuery, "boost" => 7]]], ["match" => ["disciplines" => ["query" => $andQuery, "boost" => 7]]], ["match" => ["label" => ["query" => $andQuery, "boost" => 5]]], ["match" => ["activitytype" => ["query" => $andQuery, "boost" => 3]]], ["match" => ["project" => ["query" => $andQuery, "boost" => 5]]], ["match" => ["description" => ["query" => $andQuery, "boost" => 1]]], ["match" => ["partners" => ["query" => $andQuery, "boost" => 1]]], ["match" => ["members" => ["query" => $andQuery, "boost" => 1]]], ]; } //// // Champs Oscar // $query["bool"]["should"][] = [ "match" => [ "oscar" => [ "query" => $search, "boost" => 20 ]]]; // $query["bool"]["should"][] = [ "prefix" => [ "oscar" => [ "value" => $search, "boost" => 5]]]; //// //// // Champ EOTP // $query["bool"]["should"][] = [ "match" => [ "eotp" => [ "query" => $search, "boost" => 20 ]]]; // $query["bool"]["should"][] = [ "prefix" => [ "eotp" => [ "value" => $search, "boost" => 5]]]; // // Champ ACRONYM // $query["bool"]["should"][] = [ "match" => [ "acronym" => [ "query" => $search, "fuzziness" => "auto", "boost" => 20 ]]]; // $query["bool"]["should"][] = [ "prefix" => [ "acronym" => [ "value" => strtolower($search), "boost" => 5]]]; // Champ LABEL/PROJECT LABEL $query["bool"]["should"][] = [ "match" => [ "label" => [ "query" => $search, "fuzziness" => "auto", "boost" => 4 ]]]; $query["bool"]["should"][] = [ "match" => [ "project" => [ "query" => $search, "fuzziness" => "auto", "boost" => 3 ]]]; // Champ NUMBERS $query["bool"]["should"][] = [ "match" => [ "numbers" => [ "query" => $search, "fuzziness" => "auto", "boost" => 5 ]]]; /** * "query_string" => [ * "query" => $andQuery, * "fields" => [ * 'oscar^20', * 'eotp^20', * 'acronym^15', * 'numbers^10', * 'motscles^7', * 'disciplines^7', * 'label^5', * 'project^5', * 'activitytype^3', * 'description^2', * 'partners', * 'members' * ] * ] * ]; * var_dump($query); die(); * } * else { * $query["bool"]["should"] = [ * ["match" => ["oscar" => ["query" => $andQuery, "boost" => 20]]], * ["match" => ["eotp" => ["query" => $andQuery, "boost" => 20]]], * ["match" => ["acronym" => ["query" => $andQuery, "boost" => 15]]], * ["match" => ["numbers" => ["query" => $andQuery, "boost" => 10]]], * ["match" => ["motscles" => ["query" => $andQuery, "boost" => 7]]], * ["match" => ["disciplines" => ["query" => $andQuery, "boost" => 7]]], * ["match" => ["label" => ["query" => $andQuery, "boost" => 5]]], * ["match" => ["activitytype" => ["query" => $andQuery, "boost" => 3]]], * ["match" => ["project" => ["query" => $andQuery, "boost" => 5]]], * ["match" => ["description" => ["query" => $andQuery, "boost" => 1]]], * ["match" => ["partners" => ["query" => $andQuery, "boost" => 1]]], * ["match" => ["members" => ["query" => $andQuery, "boost" => 1]]], * ]; * } * ****/ return $query; } Loading
module/Oscar/src/Oscar/Strategy/Search/ElasticSearchEngine.php +13 −19 Original line number Diff line number Diff line Loading @@ -57,27 +57,11 @@ abstract class ElasticSearchEngine ->build(); } if( !$this->elasticSearchClient->indices()->exists(['index' => $this->getIndex()]) ){ $this->loggerService->debug("INDEX CREATE " . $this->getIndex()); $this->loggerService->info("INDEX CREATE " . $this->getIndex()); $this->elasticSearchClient->indices()->create( [ 'index' => $this->getIndex(), 'body' => [ 'mappings' => [ // Suppression de la détection des dates/nombres 'date_detection' => false, 'numeric_detection' => false, "dynamic_templates"=> [ [ "numerique_en_texte"=> [ "match_mapping_type"=> "long", "mapping"=> [ "type"=> "text" ] ] ] ] ] ] 'body' => $this->indicesCreateBody() ] ); } Loading @@ -89,6 +73,16 @@ abstract class ElasticSearchEngine } } protected function indicesCreateBody(): array { return [ 'mappings' => [ // Suppression de la détection des dates/nombres 'date_detection' => false, 'numeric_detection' => false ] ]; } public function getMapping() :array { return $this->getClient()->indices()->getMapping(['index' => $this->getIndex()]); } Loading Loading @@ -194,7 +188,7 @@ abstract class ElasticSearchEngine ] ]; $this->loggerService->debug("----query elastic : \n" . json_encode($query['body']['query']) ."\n---- "); $this->loggerService->info("----query elastic : \n" . json_encode($query['body']['query']) ."\n---- "); return $query; } Loading
module/Oscar/src/Oscar/Strategy/Search/PersonElasticSearch.php +84 −10 Original line number Diff line number Diff line Loading @@ -38,6 +38,50 @@ class PersonElasticSearch extends ElasticSearchEngine implements IPersonISearchS return $this->addItem($person); } protected function indicesCreateBody(): array { return [ 'settings' => [ 'analysis' => [ 'analyzer' => [ 'folding_analyzer' => [ 'tokenizer' => 'standard', 'filter' => ['lowercase', 'asciifolding'] ] ] ] ], 'mappings' => [ 'properties' => [ 'id' => ['type' => 'keyword'], 'firstname' => [ 'type' => 'text', 'analyzer' => 'folding_analyzer' ], 'lastname' => [ 'type' => 'text', 'analyzer' => 'folding_analyzer' ], 'fullname' => [ 'type' => 'text', 'analyzer' => 'folding_analyzer' ], 'email' => [ 'type' => 'text', 'analyzer' => 'folding_analyzer' ], 'affectation' => ['type' => 'text'], 'location' => ['type' => 'text'], 'organizations' => ['type' => 'text'], 'activities' => ['type' => 'text'], 'connectors' => [ 'type' => 'text', 'analyzer' => 'folding_analyzer' ] ] ] ]; } public function getIndexableDatas(mixed $item) :array { if( !$item instanceof Person ){ Loading Loading @@ -100,11 +144,12 @@ class PersonElasticSearch extends ElasticSearchEngine implements IPersonISearchS return [ "bool" => [ "should" => [ // ---- Fuzzy matches ---- [ "match" => [ "connectors" => [ "query" => $search, "boost" => 6 // Favoriser les correspondances "boost" => 6 ] ] ], Loading @@ -112,8 +157,8 @@ class PersonElasticSearch extends ElasticSearchEngine implements IPersonISearchS "match" => [ "lastname" => [ "query" => $search, 'fuzziness' => "AUTO", // "Tolérance" aux fautes "boost" => 5 // Favoriser les correspondances "fuzziness" => "AUTO", "boost" => 5 ] ] ], Loading @@ -121,7 +166,7 @@ class PersonElasticSearch extends ElasticSearchEngine implements IPersonISearchS "match" => [ "fullname" => [ "query" => $search, 'fuzziness' => "AUTO", // "Tolérance" aux fautes, "fuzziness" => "AUTO", "boost" => 3 ] ] Loading @@ -130,7 +175,7 @@ class PersonElasticSearch extends ElasticSearchEngine implements IPersonISearchS "match" => [ "firstname" => [ "query" => $search, 'fuzziness' => "AUTO", // "Tolérance" aux fautes, "fuzziness" => "AUTO", "boost" => 2 ] ] Loading @@ -139,7 +184,7 @@ class PersonElasticSearch extends ElasticSearchEngine implements IPersonISearchS "match" => [ "email" => [ "query" => $search, 'fuzziness' => "AUTO", // "Tolérance" aux fautes, "fuzziness" => "AUTO", "boost" => 1 ] ] Loading @@ -148,7 +193,7 @@ class PersonElasticSearch extends ElasticSearchEngine implements IPersonISearchS "match" => [ "location" => [ "query" => $search, 'fuzziness' => "AUTO", // "Tolérance" aux fautes, "fuzziness" => "AUTO" ] ] ], Loading @@ -156,18 +201,47 @@ class PersonElasticSearch extends ElasticSearchEngine implements IPersonISearchS "match" => [ "affectation" => [ "query" => $search, 'fuzziness' => "AUTO", // "Tolérance" aux fautes, "fuzziness" => "AUTO" ] ] ], // ---- Commence par (prefix) ---- [ "prefix" => [ "lastname" => $search, // Documents qui commencent par l'expression "lastname" => [ "value" => $search, "boost" => 10 ] ] ], [ "prefix" => [ "connectors" => $search, // Documents qui commencent par l'expression "connectors" => [ "value" => $search, "boost" => 8 ] ] ], // ---- Autocomplétion et Prénom + Nom ---- [ "match_phrase_prefix" => [ "fullname" => [ "query" => $search, "slop" => 2, // tolère inversion prénom/nom "max_expansions" => 20, "boost" => 12 ] ] ], [ "multi_match" => [ "query" => $search, "type" => "cross_fields", "fields" => ["firstname^2", "lastname^3"], "operator" => "and", "boost" => 9 ] ] ], Loading