Resolve pin via data.gov.in with fallbacks
All checks were successful
build-and-deploy / build-deploy (push) Successful in 33s
All checks were successful
build-and-deploy / build-deploy (push) Successful in 33s
This commit is contained in:
@@ -0,0 +1,64 @@
|
|||||||
|
package com.android.trisolarisserver.component
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode
|
||||||
|
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 DataGovPincodeClient(
|
||||||
|
private val restTemplate: RestTemplate,
|
||||||
|
private val objectMapper: ObjectMapper,
|
||||||
|
@Value("\${pincode.datagov.apiKey:}")
|
||||||
|
private val apiKey: String,
|
||||||
|
@Value("\${pincode.datagov.baseUrl:https://api.data.gov.in/resource/5c2f62fe-5afa-4119-a499-fec9d604d5bd}")
|
||||||
|
private val baseUrl: String
|
||||||
|
) {
|
||||||
|
private val logger = LoggerFactory.getLogger(DataGovPincodeClient::class.java)
|
||||||
|
|
||||||
|
fun resolve(pinCode: String): PincodeLookupResult {
|
||||||
|
if (apiKey.isBlank()) return PincodeLookupResult(null, null, "NO_API_KEY", "data.gov.in")
|
||||||
|
return try {
|
||||||
|
val url = UriComponentsBuilder.fromUriString(baseUrl)
|
||||||
|
.queryParam("api-key", apiKey)
|
||||||
|
.queryParam("format", "json")
|
||||||
|
.queryParam("filters[pincode]", pinCode)
|
||||||
|
.toUriString()
|
||||||
|
val response = restTemplate.getForEntity(url, String::class.java)
|
||||||
|
val body = response.body ?: return PincodeLookupResult(null, null, "EMPTY_BODY", "data.gov.in")
|
||||||
|
val resolved = parseCityState(body)
|
||||||
|
val status = if (resolved == null) "ZERO_RESULTS" else "OK"
|
||||||
|
PincodeLookupResult(resolved, body, status, "data.gov.in")
|
||||||
|
} catch (ex: Exception) {
|
||||||
|
logger.warn("Data.gov.in lookup failed: {}", ex.message)
|
||||||
|
PincodeLookupResult(null, null, "ERROR", "data.gov.in")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseCityState(body: String): String? {
|
||||||
|
val root = objectMapper.readTree(body)
|
||||||
|
val records = root.path("records")
|
||||||
|
if (!records.isArray || records.isEmpty) return null
|
||||||
|
val chosen = chooseRecord(records) ?: return null
|
||||||
|
val district = chosen.path("district").asText(null)
|
||||||
|
val state = chosen.path("statename").asText(null)
|
||||||
|
val districtName = district?.let { toTitleCase(it) }
|
||||||
|
val stateName = state?.let { toTitleCase(it) }
|
||||||
|
if (districtName.isNullOrBlank() && stateName.isNullOrBlank()) return null
|
||||||
|
return listOfNotNull(districtName?.ifBlank { null }, stateName?.ifBlank { null }).joinToString(", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun chooseRecord(records: JsonNode): JsonNode? {
|
||||||
|
val delivery = records.firstOrNull { it.path("delivery").asText("").equals("Delivery", true) }
|
||||||
|
return delivery ?: records.firstOrNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun toTitleCase(value: String): String {
|
||||||
|
return value.lowercase().split(Regex("\\s+")).joinToString(" ") { word ->
|
||||||
|
word.replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,7 +19,7 @@ class DocumentExtractionService(
|
|||||||
private val propertyRepo: PropertyRepo,
|
private val propertyRepo: PropertyRepo,
|
||||||
private val paddleOcrClient: PaddleOcrClient,
|
private val paddleOcrClient: PaddleOcrClient,
|
||||||
private val bookingRepo: BookingRepo,
|
private val bookingRepo: BookingRepo,
|
||||||
private val googleGeocodingClient: GoogleGeocodingClient
|
private val pincodeResolver: PincodeResolver
|
||||||
) {
|
) {
|
||||||
private val logger = LoggerFactory.getLogger(DocumentExtractionService::class.java)
|
private val logger = LoggerFactory.getLogger(DocumentExtractionService::class.java)
|
||||||
|
|
||||||
@@ -432,13 +432,24 @@ class DocumentExtractionService(
|
|||||||
if (booking.fromCity?.isNotBlank() == true && booking.toCity?.isNotBlank() == true) return
|
if (booking.fromCity?.isNotBlank() == true && booking.toCity?.isNotBlank() == true) return
|
||||||
val pin = cleanedValue(results[DocumentPrompts.PIN_CODE.first]) ?: return
|
val pin = cleanedValue(results[DocumentPrompts.PIN_CODE.first]) ?: return
|
||||||
if (!isValidPin(pin)) return
|
if (!isValidPin(pin)) return
|
||||||
val geocode = googleGeocodingClient.resolveCityState(pin)
|
val resolvedResult = pincodeResolver.resolve(pin)
|
||||||
geocode.status?.let { results["geoStatus"] = it }
|
val primary = resolvedResult.primary
|
||||||
if (geocode.rawResponse != null) {
|
results["geoPrimarySource"] = primary.source
|
||||||
results["geoResponse"] = geocode.rawResponse.take(4000)
|
primary.status?.let { results["geoPrimaryStatus"] = it }
|
||||||
|
primary.rawResponse?.let { results["geoPrimaryResponse"] = it.take(4000) }
|
||||||
|
resolvedResult.secondary?.let { secondary ->
|
||||||
|
results["geoSecondarySource"] = secondary.source
|
||||||
|
secondary.status?.let { results["geoSecondaryStatus"] = it }
|
||||||
|
secondary.rawResponse?.let { results["geoSecondaryResponse"] = it.take(4000) }
|
||||||
}
|
}
|
||||||
val resolved = geocode.resolvedCityState ?: return
|
resolvedResult.tertiary?.let { tertiary ->
|
||||||
|
results["geoTertiarySource"] = tertiary.source
|
||||||
|
tertiary.status?.let { results["geoTertiaryStatus"] = it }
|
||||||
|
tertiary.rawResponse?.let { results["geoTertiaryResponse"] = it.take(4000) }
|
||||||
|
}
|
||||||
|
val resolved = resolvedResult.resolved()?.resolvedCityState ?: return
|
||||||
results["geoResolved"] = resolved
|
results["geoResolved"] = resolved
|
||||||
|
results["geoSource"] = resolvedResult.resolved()?.source ?: ""
|
||||||
var updated = false
|
var updated = false
|
||||||
if (booking.fromCity.isNullOrBlank()) {
|
if (booking.fromCity.isNullOrBlank()) {
|
||||||
booking.fromCity = resolved
|
booking.fromCity = resolved
|
||||||
|
|||||||
@@ -68,15 +68,6 @@ class GoogleGeocodingClient(
|
|||||||
val resultNode = results.firstOrNull { node ->
|
val resultNode = results.firstOrNull { node ->
|
||||||
node.path("types").any { it.asText(null) == "postal_code" }
|
node.path("types").any { it.asText(null) == "postal_code" }
|
||||||
} ?: results.first()
|
} ?: results.first()
|
||||||
val postcodeLocalities = resultNode.path("postcode_localities")
|
|
||||||
if (postcodeLocalities.isArray) {
|
|
||||||
val candidates = postcodeLocalities.mapNotNull { it.asText(null) }
|
|
||||||
val agra = candidates.firstOrNull { it.equals("Agra", ignoreCase = true) }
|
|
||||||
if (agra != null) {
|
|
||||||
val stateOnly = extractState(resultNode)
|
|
||||||
return listOfNotNull(agra, stateOnly).joinToString(", ")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val components = resultNode.path("address_components")
|
val components = resultNode.path("address_components")
|
||||||
if (!components.isArray) return null
|
if (!components.isArray) return null
|
||||||
|
|
||||||
@@ -101,18 +92,6 @@ class GoogleGeocodingClient(
|
|||||||
if (preferredCity == null && state.isNullOrBlank()) return null
|
if (preferredCity == null && state.isNullOrBlank()) return null
|
||||||
return listOfNotNull(preferredCity, state?.trim()?.ifBlank { null }).joinToString(", ")
|
return listOfNotNull(preferredCity, state?.trim()?.ifBlank { null }).joinToString(", ")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun extractState(resultNode: com.fasterxml.jackson.databind.JsonNode): String? {
|
|
||||||
val components = resultNode.path("address_components")
|
|
||||||
if (!components.isArray) return null
|
|
||||||
for (comp in components) {
|
|
||||||
val types = comp.path("types").mapNotNull { it.asText(null) }.toSet()
|
|
||||||
if ("administrative_area_level_1" in types) {
|
|
||||||
return comp.path("long_name").asText(null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data class GeocodeResult(
|
data class GeocodeResult(
|
||||||
@@ -120,3 +99,10 @@ data class GeocodeResult(
|
|||||||
val rawResponse: String?,
|
val rawResponse: String?,
|
||||||
val status: String?
|
val status: String?
|
||||||
)
|
)
|
||||||
|
|
||||||
|
data class PincodeLookupResult(
|
||||||
|
val resolvedCityState: String?,
|
||||||
|
val rawResponse: String?,
|
||||||
|
val status: String?,
|
||||||
|
val source: String
|
||||||
|
)
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package com.android.trisolarisserver.component
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Component
|
||||||
|
|
||||||
|
@Component
|
||||||
|
class PincodeResolver(
|
||||||
|
private val dataGovPincodeClient: DataGovPincodeClient,
|
||||||
|
private val postalPincodeClient: PostalPincodeClient,
|
||||||
|
private val googleGeocodingClient: GoogleGeocodingClient
|
||||||
|
) {
|
||||||
|
fun resolve(pinCode: String): PincodeResolveResult {
|
||||||
|
val primary = dataGovPincodeClient.resolve(pinCode)
|
||||||
|
if (primary.status == "OK" && primary.resolvedCityState != null) {
|
||||||
|
return PincodeResolveResult(primary, null, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
val secondary = postalPincodeClient.resolve(pinCode)
|
||||||
|
if (secondary.status == "OK" && secondary.resolvedCityState != null) {
|
||||||
|
return PincodeResolveResult(primary, secondary, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
val google = googleGeocodingClient.resolveCityState(pinCode)
|
||||||
|
val tertiary = PincodeLookupResult(
|
||||||
|
google.resolvedCityState,
|
||||||
|
google.rawResponse,
|
||||||
|
google.status,
|
||||||
|
"google"
|
||||||
|
)
|
||||||
|
return PincodeResolveResult(primary, secondary, tertiary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class PincodeResolveResult(
|
||||||
|
val primary: PincodeLookupResult,
|
||||||
|
val secondary: PincodeLookupResult?,
|
||||||
|
val tertiary: PincodeLookupResult?
|
||||||
|
) {
|
||||||
|
fun resolved(): PincodeLookupResult? {
|
||||||
|
return sequenceOf(primary, secondary, tertiary).firstOrNull { it?.resolvedCityState != null }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
package com.android.trisolarisserver.component
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode
|
||||||
|
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 PostalPincodeClient(
|
||||||
|
private val restTemplate: RestTemplate,
|
||||||
|
private val objectMapper: ObjectMapper,
|
||||||
|
@Value("\${pincode.postal.baseUrl:https://api.postalpincode.in}")
|
||||||
|
private val baseUrl: String
|
||||||
|
) {
|
||||||
|
private val logger = LoggerFactory.getLogger(PostalPincodeClient::class.java)
|
||||||
|
|
||||||
|
fun resolve(pinCode: String): PincodeLookupResult {
|
||||||
|
return try {
|
||||||
|
val url = UriComponentsBuilder.fromUriString(baseUrl)
|
||||||
|
.path("/pincode/{pin}")
|
||||||
|
.buildAndExpand(pinCode)
|
||||||
|
.toUriString()
|
||||||
|
val response = restTemplate.getForEntity(url, String::class.java)
|
||||||
|
val body = response.body ?: return PincodeLookupResult(null, null, "EMPTY_BODY", "postalpincode.in")
|
||||||
|
val resolved = parseCityState(body)
|
||||||
|
val status = if (resolved == null) "ZERO_RESULTS" else "OK"
|
||||||
|
PincodeLookupResult(resolved, body, status, "postalpincode.in")
|
||||||
|
} catch (ex: Exception) {
|
||||||
|
logger.warn("Postalpincode lookup failed: {}", ex.message)
|
||||||
|
PincodeLookupResult(null, null, "ERROR", "postalpincode.in")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseCityState(body: String): String? {
|
||||||
|
val root = objectMapper.readTree(body)
|
||||||
|
if (!root.isArray || root.isEmpty) return null
|
||||||
|
val first = root.first()
|
||||||
|
val status = first.path("Status").asText(null)
|
||||||
|
if (!status.equals("Success", true)) return null
|
||||||
|
val offices = first.path("PostOffice")
|
||||||
|
if (!offices.isArray || offices.isEmpty) return null
|
||||||
|
val office = chooseOffice(offices) ?: return null
|
||||||
|
val district = office.path("District").asText(null)
|
||||||
|
val state = office.path("State").asText(null)
|
||||||
|
if (district.isNullOrBlank() && state.isNullOrBlank()) return null
|
||||||
|
return listOfNotNull(district?.ifBlank { null }, state?.ifBlank { null }).joinToString(", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun chooseOffice(offices: JsonNode): JsonNode? {
|
||||||
|
val delivery = offices.firstOrNull { it.path("DeliveryStatus").asText("").equals("Delivery", true) }
|
||||||
|
return delivery ?: offices.firstOrNull()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -33,5 +33,9 @@ ocr.paddle.baseUrl=https://ocr.hoteltrisolaris.in/
|
|||||||
ocr.paddle.minScore=0.9
|
ocr.paddle.minScore=0.9
|
||||||
ocr.paddle.minAverageScore=0.75
|
ocr.paddle.minAverageScore=0.75
|
||||||
ocr.paddle.minTextLength=4
|
ocr.paddle.minTextLength=4
|
||||||
|
|
||||||
|
pincode.datagov.apiKey=579b464db66ec23bdd000001cdd3946e44ce4aad7209ff7b23ac571b
|
||||||
|
pincode.datagov.baseUrl=https://api.data.gov.in/resource/5c2f62fe-5afa-4119-a499-fec9d604d5bd
|
||||||
|
pincode.postal.baseUrl=https://api.postalpincode.in
|
||||||
google.maps.apiKey=AIzaSyAMuRNFWjccKSmPeR0loQI8etHMDtUIZ_k
|
google.maps.apiKey=AIzaSyAMuRNFWjccKSmPeR0loQI8etHMDtUIZ_k
|
||||||
google.maps.geocode.baseUrl=https://maps.googleapis.com/maps/api/geocode/json
|
google.maps.geocode.baseUrl=https://maps.googleapis.com/maps/api/geocode/json
|
||||||
|
|||||||
Reference in New Issue
Block a user