diff --git a/src/main/kotlin/com/android/trisolarisserver/component/geo/LocalPincodeDirectoryClient.kt b/src/main/kotlin/com/android/trisolarisserver/component/geo/LocalPincodeDirectoryClient.kt deleted file mode 100644 index 770ffd2..0000000 --- a/src/main/kotlin/com/android/trisolarisserver/component/geo/LocalPincodeDirectoryClient.kt +++ /dev/null @@ -1,76 +0,0 @@ -package com.android.trisolarisserver.component.geo - -import org.slf4j.LoggerFactory -import org.springframework.jdbc.core.JdbcTemplate -import org.springframework.stereotype.Component - -@Component -class LocalPincodeDirectoryClient( - private val jdbcTemplate: JdbcTemplate -) { - private val logger = LoggerFactory.getLogger(LocalPincodeDirectoryClient::class.java) - - fun resolve(pinCode: String): PincodeLookupResult { - val normalizedPin = pinCode.trim() - if (!PIN_REGEX.matches(normalizedPin)) { - return PincodeLookupResult( - resolvedCityState = null, - rawResponse = null, - status = "INVALID_PIN", - source = "local-db", - errorMessage = "PIN must be 6 digits" - ) - } - - return try { - val rows = jdbcTemplate.query( - """ - select city, state - from india_pincode_city_state - where pincode = ? - and nullif(trim(city), '') is not null - and nullif(trim(state), '') is not null - group by city, state - order by count(*) desc, city asc, state asc - limit 1 - """.trimIndent(), - { rs, _ -> - val city = rs.getString("city")?.trim()?.ifBlank { null } - val state = rs.getString("state")?.trim()?.ifBlank { null } - if (city == null || state == null) null else "$city, $state" - }, - normalizedPin.toInt() - ).filterNotNull() - - val resolved = rows.firstOrNull() - if (resolved != null) { - PincodeLookupResult( - resolvedCityState = resolved, - rawResponse = null, - status = "OK", - source = "local-db" - ) - } else { - PincodeLookupResult( - resolvedCityState = null, - rawResponse = null, - status = "ZERO_RESULTS", - source = "local-db" - ) - } - } catch (ex: Exception) { - logger.warn("Local pincode lookup failed: {}", ex.message) - PincodeLookupResult( - resolvedCityState = null, - rawResponse = null, - status = "ERROR", - source = "local-db", - errorMessage = ex.message - ) - } - } - - companion object { - private val PIN_REGEX = Regex("\\d{6}") - } -} diff --git a/src/main/kotlin/com/android/trisolarisserver/component/geo/PincodeResolver.kt b/src/main/kotlin/com/android/trisolarisserver/component/geo/PincodeResolver.kt index e55673f..80f1bb1 100644 --- a/src/main/kotlin/com/android/trisolarisserver/component/geo/PincodeResolver.kt +++ b/src/main/kotlin/com/android/trisolarisserver/component/geo/PincodeResolver.kt @@ -1,16 +1,21 @@ package com.android.trisolarisserver.component.geo +import com.android.trisolarisserver.repo.property.IndiaPincodeCityStateRepo +import org.slf4j.LoggerFactory +import org.springframework.data.domain.PageRequest import org.springframework.stereotype.Component @Component class PincodeResolver( - private val localPincodeDirectoryClient: LocalPincodeDirectoryClient, + private val indiaPincodeCityStateRepo: IndiaPincodeCityStateRepo, private val dataGovPincodeClient: DataGovPincodeClient, private val postalPincodeClient: PostalPincodeClient, private val googleGeocodingClient: GoogleGeocodingClient ) { + private val logger = LoggerFactory.getLogger(PincodeResolver::class.java) + fun resolve(pinCode: String): PincodeResolveResult { - val primary = localPincodeDirectoryClient.resolve(pinCode) + val primary = resolveFromLocalDb(pinCode) if (primary.status == "OK" && primary.resolvedCityState != null) { return PincodeResolveResult(primary, null, null, null) } @@ -34,6 +39,53 @@ class PincodeResolver( ) return PincodeResolveResult(primary, secondary, tertiary, quaternary) } + + private fun resolveFromLocalDb(pinCode: String): PincodeLookupResult { + val normalizedPin = pinCode.trim() + if (!PIN_REGEX.matches(normalizedPin)) { + return PincodeLookupResult( + resolvedCityState = null, + rawResponse = null, + status = "INVALID_PIN", + source = "local-db", + errorMessage = "PIN must be 6 digits" + ) + } + + return try { + val candidate = indiaPincodeCityStateRepo + .findCityStateCandidates(normalizedPin.toInt(), PageRequest.of(0, 1)) + .firstOrNull() + if (candidate != null) { + PincodeLookupResult( + resolvedCityState = "${candidate.city}, ${candidate.state}", + rawResponse = null, + status = "OK", + source = "local-db" + ) + } else { + PincodeLookupResult( + resolvedCityState = null, + rawResponse = null, + status = "ZERO_RESULTS", + source = "local-db" + ) + } + } catch (ex: Exception) { + logger.warn("Local pincode lookup failed: {}", ex.message) + PincodeLookupResult( + resolvedCityState = null, + rawResponse = null, + status = "ERROR", + source = "local-db", + errorMessage = ex.message + ) + } + } + + companion object { + private val PIN_REGEX = Regex("\\d{6}") + } } data class PincodeResolveResult( diff --git a/src/main/kotlin/com/android/trisolarisserver/models/property/IndiaPincodeCityState.kt b/src/main/kotlin/com/android/trisolarisserver/models/property/IndiaPincodeCityState.kt new file mode 100644 index 0000000..655463e --- /dev/null +++ b/src/main/kotlin/com/android/trisolarisserver/models/property/IndiaPincodeCityState.kt @@ -0,0 +1,36 @@ +package com.android.trisolarisserver.models.property + +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.GeneratedValue +import jakarta.persistence.Id +import jakarta.persistence.Table +import java.time.OffsetDateTime +import java.util.UUID + +@Entity +@Table(name = "india_pincode_city_state") +class IndiaPincodeCityState( + @Id + @GeneratedValue + @Column(columnDefinition = "uuid") + val id: UUID? = null, + + @Column(name = "pincode", nullable = false) + val pincode: Int, + + @Column(name = "locality", nullable = false) + val locality: String, + + @Column(name = "city", nullable = false) + val city: String, + + @Column(name = "state", nullable = false) + val state: String, + + @Column(name = "source_file") + val sourceFile: String? = null, + + @Column(name = "imported_at", columnDefinition = "timestamptz") + val importedAt: OffsetDateTime? = null +) diff --git a/src/main/kotlin/com/android/trisolarisserver/repo/property/IndiaPincodeCityStateRepo.kt b/src/main/kotlin/com/android/trisolarisserver/repo/property/IndiaPincodeCityStateRepo.kt new file mode 100644 index 0000000..6660f2c --- /dev/null +++ b/src/main/kotlin/com/android/trisolarisserver/repo/property/IndiaPincodeCityStateRepo.kt @@ -0,0 +1,34 @@ +package com.android.trisolarisserver.repo.property + +import com.android.trisolarisserver.models.property.IndiaPincodeCityState +import org.springframework.data.domain.Pageable +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.data.jpa.repository.Query +import org.springframework.data.repository.query.Param +import java.util.UUID + +interface IndiaPincodeCityStateRepo : JpaRepository { + @Query( + """ + select p.city as city, + p.state as state, + count(p.id) as hitCount + from IndiaPincodeCityState p + where p.pincode = :pincode + and trim(p.city) <> '' + and trim(p.state) <> '' + group by p.city, p.state + order by count(p.id) desc, p.city asc, p.state asc + """ + ) + fun findCityStateCandidates( + @Param("pincode") pincode: Int, + pageable: Pageable + ): List +} + +interface PincodeCityStateCandidate { + val city: String + val state: String + val hitCount: Long +}