From dbb0a7955c67529483435cb57581b082a5bdd432 Mon Sep 17 00:00:00 2001 From: vanitha1822 Date: Tue, 13 Jan 2026 17:04:05 +0530 Subject: [PATCH 1/2] fix: optimize the index and reduce the size --- .../ElasticsearchIndexingService.java | 97 +++- .../elasticsearch/ElasticsearchService.java | 442 +++++++----------- src/main/resources/application.properties | 34 ++ 3 files changed, 290 insertions(+), 283 deletions(-) diff --git a/src/main/java/com/iemr/common/identity/service/elasticsearch/ElasticsearchIndexingService.java b/src/main/java/com/iemr/common/identity/service/elasticsearch/ElasticsearchIndexingService.java index 941a055..458dedf 100644 --- a/src/main/java/com/iemr/common/identity/service/elasticsearch/ElasticsearchIndexingService.java +++ b/src/main/java/com/iemr/common/identity/service/elasticsearch/ElasticsearchIndexingService.java @@ -3,6 +3,9 @@ import co.elastic.clients.elasticsearch.ElasticsearchClient; import co.elastic.clients.elasticsearch._types.mapping.*; import co.elastic.clients.elasticsearch.indices.CreateIndexRequest; +import co.elastic.clients.elasticsearch.indices.IndexSettings; +import co.elastic.clients.elasticsearch.indices.TranslogDurability; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -27,7 +30,7 @@ public class ElasticsearchIndexingService { private String beneficiaryIndex; /** - * Create or recreate the Elasticsearch index with proper mapping + */ public void createIndexWithMapping() throws Exception { logger.info("Creating index with mapping: {}", beneficiaryIndex); @@ -38,24 +41,71 @@ public void createIndexWithMapping() throws Exception { esClient.indices().delete(d -> d.index(beneficiaryIndex)); } - // Create index with mapping + IndexSettings settings = IndexSettings.of(s -> s + .refreshInterval(t -> t.time("30s")) + + .numberOfShards("3") + + .numberOfReplicas("1") + + .queries(q -> q + .cache(c -> c.enabled(true)) + ) + + .maxResultWindow(10000) + + .translog(t -> t + .durability(TranslogDurability.Async) + .syncInterval(ts -> ts.time("30s")) + ) + ); + TypeMapping mapping = TypeMapping.of(tm -> tm .properties("benId", Property.of(p -> p.keyword(k -> k))) .properties("benRegId", Property.of(p -> p.long_(l -> l))) + .properties("beneficiaryID", Property.of(p -> p.keyword(k -> k))) + .properties("firstName", Property.of(p -> p.text(t -> t - .fields("keyword", Property.of(fp -> fp.keyword(k -> k)))))) + .analyzer("standard") + .fields("keyword", Property.of(fp -> fp.keyword(k -> k.ignoreAbove(256)))) + .fields("prefix", Property.of(fp -> fp.text(txt -> txt + .analyzer("standard") + .indexPrefixes(ip -> ip.minChars(2).maxChars(5)) // Fast prefix search + ))) + ))) + .properties("lastName", Property.of(p -> p.text(t -> t - .fields("keyword", Property.of(fp -> fp.keyword(k -> k)))))) + .analyzer("standard") + .fields("keyword", Property.of(fp -> fp.keyword(k -> k.ignoreAbove(256)))) + .fields("prefix", Property.of(fp -> fp.text(txt -> txt + .analyzer("standard") + .indexPrefixes(ip -> ip.minChars(2).maxChars(5)) + ))) + ))) + + .properties("fatherName", Property.of(p -> p.text(t -> t + .analyzer("standard") + .fields("keyword", Property.of(fp -> fp.keyword(k -> k.ignoreAbove(256)))) + ))) + + .properties("spouseName", Property.of(p -> p.text(t -> t + .analyzer("standard") + .fields("keyword", Property.of(fp -> fp.keyword(k -> k.ignoreAbove(256)))) + ))) + .properties("genderID", Property.of(p -> p.integer(i -> i))) .properties("genderName", Property.of(p -> p.keyword(k -> k))) - .properties("dOB", Property.of(p -> p.date(d -> d))) + .properties("dOB", Property.of(p -> p.date(d -> d.format("strict_date_optional_time||epoch_millis")))) .properties("age", Property.of(p -> p.integer(i -> i))) - .properties("phoneNum", Property.of(p -> p.keyword(k -> k))) - .properties("fatherName", Property.of(p -> p.text(t -> t - .fields("keyword", Property.of(fp -> fp.keyword(k -> k)))))) - .properties("spouseName", Property.of(p -> p.text(t -> t - .fields("keyword", Property.of(fp -> fp.keyword(k -> k)))))) + + .properties("phoneNum", Property.of(p -> p.keyword(k -> k + .fields("ngram", Property.of(fp -> fp.text(txt -> txt + .analyzer("standard") + .searchAnalyzer("standard") + ))) + ))) + .properties("isHIVPos", Property.of(p -> p.keyword(k -> k))) .properties("createdBy", Property.of(p -> p.keyword(k -> k))) .properties("createdDate", Property.of(p -> p.date(d -> d))) @@ -94,15 +144,37 @@ public void createIndexWithMapping() throws Exception { esClient.indices().create(c -> c .index(beneficiaryIndex) + .settings(settings) .mappings(mapping) ); logger.info("Index created successfully: {}", beneficiaryIndex); } + /** + * Reset refresh interval after bulk indexing completes + * Call this after syncAllBeneficiaries() finishes + */ + public void optimizeForSearch() throws Exception { + logger.info("Optimizing index for search performance..."); + + esClient.indices().putSettings(s -> s + .index(beneficiaryIndex) + .settings(is -> is + .refreshInterval(t -> t.time("1s")) + .translog(t -> t.durability(TranslogDurability.Request)) + ) + ); + + esClient.indices().forcemerge(f -> f + .index(beneficiaryIndex) + .maxNumSegments(1L) // Optimal for read-heavy workloads + ); + + } + /** * Index all beneficiaries - delegates to existing sync service - * This is much safer than loading all records at once */ public Map indexAllBeneficiaries() { logger.info("Starting full indexing via sync service..."); @@ -110,6 +182,9 @@ public Map indexAllBeneficiaries() { try { ElasticsearchSyncService.SyncResult result = syncService.syncAllBeneficiaries(); + // After indexing completes, optimize for search + optimizeForSearch(); + Map response = new HashMap<>(); response.put("success", result.getSuccessCount()); response.put("failed", result.getFailureCount()); 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 a8b10ae..89fa591 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 @@ -20,14 +20,10 @@ import org.springframework.stereotype.Service; import java.math.BigDecimal; -import java.math.BigInteger; import java.sql.Timestamp; import java.util.*; import java.util.stream.Collectors; import co.elastic.clients.elasticsearch._types.SortOrder; -import co.elastic.clients.elasticsearch._types.query_dsl.FunctionScore; -import co.elastic.clients.elasticsearch._types.query_dsl.FunctionScoreMode; -import co.elastic.clients.elasticsearch._types.query_dsl.FunctionBoostMode; import com.iemr.common.identity.repo.BenAddressRepo; @Service @@ -58,82 +54,96 @@ public class ElasticsearchService { public List> universalSearch(String query, Integer userId) { try { final Map userLocation = - (userId != null) ? getUserLocation(userId) : null; - + (userId != null) ? getUserLocation(userId) : null; boolean isNumeric = query.matches("\\d+"); - - // Determine minimum score threshold based on query type - double minScore = isNumeric ? 1.0 : 3.0; + double minScore = isNumeric ? 1.0 : 2.0; SearchResponse response = esClient.search(s -> s .index(beneficiaryIndex) + + .preference("_local") + + .requestCache(true) + .query(q -> q .functionScore(fs -> fs .query(qq -> qq .bool(b -> { if (!isNumeric) { - // Name searches with higher boost for exact matches + // OPTIMIZED NAME SEARCH + // Use match_phrase_prefix for faster prefix matching b.should(s1 -> s1.multiMatch(mm -> mm .query(query) - .fields("firstName^3", "lastName^3", "fatherName^2", "spouseName^2") - .type(TextQueryType.BestFields) - .fuzziness("AUTO") + .fields("firstName^3", "lastName^3") + .type(TextQueryType.Phrase) + .boost(3.0f) + )); + + b.should(s2 -> s2.term(t -> t + .field("firstName.keyword") + .value(query) + .boost(10.0f) + )); + b.should(s3 -> s3.term(t -> t + .field("lastName.keyword") + .value(query) + .boost(10.0f) + )); + + // Prefix search using index_prefixes (FAST!) + b.should(s4 -> s4.match(m -> m + .field("firstName.prefix") + .query(query) + .boost(2.0f) + )); + b.should(s5 -> s5.match(m -> m + .field("lastName.prefix") + .query(query) + .boost(2.0f) )); - - // Exact keyword matches get highest boost - b.should(s2 -> s2.term(t -> t.field("firstName.keyword").value(query).boost(5.0f))); - b.should(s3 -> s3.term(t -> t.field("lastName.keyword").value(query).boost(5.0f))); - b.should(s4 -> s4.term(t -> t.field("fatherName.keyword").value(query).boost(4.0f))); - b.should(s5 -> s5.term(t -> t.field("spouseName.keyword").value(query).boost(4.0f))); } - // ID searches - exact matches get very high boost - b.should(s6 -> s6.term(t -> t.field("healthID").value(query).boost(10.0f))); - b.should(s7 -> s7.term(t -> t.field("abhaID").value(query).boost(10.0f))); - b.should(s8 -> s8.term(t -> t.field("familyID").value(query).boost(8.0f))); - b.should(s9 -> s9.term(t -> t.field("beneficiaryID").value(query).boost(10.0f))); - b.should(s10 -> s10.term(t -> t.field("benId").value(query).boost(10.0f))); - b.should(s11 -> s11.term(t -> t.field("aadharNo").value(query).boost(9.0f))); - b.should(s12 -> s12.term(t -> t.field("govtIdentityNo").value(query).boost(8.0f))); + b.should(s6 -> s6.term(t -> t.field("healthID").value(query).boost(15.0f))); + b.should(s7 -> s7.term(t -> t.field("abhaID").value(query).boost(15.0f))); + b.should(s8 -> s8.term(t -> t.field("beneficiaryID").value(query).boost(15.0f))); + b.should(s9 -> s9.term(t -> t.field("benId").value(query).boost(15.0f))); + b.should(s10 -> s10.term(t -> t.field("aadharNo").value(query).boost(12.0f))); if (isNumeric) { - // Partial matches for numeric fields - b.should(s13 -> s13.wildcard(w -> w.field("phoneNum").value("*" + query + "*").boost(3.0f))); - b.should(s14 -> s14.wildcard(w -> w.field("healthID").value("*" + query + "*").boost(2.0f))); - b.should(s15 -> s15.wildcard(w -> w.field("abhaID").value("*" + query + "*").boost(2.0f))); - b.should(s16 -> s16.wildcard(w -> w.field("familyID").value("*" + query + "*").boost(2.0f))); - b.should(s17 -> s17.wildcard(w -> w.field("beneficiaryID").value("*" + query + "*").boost(2.0f))); - b.should(s18 -> s18.wildcard(w -> w.field("benId").value("*" + query + "*").boost(2.0f))); - b.should(s19 -> s19.wildcard(w -> w.field("aadharNo").value("*" + query + "*").boost(2.0f))); - b.should(s20 -> s20.wildcard(w -> w.field("govtIdentityNo").value("*" + query + "*").boost(2.0f))); + // PREFIX QUERIES (much faster than wildcard) + b.should(s11 -> s11.prefix(p -> p.field("phoneNum").value(query).boost(5.0f))); + b.should(s12 -> s12.prefix(p -> p.field("healthID").value(query).boost(4.0f))); + b.should(s13 -> s13.prefix(p -> p.field("abhaID").value(query).boost(4.0f))); + b.should(s14 -> s14.prefix(p -> p.field("beneficiaryID").value(query).boost(4.0f))); + + // ONLY use wildcard if query is long enough (>= 4 digits) + if (query.length() >= 4) { + b.should(s15 -> s15.wildcard(w -> w + .field("phoneNum") + .value("*" + query + "*") + .boost(2.0f) + )); + } - // Prefix matches - b.should(s21 -> s21.prefix(p -> p.field("phoneNum").value(query).boost(4.0f))); - b.should(s22 -> s22.prefix(p -> p.field("healthID").value(query).boost(3.0f))); - b.should(s23 -> s23.prefix(p -> p.field("abhaID").value(query).boost(3.0f))); - b.should(s24 -> s24.prefix(p -> p.field("familyID").value(query).boost(3.0f))); - b.should(s25 -> s25.prefix(p -> p.field("beneficiaryID").value(query).boost(3.0f))); - b.should(s26 -> s26.prefix(p -> p.field("benId").value(query).boost(3.0f))); - try { Long numericValue = Long.parseLong(query); - b.should(s27 -> s27.term(t -> t.field("benRegId").value(numericValue).boost(10.0f))); - b.should(s28 -> s28.term(t -> t.field("benAccountID").value(numericValue).boost(8.0f))); + b.should(s16 -> s16.term(t -> t.field("benRegId").value(numericValue).boost(15.0f))); + b.should(s17 -> s17.term(t -> t.field("benAccountID").value(numericValue).boost(10.0f))); int intValue = numericValue.intValue(); - b.should(s29 -> s29.term(t -> t.field("genderID").value(intValue).boost(2.0f))); - b.should(s30 -> s30.term(t -> t.field("age").value(intValue).boost(1.0f))); - b.should(s31 -> s31.term(t -> t.field("stateID").value(intValue).boost(1.0f))); - b.should(s32 -> s32.term(t -> t.field("districtID").value(intValue).boost(1.0f))); - b.should(s33 -> s33.term(t -> t.field("blockID").value(intValue).boost(1.0f))); - b.should(s34 -> s34.term(t -> t.field("villageID").value(intValue).boost(1.0f))); - b.should(s35 -> s35.term(t -> t.field("servicePointID").value(intValue).boost(1.0f))); - b.should(s36 -> s36.term(t -> t.field("parkingPlaceID").value(intValue).boost(1.0f))); - - logger.info("Added numeric searches for value: {}", numericValue); + if (userLocation != null) { + Integer userVillageId = userLocation.get("villageId"); + Integer userBlockId = userLocation.get("blockId"); + + if (userVillageId != null && userVillageId == intValue) { + b.should(s18 -> s18.term(t -> t.field("villageID").value(intValue).boost(3.0f))); + } + if (userBlockId != null && userBlockId == intValue) { + b.should(s19 -> s19.term(t -> t.field("blockID").value(intValue).boost(2.0f))); + } + } } catch (NumberFormatException e) { - logger.warn("Failed to parse numeric value: {}", query); } } @@ -141,51 +151,55 @@ public List> universalSearch(String query, Integer userId) { return b; }) ) - // Add location-based scoring if user location is available .functions(getFunctionScores(userLocation)) .scoreMode(FunctionScoreMode.Sum) .boostMode(FunctionBoostMode.Multiply) + .maxBoost(5.0) ) ) - .minScore(minScore) - .size(500) - .sort(so -> so - .score(sc -> sc.order(SortOrder.Desc)) + .minScore(minScore) + + .size(100) // Reduced from 500 + + .sort(so -> so.score(sc -> sc.order(SortOrder.Desc))) + + .source(src -> src + .filter(f -> f + .includes("benRegId", "beneficiaryID", "firstName", "lastName", + "genderID", "genderName", "dOB", "phoneNum", + "stateID", "districtID", "blockID", "villageID") + ) ) + + , BeneficiariesESDTO.class); - logger.info("ES returned {} hits for query: '{}' (min score: {})", - response.hits().hits().size(), query, minScore); + logger.info("ES returned {} hits in {}ms for query: '{}'", + response.hits().hits().size(), + response.took(), + query); + + if (response.hits().hits().isEmpty()) { + logger.info("No results in ES, using database fallback"); + return searchInDatabaseDirectly(query); + } - List> allResults = response.hits().hits().stream() + List> results = response.hits().hits().stream() .map(hit -> { - if (hit.source() != null) { - logger.debug("Hit score: {}, benRegId: {}, name: {} {}", - hit.score(), - hit.source().getBenRegId(), - hit.source().getFirstName(), - hit.source().getLastName()); - } Map result = mapESResultToExpectedFormat(hit.source()); if (result != null) { - result.put("_score", hit.score()); + result.put("_score", hit.score()); } return result; }) .filter(Objects::nonNull) .collect(Collectors.toList()); - if (allResults.isEmpty()) { - logger.info("No results found in ES, falling back to database"); - return searchInDatabaseDirectly(query); - } - - logger.info("Returning {} matched results", allResults.size()); - return allResults; + logger.info("Returning {} results", results.size()); + return results; } catch (Exception e) { - logger.error("ES universal search failed: {}", e.getMessage(), e); - logger.info("Fallback: Searching in MySQL database"); + logger.error("ES search failed: {}", e.getMessage()); return searchInDatabaseDirectly(query); } } @@ -203,19 +217,19 @@ private List getFunctionScores(Map userLocation) Integer userVillageId = userLocation.get("villageId"); Integer userBlockId = userLocation.get("blockId"); - // Village match - highest boost + // Village match if (userVillageId != null) { scores.add(FunctionScore.of(f -> f .filter(ff -> ff.term(t -> t.field("villageID").value(userVillageId))) - .weight(3.0) + .weight(2.0) )); } - // Block match - medium boost + // Block match if (userBlockId != null) { scores.add(FunctionScore.of(f -> f .filter(ff -> ff.term(t -> t.field("blockID").value(userBlockId))) - .weight(2.0) + .weight(1.5) )); } @@ -223,7 +237,7 @@ private List getFunctionScores(Map userLocation) } /** - * Advanced search with multiple criteria - only returns actual matches + * Advanced search with filter context */ public List> advancedSearch( String firstName, @@ -243,209 +257,93 @@ public List> advancedSearch( Integer userId) { try { - logger.info("ES Advanced Search - firstName: {}, lastName: {}, genderId: {}, stateId: {}, districtId: {}, blockId: {}, villageId: {}", - firstName, lastName, genderId, stateId, districtId, blockId, villageId); - final Map userLocation = - (userId != null) ? getUserLocation(userId) : null; - + (userId != null) ? getUserLocation(userId) : null; SearchResponse response = esClient.search(s -> s .index(beneficiaryIndex) + .preference("_local") + .requestCache(true) + .query(q -> q - .functionScore(fs -> fs - .query(qq -> qq - .bool(b -> { - // Name searches with fuzzy matching and boost - if (firstName != null && !firstName.trim().isEmpty()) { - b.must(m -> m.bool(bb -> bb - .should(s1 -> s1.match(mm -> mm - .field("firstName") - .query(firstName) - .fuzziness("AUTO") - .boost(2.0f) - )) - .should(s2 -> s2.term(t -> t - .field("firstName.keyword") - .value(firstName) - .boost(5.0f) - )) - .minimumShouldMatch("1") - )); - } - - if (lastName != null && !lastName.trim().isEmpty()) { - b.must(m -> m.bool(bb -> bb - .should(s1 -> s1.match(mm -> mm - .field("lastName") - .query(lastName) - .fuzziness("AUTO") - .boost(2.0f) - )) - .should(s2 -> s2.term(t -> t - .field("lastName.keyword") - .value(lastName) - .boost(5.0f) - )) - .minimumShouldMatch("1") - )); - } - - if (fatherName != null && !fatherName.trim().isEmpty()) { - b.must(m -> m.match(mm -> mm - .field("fatherName") - .query(fatherName) - .fuzziness("AUTO") - .boost(2.0f) - )); - } - - if (spouseName != null && !spouseName.trim().isEmpty()) { - b.must(m -> m.match(mm -> mm - .field("spouseName") - .query(spouseName) - .fuzziness("AUTO") - .boost(2.0f) - )); - } - - // Exact matches for IDs and structured data - if (genderId != null) { - b.filter(m -> m.term(t -> t - .field("genderID") - .value(genderId) - )); - } - - if (dob != null) { - b.must(m -> m.term(t -> t - .field("dob") - .value(dob.getTime()) - .boost(5.0f) - )); - } - - // Location filters - if (stateId != null) { - b.filter(m -> m.term(t -> t - .field("stateID") - .value(stateId) - )); - } - - if (districtId != null) { - b.filter(m -> m.term(t -> t - .field("districtID") - .value(districtId) - )); - } - - if (blockId != null) { - b.filter(m -> m.term(t -> t - .field("blockID") - .value(blockId) - )); - } - - if (villageId != null) { - b.must(m -> m.term(t -> t - .field("villageID") - .value(villageId) - .boost(3.0f) - )); - } - - // Identity searches - if (phoneNumber != null && !phoneNumber.trim().isEmpty()) { - b.must(m -> m.bool(bb -> bb - .should(s1 -> s1.term(t -> t - .field("phoneNum") - .value(phoneNumber) - .boost(5.0f) - )) - .should(s2 -> s2.wildcard(w -> w - .field("phoneNum") - .value("*" + phoneNumber + "*") - .boost(2.0f) - )) - .minimumShouldMatch("1") - )); - } - - if (beneficiaryId != null && !beneficiaryId.trim().isEmpty()) { - b.must(m -> m.term(t -> t - .field("beneficiaryID") - .value(beneficiaryId) - .boost(10.0f) - )); - } - - if (healthId != null && !healthId.trim().isEmpty()) { - b.must(m -> m.bool(bb -> bb - .should(s1 -> s1.term(t -> t - .field("healthID") - .value(healthId) - .boost(10.0f) - )) - .should(s2 -> s2.term(t -> t - .field("abhaID") - .value(healthId) - .boost(10.0f) - )) - .minimumShouldMatch("1") - )); - } - - if (aadharNo != null && !aadharNo.trim().isEmpty()) { - b.must(m -> m.term(t -> t - .field("aadharNo") - .value(aadharNo) - .boost(10.0f) - )); - } - - return b; - }) - ) - // Add location-based scoring - .functions(getFunctionScores(userLocation)) - .scoreMode(FunctionScoreMode.Sum) - .boostMode(FunctionBoostMode.Multiply) - ) - ) - .minScore(2.0) - .size(500) - .sort(so -> so - .score(sc -> sc.order(SortOrder.Desc)) + .bool(b -> { + // Use FILTER context for exact matches (faster, cached) + if (genderId != null) { + b.filter(f -> f.term(t -> t.field("genderID").value(genderId))); + } + if (stateId != null) { + b.filter(f -> f.term(t -> t.field("stateID").value(stateId))); + } + if (districtId != null) { + b.filter(f -> f.term(t -> t.field("districtID").value(districtId))); + } + if (blockId != null) { + b.filter(f -> f.term(t -> t.field("blockID").value(blockId))); + } + if (villageId != null) { + b.filter(f -> f.term(t -> t.field("villageID").value(villageId))); + } + + // MUST context for scored searches + if (firstName != null && !firstName.trim().isEmpty()) { + b.must(m -> m.bool(bb -> bb + .should(s1 -> s1.term(t -> t.field("firstName.keyword").value(firstName).boost(5.0f))) + .should(s2 -> s2.match(mm -> mm.field("firstName").query(firstName).boost(2.0f))) + .minimumShouldMatch("1") + )); + } + + if (lastName != null && !lastName.trim().isEmpty()) { + b.must(m -> m.bool(bb -> bb + .should(s1 -> s1.term(t -> t.field("lastName.keyword").value(lastName).boost(5.0f))) + .should(s2 -> s2.match(mm -> mm.field("lastName").query(lastName).boost(2.0f))) + .minimumShouldMatch("1") + )); + } + + // Exact match IDs in filter context + if (beneficiaryId != null && !beneficiaryId.trim().isEmpty()) { + b.filter(f -> f.term(t -> t.field("beneficiaryID").value(beneficiaryId))); + } + if (healthId != null && !healthId.trim().isEmpty()) { + b.filter(f -> f.bool(bb -> bb + .should(s1 -> s1.term(t -> t.field("healthID").value(healthId))) + .should(s2 -> s2.term(t -> t.field("abhaID").value(healthId))) + .minimumShouldMatch("1") + )); + } + if (aadharNo != null && !aadharNo.trim().isEmpty()) { + b.filter(f -> f.term(t -> t.field("aadharNo").value(aadharNo))); + } + if (phoneNumber != null && !phoneNumber.trim().isEmpty()) { + b.must(m -> m.bool(bb -> bb + .should(s1 -> s1.term(t -> t.field("phoneNum").value(phoneNumber).boost(3.0f))) + .should(s2 -> s2.prefix(p -> p.field("phoneNum").value(phoneNumber).boost(2.0f))) + .minimumShouldMatch("1") + )); + } + + return b; + }) ) + .size(100) + .sort(so -> so.score(sc -> sc.order(SortOrder.Desc))) + , BeneficiariesESDTO.class); - logger.info("ES advanced search returned {} hits", response.hits().hits().size()); - if (response.hits().hits().isEmpty()) { - logger.info("No results in ES, falling back to database"); return searchInDatabaseForAdvanced(firstName, lastName, genderId, dob, stateId, districtId, blockId, villageId, fatherName, spouseName, phoneNumber, beneficiaryId, healthId, aadharNo); } - List> results = response.hits().hits().stream() - .map(hit -> { - Map result = mapESResultToExpectedFormat(hit.source()); - if (result != null) { - result.put("_score", hit.score()); // Include score - } - return result; - }) + return response.hits().hits().stream() + .map(hit -> mapESResultToExpectedFormat(hit.source())) .filter(Objects::nonNull) .collect(Collectors.toList()); - logger.info("Returning {} matched results", results.size()); - return results; - } catch (Exception e) { - logger.error("ES advanced search failed: {}", e.getMessage(), e); - logger.info("Fallback: Searching in MySQL database"); + logger.error("ES advanced search failed: {}", e.getMessage()); return searchInDatabaseForAdvanced(firstName, lastName, genderId, dob, stateId, districtId, blockId, villageId, fatherName, spouseName, phoneNumber, beneficiaryId, healthId, aadharNo); diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 22a9ff6..8774780 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -78,6 +78,40 @@ jwt.refresh.expiration=604800000 # Connection pool settings elasticsearch.connection.timeout=5000 elasticsearch.socket.timeout=60000 +elasticsearch.max.retry.timeout=60000 + +# Connection pooling +elasticsearch.max.connections=100 +elasticsearch.max.connections.per.route=50 + +# Request Configuration +elasticsearch.request.timeout=30000 +elasticsearch.max.result.window=10000 + +# Bulk Indexing Performance +elasticsearch.bulk.size=100 +elasticsearch.bulk.concurrent.requests=4 +elasticsearch.bulk.flush.interval=10s + +# Search Performance +elasticsearch.search.default.size=100 +elasticsearch.search.max.size=500 +elasticsearch.search.timeout=5s + +# Query Cache Settings +elasticsearch.query.cache.enabled=true +elasticsearch.query.cache.size=10% + +# Request Cache Settings +elasticsearch.request.cache.enabled=true + +# Circuit Breaker (Prevent OOM) +elasticsearch.circuit.breaker.enabled=true +elasticsearch.circuit.breaker.limit=95% + +# Thread Pool for Async Operations +elasticsearch.async.thread.pool.size=10 +elasticsearch.async.thread.pool.queue.size=1000 # Logging logging.level.com.iemr.common.identity.service.elasticsearch=INFO From 50d123c1828f2f2ef261c8532ae258f0375588b1 Mon Sep 17 00:00:00 2001 From: vanitha1822 Date: Tue, 13 Jan 2026 20:01:09 +0530 Subject: [PATCH 2/2] fix: align indent --- .../elasticsearch/ElasticsearchService.java | 835 +++++++++--------- 1 file changed, 420 insertions(+), 415 deletions(-) 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 89fa591..430392c 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 @@ -7,10 +7,7 @@ import co.elastic.clients.elasticsearch._types.query_dsl.TextQueryType; import co.elastic.clients.elasticsearch.core.SearchResponse; -import com.iemr.common.identity.domain.Address; -import com.iemr.common.identity.dto.BeneficiariesDTO; import com.iemr.common.identity.dto.BeneficiariesESDTO; -import com.iemr.common.identity.dto.IdentitySearchDTO; import com.iemr.common.identity.repo.BenDetailRepo; import org.slf4j.Logger; @@ -28,354 +25,349 @@ @Service public class ElasticsearchService { - + private static final Logger logger = LoggerFactory.getLogger(ElasticsearchService.class); - + @Autowired private ElasticsearchClient esClient; - + @Autowired private BenDetailRepo benDetailRepo; @Autowired private BenAddressRepo benAddressRepo; - + @Value("${elasticsearch.index.beneficiary}") private String beneficiaryIndex; - + @Value("${elasticsearch.enabled}") private boolean esEnabled; - -/** - * Universal search with score-based filtering and location ranking - * Only returns records that actually match the query (not all 10000) - */ -public List> universalSearch(String query, Integer userId) { - try { - final Map userLocation = - (userId != null) ? getUserLocation(userId) : null; - - boolean isNumeric = query.matches("\\d+"); - double minScore = isNumeric ? 1.0 : 2.0; - - SearchResponse response = esClient.search(s -> s - .index(beneficiaryIndex) - - .preference("_local") - - .requestCache(true) - - .query(q -> q - .functionScore(fs -> fs - .query(qq -> qq - .bool(b -> { - if (!isNumeric) { - // OPTIMIZED NAME SEARCH - // Use match_phrase_prefix for faster prefix matching - b.should(s1 -> s1.multiMatch(mm -> mm - .query(query) - .fields("firstName^3", "lastName^3") - .type(TextQueryType.Phrase) - .boost(3.0f) - )); - - b.should(s2 -> s2.term(t -> t - .field("firstName.keyword") - .value(query) - .boost(10.0f) - )); - b.should(s3 -> s3.term(t -> t - .field("lastName.keyword") - .value(query) - .boost(10.0f) - )); - - // Prefix search using index_prefixes (FAST!) - b.should(s4 -> s4.match(m -> m - .field("firstName.prefix") - .query(query) - .boost(2.0f) - )); - b.should(s5 -> s5.match(m -> m - .field("lastName.prefix") - .query(query) - .boost(2.0f) - )); - } - - b.should(s6 -> s6.term(t -> t.field("healthID").value(query).boost(15.0f))); - b.should(s7 -> s7.term(t -> t.field("abhaID").value(query).boost(15.0f))); - b.should(s8 -> s8.term(t -> t.field("beneficiaryID").value(query).boost(15.0f))); - b.should(s9 -> s9.term(t -> t.field("benId").value(query).boost(15.0f))); - b.should(s10 -> s10.term(t -> t.field("aadharNo").value(query).boost(12.0f))); - - if (isNumeric) { - // PREFIX QUERIES (much faster than wildcard) - b.should(s11 -> s11.prefix(p -> p.field("phoneNum").value(query).boost(5.0f))); - b.should(s12 -> s12.prefix(p -> p.field("healthID").value(query).boost(4.0f))); - b.should(s13 -> s13.prefix(p -> p.field("abhaID").value(query).boost(4.0f))); - b.should(s14 -> s14.prefix(p -> p.field("beneficiaryID").value(query).boost(4.0f))); - - // ONLY use wildcard if query is long enough (>= 4 digits) - if (query.length() >= 4) { - b.should(s15 -> s15.wildcard(w -> w - .field("phoneNum") - .value("*" + query + "*") - .boost(2.0f) - )); - } - - try { - Long numericValue = Long.parseLong(query); - b.should(s16 -> s16.term(t -> t.field("benRegId").value(numericValue).boost(15.0f))); - b.should(s17 -> s17.term(t -> t.field("benAccountID").value(numericValue).boost(10.0f))); - - int intValue = numericValue.intValue(); - if (userLocation != null) { - Integer userVillageId = userLocation.get("villageId"); - Integer userBlockId = userLocation.get("blockId"); - - if (userVillageId != null && userVillageId == intValue) { - b.should(s18 -> s18.term(t -> t.field("villageID").value(intValue).boost(3.0f))); - } - if (userBlockId != null && userBlockId == intValue) { - b.should(s19 -> s19.term(t -> t.field("blockID").value(intValue).boost(2.0f))); - } - } - } catch (NumberFormatException e) { - } - } - - b.minimumShouldMatch("1"); - return b; - }) - ) - .functions(getFunctionScores(userLocation)) - .scoreMode(FunctionScoreMode.Sum) - .boostMode(FunctionBoostMode.Multiply) - .maxBoost(5.0) - ) - ) - .minScore(minScore) - - .size(100) // Reduced from 500 - - .sort(so -> so.score(sc -> sc.order(SortOrder.Desc))) - - .source(src -> src - .filter(f -> f - .includes("benRegId", "beneficiaryID", "firstName", "lastName", - "genderID", "genderName", "dOB", "phoneNum", - "stateID", "districtID", "blockID", "villageID") - ) - ) - - - , BeneficiariesESDTO.class); - - logger.info("ES returned {} hits in {}ms for query: '{}'", - response.hits().hits().size(), - response.took(), - query); - - if (response.hits().hits().isEmpty()) { - logger.info("No results in ES, using database fallback"); + /** + * Universal search with score-based filtering and location ranking + * Only returns records that actually match the query (not all 10000) + */ + public List> universalSearch(String query, Integer userId) { + try { + final Map userLocation = (userId != null) ? getUserLocation(userId) : null; + + boolean isNumeric = query.matches("\\d+"); + double minScore = isNumeric ? 1.0 : 2.0; + + SearchResponse response = esClient.search(s -> s + .index(beneficiaryIndex) + + .preference("_local") + + .requestCache(true) + + .query(q -> q + .functionScore(fs -> fs + .query(qq -> qq + .bool(b -> { + if (!isNumeric) { + // OPTIMIZED NAME SEARCH + // Use match_phrase_prefix for faster prefix matching + b.should(s1 -> s1.multiMatch(mm -> mm + .query(query) + .fields("firstName^3", "lastName^3") + .type(TextQueryType.Phrase) + .boost(3.0f))); + + b.should(s2 -> s2.term(t -> t + .field("firstName.keyword") + .value(query) + .boost(10.0f))); + b.should(s3 -> s3.term(t -> t + .field("lastName.keyword") + .value(query) + .boost(10.0f))); + + // Prefix search using index_prefixes (FAST!) + b.should(s4 -> s4.match(m -> m + .field("firstName.prefix") + .query(query) + .boost(2.0f))); + b.should(s5 -> s5.match(m -> m + .field("lastName.prefix") + .query(query) + .boost(2.0f))); + } + + b.should(s6 -> s6 + .term(t -> t.field("healthID").value(query).boost(15.0f))); + b.should(s7 -> s7 + .term(t -> t.field("abhaID").value(query).boost(15.0f))); + b.should(s8 -> s8 + .term(t -> t.field("beneficiaryID").value(query).boost(15.0f))); + b.should( + s9 -> s9.term(t -> t.field("benId").value(query).boost(15.0f))); + b.should(s10 -> s10 + .term(t -> t.field("aadharNo").value(query).boost(12.0f))); + + if (isNumeric) { + // PREFIX QUERIES (much faster than wildcard) + b.should(s11 -> s11 + .prefix(p -> p.field("phoneNum").value(query).boost(5.0f))); + b.should(s12 -> s12 + .prefix(p -> p.field("healthID").value(query).boost(4.0f))); + b.should(s13 -> s13 + .prefix(p -> p.field("abhaID").value(query).boost(4.0f))); + b.should(s14 -> s14.prefix( + p -> p.field("beneficiaryID").value(query).boost(4.0f))); + + // ONLY use wildcard if query is long enough (>= 4 digits) + if (query.length() >= 4) { + b.should(s15 -> s15.wildcard(w -> w + .field("phoneNum") + .value("*" + query + "*") + .boost(2.0f))); + } + + try { + Long numericValue = Long.parseLong(query); + b.should(s16 -> s16.term(t -> t.field("benRegId") + .value(numericValue).boost(15.0f))); + b.should(s17 -> s17.term(t -> t.field("benAccountID") + .value(numericValue).boost(10.0f))); + + int intValue = numericValue.intValue(); + if (userLocation != null) { + Integer userVillageId = userLocation.get("villageId"); + Integer userBlockId = userLocation.get("blockId"); + + if (userVillageId != null && userVillageId == intValue) { + b.should(s18 -> s18.term(t -> t.field("villageID") + .value(intValue).boost(3.0f))); + } + if (userBlockId != null && userBlockId == intValue) { + b.should(s19 -> s19.term(t -> t.field("blockID") + .value(intValue).boost(2.0f))); + } + } + } catch (NumberFormatException e) { + } + } + + b.minimumShouldMatch("1"); + return b; + })) + .functions(getFunctionScores(userLocation)) + .scoreMode(FunctionScoreMode.Sum) + .boostMode(FunctionBoostMode.Multiply) + .maxBoost(5.0))) + .minScore(minScore) + + .size(100) // Reduced from 500 + + .sort(so -> so.score(sc -> sc.order(SortOrder.Desc))) + + .source(src -> src + .filter(f -> f + .includes("benRegId", "beneficiaryID", "firstName", "lastName", + "genderID", "genderName", "dOB", "phoneNum", + "stateID", "districtID", "blockID", "villageID"))) + + , BeneficiariesESDTO.class); + + logger.info("ES returned {} hits in {}ms for query: '{}'", + response.hits().hits().size(), + response.took(), + query); + + if (response.hits().hits().isEmpty()) { + logger.info("No results in ES, using database fallback"); + return searchInDatabaseDirectly(query); + } + + List> results = response.hits().hits().stream() + .map(hit -> { + Map result = mapESResultToExpectedFormat(hit.source()); + if (result != null) { + result.put("_score", hit.score()); + } + return result; + }) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + + logger.info("Returning {} results", results.size()); + return results; + + } catch (Exception e) { + logger.error("ES search failed: {}", e.getMessage()); return searchInDatabaseDirectly(query); } + } - List> results = response.hits().hits().stream() - .map(hit -> { - Map result = mapESResultToExpectedFormat(hit.source()); - if (result != null) { - result.put("_score", hit.score()); - } - return result; - }) - .filter(Objects::nonNull) - .collect(Collectors.toList()); + /** + * Generate function scores for location-based ranking + */ + private List getFunctionScores(Map userLocation) { + if (userLocation == null) { + return List.of(); + } - logger.info("Returning {} results", results.size()); - return results; + List scores = new ArrayList<>(); - } catch (Exception e) { - logger.error("ES search failed: {}", e.getMessage()); - return searchInDatabaseDirectly(query); - } -} - -/** - * Generate function scores for location-based ranking - */ -private List getFunctionScores(Map userLocation) { - if (userLocation == null) { - return List.of(); - } + Integer userVillageId = userLocation.get("villageId"); + Integer userBlockId = userLocation.get("blockId"); - List scores = new ArrayList<>(); - - Integer userVillageId = userLocation.get("villageId"); - Integer userBlockId = userLocation.get("blockId"); - - // Village match - if (userVillageId != null) { - scores.add(FunctionScore.of(f -> f - .filter(ff -> ff.term(t -> t.field("villageID").value(userVillageId))) - .weight(2.0) - )); - } - - // Block match - if (userBlockId != null) { - scores.add(FunctionScore.of(f -> f - .filter(ff -> ff.term(t -> t.field("blockID").value(userBlockId))) - .weight(1.5) - )); - } - - return scores; -} - -/** - * Advanced search with filter context - */ -public List> advancedSearch( - String firstName, - String lastName, - Integer genderId, - Date dob, - Integer stateId, - Integer districtId, - Integer blockId, - Integer villageId, - String fatherName, - String spouseName, - String phoneNumber, - String beneficiaryId, - String healthId, - String aadharNo, - Integer userId) { - - try { - final Map userLocation = - (userId != null) ? getUserLocation(userId) : null; - - SearchResponse response = esClient.search(s -> s - .index(beneficiaryIndex) - .preference("_local") - .requestCache(true) - - .query(q -> q - .bool(b -> { - // Use FILTER context for exact matches (faster, cached) - if (genderId != null) { - b.filter(f -> f.term(t -> t.field("genderID").value(genderId))); - } - if (stateId != null) { - b.filter(f -> f.term(t -> t.field("stateID").value(stateId))); - } - if (districtId != null) { - b.filter(f -> f.term(t -> t.field("districtID").value(districtId))); - } - if (blockId != null) { - b.filter(f -> f.term(t -> t.field("blockID").value(blockId))); - } - if (villageId != null) { - b.filter(f -> f.term(t -> t.field("villageID").value(villageId))); - } - - // MUST context for scored searches - if (firstName != null && !firstName.trim().isEmpty()) { - b.must(m -> m.bool(bb -> bb - .should(s1 -> s1.term(t -> t.field("firstName.keyword").value(firstName).boost(5.0f))) - .should(s2 -> s2.match(mm -> mm.field("firstName").query(firstName).boost(2.0f))) - .minimumShouldMatch("1") - )); - } - - if (lastName != null && !lastName.trim().isEmpty()) { - b.must(m -> m.bool(bb -> bb - .should(s1 -> s1.term(t -> t.field("lastName.keyword").value(lastName).boost(5.0f))) - .should(s2 -> s2.match(mm -> mm.field("lastName").query(lastName).boost(2.0f))) - .minimumShouldMatch("1") - )); - } - - // Exact match IDs in filter context - if (beneficiaryId != null && !beneficiaryId.trim().isEmpty()) { - b.filter(f -> f.term(t -> t.field("beneficiaryID").value(beneficiaryId))); - } - if (healthId != null && !healthId.trim().isEmpty()) { - b.filter(f -> f.bool(bb -> bb - .should(s1 -> s1.term(t -> t.field("healthID").value(healthId))) - .should(s2 -> s2.term(t -> t.field("abhaID").value(healthId))) - .minimumShouldMatch("1") - )); - } - if (aadharNo != null && !aadharNo.trim().isEmpty()) { - b.filter(f -> f.term(t -> t.field("aadharNo").value(aadharNo))); - } - if (phoneNumber != null && !phoneNumber.trim().isEmpty()) { - b.must(m -> m.bool(bb -> bb - .should(s1 -> s1.term(t -> t.field("phoneNum").value(phoneNumber).boost(3.0f))) - .should(s2 -> s2.prefix(p -> p.field("phoneNum").value(phoneNumber).boost(2.0f))) - .minimumShouldMatch("1") - )); - } - - return b; - }) - ) - .size(100) - .sort(so -> so.score(sc -> sc.order(SortOrder.Desc))) - - , BeneficiariesESDTO.class); - - if (response.hits().hits().isEmpty()) { - return searchInDatabaseForAdvanced(firstName, lastName, genderId, dob, - stateId, districtId, blockId, villageId, fatherName, spouseName, - phoneNumber, beneficiaryId, healthId, aadharNo); + // Village match + if (userVillageId != null) { + scores.add(FunctionScore.of(f -> f + .filter(ff -> ff.term(t -> t.field("villageID").value(userVillageId))) + .weight(2.0))); } - return response.hits().hits().stream() - .map(hit -> mapESResultToExpectedFormat(hit.source())) - .filter(Objects::nonNull) - .collect(Collectors.toList()); + // Block match + if (userBlockId != null) { + scores.add(FunctionScore.of(f -> f + .filter(ff -> ff.term(t -> t.field("blockID").value(userBlockId))) + .weight(1.5))); + } - } catch (Exception e) { - logger.error("ES advanced search failed: {}", e.getMessage()); - return searchInDatabaseForAdvanced(firstName, lastName, genderId, dob, - stateId, districtId, blockId, villageId, fatherName, spouseName, - phoneNumber, beneficiaryId, healthId, aadharNo); + return scores; } -} - -/** - * Database fallback for advanced search - */ -private List> searchInDatabaseForAdvanced( - String firstName, String lastName, Integer genderId, Date dob, - Integer stateId, Integer districtId, Integer blockId, Integer villageId, - String fatherName, String spouseName, String phoneNumber, - String beneficiaryId, String healthId, String aadharNo) { - - try { - List results = benDetailRepo.advancedSearchBeneficiaries( - firstName, lastName, genderId, dob, stateId, districtId, - blockId, fatherName, spouseName, phoneNumber, - beneficiaryId - ); - - return results.stream() - .map(this::mapToExpectedFormat) - .collect(Collectors.toList()); - - } catch (Exception e) { - logger.error("Database advanced search failed: {}", e.getMessage(), e); - return Collections.emptyList(); + + /** + * Advanced search with filter context + */ + public List> advancedSearch( + String firstName, + String lastName, + Integer genderId, + Date dob, + Integer stateId, + Integer districtId, + Integer blockId, + Integer villageId, + String fatherName, + String spouseName, + String phoneNumber, + String beneficiaryId, + String healthId, + String aadharNo, + Integer userId) { + + try { + final Map userLocation = (userId != null) ? getUserLocation(userId) : null; + + SearchResponse response = esClient.search(s -> s + .index(beneficiaryIndex) + .preference("_local") + .requestCache(true) + + .query(q -> q + .bool(b -> { + // Use FILTER context for exact matches (faster, cached) + if (genderId != null) { + b.filter(f -> f.term(t -> t.field("genderID").value(genderId))); + } + if (stateId != null) { + b.filter(f -> f.term(t -> t.field("stateID").value(stateId))); + } + if (districtId != null) { + b.filter(f -> f.term(t -> t.field("districtID").value(districtId))); + } + if (blockId != null) { + b.filter(f -> f.term(t -> t.field("blockID").value(blockId))); + } + if (villageId != null) { + b.filter(f -> f.term(t -> t.field("villageID").value(villageId))); + } + + // MUST context for scored searches + if (firstName != null && !firstName.trim().isEmpty()) { + b.must(m -> m.bool(bb -> bb + .should(s1 -> s1.term( + t -> t.field("firstName.keyword").value(firstName).boost(5.0f))) + .should(s2 -> s2 + .match(mm -> mm.field("firstName").query(firstName).boost(2.0f))) + .minimumShouldMatch("1"))); + } + + if (lastName != null && !lastName.trim().isEmpty()) { + b.must(m -> m.bool(bb -> bb + .should(s1 -> s1 + .term(t -> t.field("lastName.keyword").value(lastName).boost(5.0f))) + .should(s2 -> s2 + .match(mm -> mm.field("lastName").query(lastName).boost(2.0f))) + .minimumShouldMatch("1"))); + } + + // Exact match IDs in filter context + if (beneficiaryId != null && !beneficiaryId.trim().isEmpty()) { + b.filter(f -> f.term(t -> t.field("beneficiaryID").value(beneficiaryId))); + } + if (healthId != null && !healthId.trim().isEmpty()) { + b.filter(f -> f.bool(bb -> bb + .should(s1 -> s1.term(t -> t.field("healthID").value(healthId))) + .should(s2 -> s2.term(t -> t.field("abhaID").value(healthId))) + .minimumShouldMatch("1"))); + } + if (aadharNo != null && !aadharNo.trim().isEmpty()) { + b.filter(f -> f.term(t -> t.field("aadharNo").value(aadharNo))); + } + if (phoneNumber != null && !phoneNumber.trim().isEmpty()) { + b.must(m -> m.bool(bb -> bb + .should(s1 -> s1 + .term(t -> t.field("phoneNum").value(phoneNumber).boost(3.0f))) + .should(s2 -> s2 + .prefix(p -> p.field("phoneNum").value(phoneNumber).boost(2.0f))) + .minimumShouldMatch("1"))); + } + + return b; + })) + .size(100) + .sort(so -> so.score(sc -> sc.order(SortOrder.Desc))) + + , BeneficiariesESDTO.class); + + if (response.hits().hits().isEmpty()) { + return searchInDatabaseForAdvanced(firstName, lastName, genderId, dob, + stateId, districtId, blockId, villageId, fatherName, spouseName, + phoneNumber, beneficiaryId, healthId, aadharNo); + } + + return response.hits().hits().stream() + .map(hit -> mapESResultToExpectedFormat(hit.source())) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + + } catch (Exception e) { + logger.error("ES advanced search failed: {}", e.getMessage()); + return searchInDatabaseForAdvanced(firstName, lastName, genderId, dob, + stateId, districtId, blockId, villageId, fatherName, spouseName, + phoneNumber, beneficiaryId, healthId, aadharNo); + } } -} + /** + * Database fallback for advanced search + */ + private List> searchInDatabaseForAdvanced( + String firstName, String lastName, Integer genderId, Date dob, + Integer stateId, Integer districtId, Integer blockId, Integer villageId, + String fatherName, String spouseName, String phoneNumber, + String beneficiaryId, String healthId, String aadharNo) { + + try { + List results = benDetailRepo.advancedSearchBeneficiaries( + firstName, lastName, genderId, dob, stateId, districtId, + blockId, fatherName, spouseName, phoneNumber, + beneficiaryId); + + return results.stream() + .map(this::mapToExpectedFormat) + .collect(Collectors.toList()); + + } catch (Exception e) { + logger.error("Database advanced search failed: {}", e.getMessage(), e); + return Collections.emptyList(); + } + } /** * Overloaded method without userId (backward compatibility) @@ -383,7 +375,7 @@ private List> searchInDatabaseForAdvanced( public List> universalSearch(String query) { return universalSearch(query, null); } - + /** * Get user location from database */ @@ -404,73 +396,73 @@ private Map getUserLocation(Integer userId) { } return null; } - + /** * Rank results by location match priority * Priority: Village > Block > District > State > No Match */ - private List> rankByLocation(List> results, - Map userLocation) { + private List> rankByLocation(List> results, + Map userLocation) { Integer userBlockId = userLocation.get("blockId"); Integer userVillageId = userLocation.get("villageId"); - + logger.info("Ranking by location - User Block: {}, Village: {}", userBlockId, userVillageId); - + return results.stream() - .sorted((r1, r2) -> { - int score1 = calculateLocationScore(r1, userBlockId, userVillageId); - int score2 = calculateLocationScore(r2, userBlockId, userVillageId); - return Integer.compare(score2, score1); - }) - .collect(Collectors.toList()); + .sorted((r1, r2) -> { + int score1 = calculateLocationScore(r1, userBlockId, userVillageId); + int score2 = calculateLocationScore(r2, userBlockId, userVillageId); + return Integer.compare(score2, score1); + }) + .collect(Collectors.toList()); } - + /** * Calculate location match score * Higher score = better match */ - private int calculateLocationScore(Map beneficiary, - Integer userBlockId, - Integer userVillageId) { + private int calculateLocationScore(Map beneficiary, + Integer userBlockId, + Integer userVillageId) { int score = 0; - + try { Map demographics = (Map) beneficiary.get("i_bendemographics"); if (demographics == null) { return score; } - + Integer currBlockId = getIntegerFromMap(demographics, "blockID"); Integer currVillageId = getIntegerFromMap(demographics, "m_districtblock", "blockID"); - + // Village match (highest priority) - score: 100 if (userVillageId != null && userVillageId.equals(currVillageId)) { score += 100; } - + // Block match - score: 50 if (userBlockId != null && userBlockId.equals(currBlockId)) { score += 50; } - + Integer permBlockId = getIntegerFromMap(beneficiary, "permBlockID"); Integer permVillageId = getIntegerFromMap(beneficiary, "permVillageID"); - + if (userVillageId != null && userVillageId.equals(permVillageId)) { - score += 75; + score += 75; } - + if (userBlockId != null && userBlockId.equals(permBlockId)) { - score += 25; + score += 25; } - + } catch (Exception e) { logger.error("Error calculating location score: {}", e.getMessage()); } - + return score; } - + /** * Helper to safely get Integer from nested maps */ @@ -485,7 +477,7 @@ private Integer getIntegerFromMap(Map map, String... keys) { } return value instanceof Integer ? (Integer) value : null; } - + /** * Map ES DTO directly to expected API format with COMPLETE data */ @@ -493,9 +485,9 @@ private Map mapESResultToExpectedFormat(BeneficiariesESDTO esDat if (esData == null) { return null; } - + Map result = new HashMap<>(); - + try { // Basic fields from ES result.put("beneficiaryRegID", esData.getBenRegId()); @@ -513,16 +505,16 @@ private Map mapESResultToExpectedFormat(BeneficiariesESDTO esDat result.put("createdDate", esData.getCreatedDate()); result.put("lastModDate", esData.getLastModDate()); result.put("benAccountID", esData.getBenAccountID()); - + result.put("healthID", esData.getHealthID()); result.put("abhaID", esData.getAbhaID()); result.put("familyID", esData.getFamilyID()); - + Map mGender = new HashMap<>(); mGender.put("genderID", esData.getGenderID()); mGender.put("genderName", esData.getGenderName()); result.put("m_gender", mGender); - + Map demographics = new HashMap<>(); demographics.put("beneficiaryRegID", esData.getBenRegId()); demographics.put("stateID", esData.getStateID()); @@ -539,36 +531,36 @@ private Map mapESResultToExpectedFormat(BeneficiariesESDTO esDat demographics.put("servicePointID", esData.getServicePointID()); demographics.put("servicePointName", esData.getServicePointName()); demographics.put("createdBy", esData.getCreatedBy()); - + Map mState = new HashMap<>(); mState.put("stateID", esData.getStateID()); mState.put("stateName", esData.getStateName()); mState.put("stateCode", null); mState.put("countryID", 1); demographics.put("m_state", mState); - + Map mDistrict = new HashMap<>(); mDistrict.put("districtID", esData.getDistrictID()); mDistrict.put("districtName", esData.getDistrictName()); mDistrict.put("stateID", esData.getStateID()); demographics.put("m_district", mDistrict); - + Map mBlock = new HashMap<>(); mBlock.put("blockID", esData.getBlockID()); mBlock.put("blockName", esData.getBlockName()); mBlock.put("districtID", esData.getDistrictID()); mBlock.put("stateID", esData.getStateID()); demographics.put("m_districtblock", mBlock); - + Map mBranch = new HashMap<>(); mBranch.put("districtBranchID", null); mBranch.put("blockID", esData.getBlockID()); mBranch.put("villageName", esData.getVillageName()); mBranch.put("pinCode", esData.getPinCode()); demographics.put("m_districtbranchmapping", mBranch); - + result.put("i_bendemographics", demographics); - + List> benPhoneMaps = new ArrayList<>(); if (esData.getPhoneNum() != null && !esData.getPhoneNum().isEmpty()) { Map phoneMap = new HashMap<>(); @@ -577,16 +569,16 @@ private Map mapESResultToExpectedFormat(BeneficiariesESDTO esDat phoneMap.put("parentBenRegID", esData.getBenRegId()); phoneMap.put("benRelationshipID", 1); phoneMap.put("phoneNo", esData.getPhoneNum()); - + Map relationType = new HashMap<>(); relationType.put("benRelationshipID", 1); relationType.put("benRelationshipType", "Self"); phoneMap.put("benRelationshipType", relationType); - + benPhoneMaps.add(phoneMap); } result.put("benPhoneMaps", benPhoneMaps); - + result.put("isConsent", false); result.put("m_title", new HashMap<>()); result.put("maritalStatus", new HashMap<>()); @@ -603,38 +595,38 @@ private Map mapESResultToExpectedFormat(BeneficiariesESDTO esDat result.put("emergencyRegistration", false); result.put("passToNurse", false); result.put("beneficiaryIdentities", new ArrayList<>()); - + } catch (Exception e) { logger.error("Error mapping ES result: {}", e.getMessage(), e); return null; } - + return result; } - + /** * Direct database search as fallback */ private List> searchInDatabaseDirectly(String query) { try { List results = benDetailRepo.searchBeneficiaries(query); - + return results.stream() - .map(this::mapToExpectedFormat) - .collect(Collectors.toList()); - + .map(this::mapToExpectedFormat) + .collect(Collectors.toList()); + } catch (Exception e) { logger.error("Database search failed: {}", e.getMessage(), e); return Collections.emptyList(); } } - + /** * Map database result to expected API format */ private Map mapToExpectedFormat(Object[] row) { Map result = new HashMap<>(); - + try { Long beneficiaryRegID = getLong(row[0]); String beneficiaryID = getString(row[1]); @@ -651,7 +643,7 @@ private Map mapToExpectedFormat(Object[] row) { Date createdDate = getDate(row[12]); Long lastModDate = getLong(row[13]); Long benAccountID = getLong(row[14]); - + Integer stateID = getInteger(row[15]); String stateName = getString(row[16]); Integer districtID = getInteger(row[17]); @@ -663,7 +655,7 @@ private Map mapToExpectedFormat(Object[] row) { String servicePointName = getString(row[23]); Integer parkingPlaceID = getInteger(row[24]); String phoneNum = getString(row[25]); - + result.put("beneficiaryRegID", beneficiaryRegID); result.put("beneficiaryID", beneficiaryID); result.put("firstName", firstName); @@ -679,12 +671,12 @@ private Map mapToExpectedFormat(Object[] row) { result.put("createdDate", createdDate); result.put("lastModDate", lastModDate); result.put("benAccountID", benAccountID); - + Map mGender = new HashMap<>(); mGender.put("genderID", genderID); mGender.put("genderName", genderName); result.put("m_gender", mGender); - + Map demographics = new HashMap<>(); demographics.put("beneficiaryRegID", beneficiaryRegID); demographics.put("stateID", stateID); @@ -699,39 +691,39 @@ private Map mapToExpectedFormat(Object[] row) { demographics.put("servicePointID", servicePointID); demographics.put("servicePointName", servicePointName); demographics.put("createdBy", createdBy); - + Map mState = new HashMap<>(); mState.put("stateID", stateID); mState.put("stateName", stateName); mState.put("stateCode", null); mState.put("countryID", 1); demographics.put("m_state", mState); - + Map mDistrict = new HashMap<>(); mDistrict.put("districtID", districtID); mDistrict.put("districtName", districtName); mDistrict.put("stateID", stateID); demographics.put("m_district", mDistrict); - + Map mBlock = new HashMap<>(); mBlock.put("blockID", blockID); mBlock.put("blockName", blockName); mBlock.put("districtID", districtID); mBlock.put("stateID", stateID); demographics.put("m_districtblock", mBlock); - + Map mBranch = new HashMap<>(); mBranch.put("districtBranchID", null); mBranch.put("blockID", blockID); mBranch.put("villageName", null); mBranch.put("pinCode", pinCode); demographics.put("m_districtbranchmapping", mBranch); - + result.put("i_bendemographics", demographics); - + List> benPhoneMaps = fetchPhoneNumbers(beneficiaryRegID); result.put("benPhoneMaps", benPhoneMaps); - + result.put("isConsent", false); result.put("m_title", new HashMap<>()); result.put("maritalStatus", new HashMap<>()); @@ -748,28 +740,28 @@ private Map mapToExpectedFormat(Object[] row) { result.put("emergencyRegistration", false); result.put("passToNurse", false); result.put("beneficiaryIdentities", new ArrayList<>()); - + } catch (Exception e) { logger.error("Error mapping result: {}", e.getMessage(), e); } - + return result; } - + /** * Fetch phone numbers for a beneficiary */ private List> fetchPhoneNumbers(Long beneficiaryRegID) { List> phoneList = new ArrayList<>(); - + try { List phones = benDetailRepo.findPhoneNumbersByBeneficiaryId(beneficiaryRegID); - + int mapId = 1; for (Object[] phone : phones) { String phoneNo = getString(phone[0]); String phoneType = getString(phone[1]); - + if (phoneNo != null && !phoneNo.isEmpty()) { Map phoneMap = new HashMap<>(); phoneMap.put("benPhMapID", (long) mapId++); @@ -777,33 +769,38 @@ private List> fetchPhoneNumbers(Long beneficiaryRegID) { phoneMap.put("parentBenRegID", beneficiaryRegID); phoneMap.put("benRelationshipID", 1); phoneMap.put("phoneNo", phoneNo); - + Map relationType = new HashMap<>(); relationType.put("benRelationshipID", 1); relationType.put("benRelationshipType", phoneType != null ? phoneType : "Self"); phoneMap.put("benRelationshipType", relationType); - + phoneList.add(phoneMap); } } } catch (Exception e) { logger.error("Error fetching phone numbers: {}", e.getMessage(), e); } - + return phoneList; } - + // Helper methods private String getString(Object value) { - if (value == null) return null; + if (value == null) + return null; return value.toString(); } - + private Long getLong(Object value) { - if (value == null) return null; - if (value instanceof Long) return (Long) value; - if (value instanceof Integer) return ((Integer) value).longValue(); - if (value instanceof BigDecimal) return ((BigDecimal) value).longValue(); + if (value == null) + return null; + if (value instanceof Long) + return (Long) value; + if (value instanceof Integer) + return ((Integer) value).longValue(); + if (value instanceof BigDecimal) + return ((BigDecimal) value).longValue(); if (value instanceof String) { try { return Long.parseLong((String) value); @@ -813,12 +810,16 @@ private Long getLong(Object value) { } return null; } - + private Integer getInteger(Object value) { - if (value == null) return null; - if (value instanceof Integer) return (Integer) value; - if (value instanceof Long) return ((Long) value).intValue(); - if (value instanceof BigDecimal) return ((BigDecimal) value).intValue(); + if (value == null) + return null; + if (value instanceof Integer) + return (Integer) value; + if (value instanceof Long) + return ((Long) value).intValue(); + if (value instanceof BigDecimal) + return ((BigDecimal) value).intValue(); if (value instanceof String) { try { return Integer.parseInt((String) value); @@ -828,12 +829,16 @@ private Integer getInteger(Object value) { } return null; } - + private Date getDate(Object value) { - if (value == null) return null; - if (value instanceof Date) return (Date) value; - if (value instanceof Timestamp) return new Date(((Timestamp) value).getTime()); - if (value instanceof java.sql.Date) return new Date(((java.sql.Date) value).getTime()); + if (value == null) + return null; + if (value instanceof Date) + return (Date) value; + if (value instanceof Timestamp) + return new Date(((Timestamp) value).getTime()); + if (value instanceof java.sql.Date) + return new Date(((java.sql.Date) value).getTime()); return null; } } \ No newline at end of file