Auto-fill booking city via pin geocoding
All checks were successful
build-and-deploy / build-deploy (push) Successful in 33s

This commit is contained in:
androidlover5842
2026-01-31 10:48:25 +05:30
parent 366673c690
commit e148549b6c
4 changed files with 88 additions and 2 deletions

View File

@@ -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<String, String>) {
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(

View File

@@ -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(", ")
}
}

View File

@@ -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
ocr.paddle.baseUrl=http://127.0.0.1:9001/ocr

View File

@@ -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