From d9a3eadbb0fed3eb18f8d708f0ef8eba50b2a4f1 Mon Sep 17 00:00:00 2001 From: vanitha1822 Date: Fri, 20 Feb 2026 15:44:04 +0530 Subject: [PATCH 1/2] fix: enable multi-word fuzzy search requirement --- pom.xml | 2 +- .../elasticsearch/ElasticsearchService.java | 211 ++++++++++++------ 2 files changed, 138 insertions(+), 75 deletions(-) diff --git a/pom.xml b/pom.xml index 57f74646..feda3ff1 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 com.iemr.common.identity identity-api - 3.6.1 + 3.6.2 war diff --git a/src/main/java/com/iemr/common/identity/service/elasticsearch/ElasticsearchService.java b/src/main/java/com/iemr/common/identity/service/elasticsearch/ElasticsearchService.java index 800f4c6b..513bad2f 100644 --- a/src/main/java/com/iemr/common/identity/service/elasticsearch/ElasticsearchService.java +++ b/src/main/java/com/iemr/common/identity/service/elasticsearch/ElasticsearchService.java @@ -96,88 +96,147 @@ public List> universalSearch(String query, Integer userId) { .query(qq -> qq .bool(b -> { if (!isNumeric) { - // 1. EXACT MATCH (highest priority) - b.should(s1 -> s1.term(t -> t - .field("firstName.keyword") - .value(query) - .boost(20.0f))); - b.should(s2 -> s2.term(t -> t - .field("middleName.keyword") - .value(query) - .boost(20.0f))); - b.should(s3 -> s3.term(t -> t - .field("lastName.keyword") - .value(query) - .boost(20.0f))); + String[] queryWords = query.trim().split("\\s+"); + boolean isMultiWord = queryWords.length > 1; + + if (isMultiWord) { + // MULTI-WORD SEARCH: Each word must match at least one name field + // e.g., "Ravi Kumar" → "Ravi" must match firstName/middleName/lastName + // AND "Kumar" must match firstName/middleName/lastName + for (String word : queryWords) { + final String w = word; + final String wLower = word.toLowerCase(); + b.must(m -> m.bool(wb -> { + // Exact match (case-insensitive via lowercase) + wb.should(s1 -> s1.term(t -> t.field("firstName.keyword").value(wLower).boost(20.0f))); + wb.should(s2 -> s2.term(t -> t.field("middleName.keyword").value(wLower).boost(20.0f))); + wb.should(s3 -> s3.term(t -> t.field("lastName.keyword").value(wLower).boost(20.0f))); + // Prefix match on analyzed text fields (works from 1st character, case-insensitive via analyzer) + wb.should(s4 -> s4.prefix(p -> p.field("firstName").value(wLower).boost(12.0f))); + wb.should(s5 -> s5.prefix(p -> p.field("middleName").value(wLower).boost(12.0f))); + wb.should(s6 -> s6.prefix(p -> p.field("lastName").value(wLower).boost(12.0f))); + // Prefix match on keyword fields (case-insensitive) + wb.should(s7 -> s7.prefix(p -> p.field("firstName.keyword").value(wLower).caseInsensitive(true).boost(10.0f))); + wb.should(s8 -> s8.prefix(p -> p.field("middleName.keyword").value(wLower).caseInsensitive(true).boost(10.0f))); + wb.should(s9 -> s9.prefix(p -> p.field("lastName.keyword").value(wLower).caseInsensitive(true).boost(10.0f))); + // Fuzzy match (kicks in for 3+ char words) + if (w.length() >= 3) { + wb.should(s10 -> s10.match(mm -> mm.field("firstName").query(w).fuzziness("AUTO").prefixLength(1).maxExpansions(50).boost(5.0f))); + wb.should(s11 -> s11.match(mm -> mm.field("middleName").query(w).fuzziness("AUTO").prefixLength(1).maxExpansions(50).boost(5.0f))); + wb.should(s12 -> s12.match(mm -> mm.field("lastName").query(w).fuzziness("AUTO").prefixLength(1).maxExpansions(50).boost(5.0f))); + } + // Wildcard starts-with match (case-insensitive) + if (w.length() >= 2) { + wb.should(s13 -> s13.wildcard(wc -> wc.field("firstName.keyword").value(wLower + "*").caseInsensitive(true).boost(8.0f))); + wb.should(s14 -> s14.wildcard(wc -> wc.field("middleName.keyword").value(wLower + "*").caseInsensitive(true).boost(8.0f))); + wb.should(s15 -> s15.wildcard(wc -> wc.field("lastName.keyword").value(wLower + "*").caseInsensitive(true).boost(8.0f))); + } + // Contains match (case-insensitive) + if (w.length() >= 3) { + wb.should(s16 -> s16.wildcard(wc -> wc.field("firstName.keyword").value("*" + wLower + "*").caseInsensitive(true).boost(3.0f))); + wb.should(s17 -> s17.wildcard(wc -> wc.field("middleName.keyword").value("*" + wLower + "*").caseInsensitive(true).boost(3.0f))); + wb.should(s18 -> s18.wildcard(wc -> wc.field("lastName.keyword").value("*" + wLower + "*").caseInsensitive(true).boost(3.0f))); + } + wb.minimumShouldMatch("1"); + return wb; + })); + } + } else { + // SINGLE-WORD SEARCH: original logic + String queryLower = query.toLowerCase(); + + // 1. EXACT MATCH (highest priority) + b.should(s1 -> s1.term(t -> t + .field("firstName.keyword") + .value(queryLower) + .boost(20.0f))); + b.should(s2 -> s2.term(t -> t + .field("middleName.keyword") + .value(queryLower) + .boost(20.0f))); + b.should(s3 -> s3.term(t -> t + .field("lastName.keyword") + .value(queryLower) + .boost(20.0f))); // 2. PREFIX MATCH (high priority for "vani" → "vanitha") - b.should(s4 -> s4.prefix(p -> p - .field("firstName.keyword") - .value(query) - .boost(10.0f))); - b.should(s5 -> s5.prefix(p -> p - .field("middleName.keyword") - .value(query) - .boost(10.0f))); - b.should(s6 -> s6.prefix(p -> p - .field("lastName.keyword") - .value(query) - .boost(10.0f))); + b.should(s4 -> s4.prefix(p -> p + .field("firstName.keyword") + .value(queryLower) + .caseInsensitive(true) + .boost(10.0f))); + b.should(s5 -> s5.prefix(p -> p + .field("middleName.keyword") + .value(queryLower) + .caseInsensitive(true) + .boost(10.0f))); + b.should(s6 -> s6.prefix(p -> p + .field("lastName.keyword") + .value(queryLower) + .caseInsensitive(true) + .boost(10.0f))); // 3. FUZZY MATCH (for typos: "vanit" → "vanitha") // AUTO fuzziness: 1 edit for 3-5 chars, 2 edits for 6+ chars - b.should(s7 -> s7.match(m -> m - .field("firstName") - .query(query) - .fuzziness("AUTO") - .prefixLength(1) // First char must match exactly - .maxExpansions(50) - .boost(5.0f))); - b.should(s8 -> s8.match(m -> m - .field("middleName") - .query(query) - .fuzziness("AUTO") - .prefixLength(1) // First char must match exactly - .maxExpansions(50) - .boost(5.0f))); - b.should(s9 -> s9.match(m -> m - .field("lastName") - .query(query) - .fuzziness("AUTO") - .prefixLength(1) - .maxExpansions(50) - .boost(5.0f))); + b.should(s7 -> s7.match(m -> m + .field("firstName") + .query(query) + .fuzziness("AUTO") + .prefixLength(1) + .maxExpansions(50) + .boost(5.0f))); + b.should(s8 -> s8.match(m -> m + .field("middleName") + .query(query) + .fuzziness("AUTO") + .prefixLength(1) + .maxExpansions(50) + .boost(5.0f))); + b.should(s9 -> s9.match(m -> m + .field("lastName") + .query(query) + .fuzziness("AUTO") + .prefixLength(1) + .maxExpansions(50) + .boost(5.0f))); // 4. WILDCARD MATCH (for "sur*" → "suraj", "surya") - if (query.length() >= 2) { - b.should(s10 -> s10.wildcard(w -> w - .field("firstName.keyword") - .value(query + "*") - .boost(8.0f))); - b.should(s11 -> s11.wildcard(w -> w - .field("middleName.keyword") - .value(query + "*") - .boost(8.0f))); - b.should(s12 -> s12.wildcard(w -> w - .field("lastName.keyword") - .value(query + "*") - .boost(8.0f))); - } + if (query.length() >= 2) { + b.should(s10 -> s10.wildcard(w -> w + .field("firstName.keyword") + .value(queryLower + "*") + .caseInsensitive(true) + .boost(8.0f))); + b.should(s11 -> s11.wildcard(w -> w + .field("middleName.keyword") + .value(queryLower + "*") + .caseInsensitive(true) + .boost(8.0f))); + b.should(s12 -> s12.wildcard(w -> w + .field("lastName.keyword") + .value(queryLower + "*") + .caseInsensitive(true) + .boost(8.0f))); + } // 5. CONTAINS MATCH (for partial matches anywhere) - if (query.length() >= 3) { - b.should(s13 -> s13.wildcard(w -> w - .field("firstName.keyword") - .value("*" + query + "*") - .boost(3.0f))); - b.should(s14 -> s14.wildcard(w -> w - .field("middleName.keyword") - .value("*" + query + "*") - .boost(3.0f))); - b.should(s15 -> s15.wildcard(w -> w - .field("lastName.keyword") - .value("*" + query + "*") - .boost(3.0f))); + if (query.length() >= 3) { + b.should(s13 -> s13.wildcard(w -> w + .field("firstName.keyword") + .value("*" + queryLower + "*") + .caseInsensitive(true) + .boost(3.0f))); + b.should(s14 -> s14.wildcard(w -> w + .field("middleName.keyword") + .value("*" + queryLower + "*") + .caseInsensitive(true) + .boost(3.0f))); + b.should(s15 -> s15.wildcard(w -> w + .field("lastName.keyword") + .value("*" + queryLower + "*") + .caseInsensitive(true) + .boost(3.0f))); + } } } @@ -239,7 +298,11 @@ public List> universalSearch(String query, Integer userId) { } } - b.minimumShouldMatch("1"); + // For multi-word name queries, must clauses handle the AND logic + // For single-word or numeric queries, at least one should must match + if (isNumeric || !query.trim().contains(" ")) { + b.minimumShouldMatch("1"); + } return b; })) .functions(getFunctionScores(userLocation)) From f59737836b9284e0b8b1482437e51a24a92bc2c7 Mon Sep 17 00:00:00 2001 From: Vanitha S <116701245+vanitha1822@users.noreply.github.com> Date: Fri, 20 Feb 2026 17:03:24 +0530 Subject: [PATCH 2/2] Downgrade version from 3.6.2 to 3.6.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index feda3ff1..57f74646 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 com.iemr.common.identity identity-api - 3.6.2 + 3.6.1 war