diff --git a/src/main/kotlin/com/android/trisolarisserver/component/DocumentExtractionService.kt b/src/main/kotlin/com/android/trisolarisserver/component/DocumentExtractionService.kt index fd8805b..1888581 100644 --- a/src/main/kotlin/com/android/trisolarisserver/component/DocumentExtractionService.kt +++ b/src/main/kotlin/com/android/trisolarisserver/component/DocumentExtractionService.kt @@ -2,6 +2,7 @@ package com.android.trisolarisserver.component import com.android.trisolarisserver.controller.DocumentPrompts import com.android.trisolarisserver.db.repo.GuestRepo +import com.android.trisolarisserver.db.repo.BookingRepo import com.android.trisolarisserver.models.booking.GuestDocument import com.android.trisolarisserver.models.booking.GuestVehicle import com.android.trisolarisserver.repo.GuestVehicleRepo @@ -16,7 +17,9 @@ class DocumentExtractionService( private val guestRepo: GuestRepo, private val guestVehicleRepo: GuestVehicleRepo, private val propertyRepo: PropertyRepo, - private val paddleOcrClient: PaddleOcrClient + private val paddleOcrClient: PaddleOcrClient, + private val bookingRepo: BookingRepo, + private val googleGeocodingClient: GoogleGeocodingClient ) { private val logger = LoggerFactory.getLogger(DocumentExtractionService::class.java) @@ -241,6 +244,7 @@ class DocumentExtractionService( normalizePinCode(results) normalizeIdNumber(results) markAadhaarIfValid(results) + applyBookingCityUpdates(document, results) results["docType"] = computeDocType(results, handled) applyGuestUpdates(document, propertyId, results) return ExtractionResult(results, handled) @@ -421,6 +425,27 @@ class DocumentExtractionService( } } } + + private fun applyBookingCityUpdates(document: GuestDocument, results: Map) { + val booking = document.booking + if (booking.fromCity?.isNotBlank() == true && booking.toCity?.isNotBlank() == true) return + val pin = cleanedValue(results[DocumentPrompts.PIN_CODE.first]) ?: return + if (!isValidPin(pin)) return + val resolved = googleGeocodingClient.resolveCityState(pin) ?: return + var updated = false + if (booking.fromCity.isNullOrBlank()) { + booking.fromCity = resolved + updated = true + } + if (booking.toCity.isNullOrBlank()) { + booking.toCity = resolved + updated = true + } + if (updated) { + booking.updatedAt = OffsetDateTime.now() + bookingRepo.save(booking) + } + } } data class ExtractionResult( diff --git a/src/main/kotlin/com/android/trisolarisserver/component/GoogleGeocodingClient.kt b/src/main/kotlin/com/android/trisolarisserver/component/GoogleGeocodingClient.kt new file mode 100644 index 0000000..278dcac --- /dev/null +++ b/src/main/kotlin/com/android/trisolarisserver/component/GoogleGeocodingClient.kt @@ -0,0 +1,59 @@ +package com.android.trisolarisserver.component + +import com.fasterxml.jackson.databind.ObjectMapper +import org.slf4j.LoggerFactory +import org.springframework.beans.factory.annotation.Value +import org.springframework.stereotype.Component +import org.springframework.web.client.RestTemplate +import org.springframework.web.util.UriComponentsBuilder + +@Component +class GoogleGeocodingClient( + private val restTemplate: RestTemplate, + private val objectMapper: ObjectMapper, + @Value("\${google.maps.apiKey:}") + private val apiKey: String, + @Value("\${google.maps.geocode.baseUrl:https://maps.googleapis.com/maps/api/geocode/json}") + private val baseUrl: String +) { + private val logger = LoggerFactory.getLogger(GoogleGeocodingClient::class.java) + + fun resolveCityState(pinCode: String): String? { + if (apiKey.isBlank()) return null + return try { + val url = UriComponentsBuilder.fromUriString(baseUrl) + .queryParam("address", "${pinCode} India") + .queryParam("key", apiKey) + .toUriString() + val response = restTemplate.getForEntity(url, String::class.java) + val body = response.body ?: return null + parseCityState(body) + } catch (ex: Exception) { + logger.warn("Geocoding failed: {}", ex.message) + null + } + } + + private fun parseCityState(body: String): String? { + val root = objectMapper.readTree(body) + val results = root.path("results") + if (!results.isArray || results.isEmpty) return null + val components = results.first().path("address_components") + if (!components.isArray) return null + + var city: String? = null + var state: String? = null + for (comp in components) { + val types = comp.path("types").mapNotNull { it.asText(null) }.toSet() + when { + "locality" in types -> city = comp.path("long_name").asText(null) ?: city + "administrative_area_level_2" in types -> if (city == null) { + city = comp.path("long_name").asText(null) + } + "administrative_area_level_1" in types -> state = comp.path("long_name").asText(null) ?: state + } + } + if (city.isNullOrBlank() && state.isNullOrBlank()) return null + return listOfNotNull(city?.trim()?.ifBlank { null }, state?.trim()?.ifBlank { null }).joinToString(", ") + } +} diff --git a/src/main/resources/application-prod.properties b/src/main/resources/application-prod.properties index 6bc4835..6932923 100644 --- a/src/main/resources/application-prod.properties +++ b/src/main/resources/application-prod.properties @@ -2,4 +2,4 @@ spring.datasource.url=jdbc:postgresql://localhost:5432/trisolaris ai.llama.baseUrl=http://localhost:8089/v1/chat/completions logging.level.com.android.trisolarisserver.controller.Auth=INFO storage.documents.aiBaseUrl=http://127.0.0.1:18921 -ocr.paddle.baseUrl=http://127.0.0.1:9001/ocr \ No newline at end of file +ocr.paddle.baseUrl=http://127.0.0.1:9001/ocr diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 7a93a2e..c5951e5 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -33,3 +33,5 @@ ocr.paddle.baseUrl=https://ocr.hoteltrisolaris.in/ ocr.paddle.minScore=0.9 ocr.paddle.minAverageScore=0.75 ocr.paddle.minTextLength=4 +google.maps.apiKey=AIzaSyAMuRNFWjccKSmPeR0loQI8etHMDtUIZ_k +google.maps.geocode.baseUrl=https://maps.googleapis.com/maps/api/geocode/json