diff --git a/src/main/kotlin/com/android/trisolarisserver/component/DocumentExtractionService.kt b/src/main/kotlin/com/android/trisolarisserver/component/DocumentExtractionService.kt index 49c410a..b30d2d6 100644 --- a/src/main/kotlin/com/android/trisolarisserver/component/DocumentExtractionService.kt +++ b/src/main/kotlin/com/android/trisolarisserver/component/DocumentExtractionService.kt @@ -293,12 +293,17 @@ class DocumentExtractionService( } logger.info("Aadhaar retry failed; using OpenAI fallback") - val fallback = openAIVisionClient.extractAadhaarNumber(publicImageUrl) ?: "" + val fallback = openAIVisionClient.extractAadhaarNumber( + publicImageUrl, + document.storagePath, + document.contentType + ) ?: "" val fallbackNormalized = normalizeDigits(cleanedValue(fallback)) if (fallbackNormalized != null && isValidAadhaar(fallbackNormalized)) { results[key] = formatAadhaar(fallbackNormalized) } else { - logger.warn("OpenAI fallback failed to produce valid Aadhaar") + val len = fallbackNormalized?.length ?: 0 + logger.warn("OpenAI fallback failed to produce valid Aadhaar (digits={})", len) results[key] = "NONE" } } diff --git a/src/main/kotlin/com/android/trisolarisserver/component/OpenAIVisionClient.kt b/src/main/kotlin/com/android/trisolarisserver/component/OpenAIVisionClient.kt index 946c07f..0e202d3 100644 --- a/src/main/kotlin/com/android/trisolarisserver/component/OpenAIVisionClient.kt +++ b/src/main/kotlin/com/android/trisolarisserver/component/OpenAIVisionClient.kt @@ -23,7 +23,7 @@ class OpenAIVisionClient( ) { private val logger = LoggerFactory.getLogger(OpenAIVisionClient::class.java) - fun extractAadhaarNumber(imageUrl: String): String? { + fun extractAadhaarNumber(imageUrl: String, localPath: String?, contentType: String?): String? { if (apiKey.isBlank()) { logger.warn("OpenAI fallback skipped: openai.apiKey is blank") return null @@ -32,8 +32,9 @@ class OpenAIVisionClient( val headers = HttpHeaders() headers.contentType = MediaType.APPLICATION_JSON headers.setBearerAuth(apiKey) + val resolvedImage = buildDataUrl(localPath, contentType) ?: imageUrl val first = call( - imageUrl, + resolvedImage, "Read extremely carefully. Aadhaar number = 12 digits. Reply ONLY the 12 digits or NONE.", "aadhaar_default", headers @@ -45,7 +46,7 @@ class OpenAIVisionClient( logger.info("OpenAI fallback returned NONE; retrying with focused prompt") } return call( - imageUrl, + resolvedImage, "Focus on the 12-digit Aadhaar number printed on the card (often vertical on the left). Reply ONLY the 12 digits or NONE.", "aadhaar_left_strip", headers @@ -130,4 +131,25 @@ class OpenAIVisionClient( val digits = trimmed.filter { it.isDigit() } return if (digits.isNotBlank()) "digits_len=${digits.length}" else "text_len=${trimmed.length}" } + + private fun buildDataUrl(localPath: String?, contentType: String?): String? { + if (localPath.isNullOrBlank()) return null + val type = contentType ?: guessContentType(localPath) ?: return null + return try { + val bytes = java.nio.file.Files.readAllBytes(java.nio.file.Paths.get(localPath)) + val base64 = java.util.Base64.getEncoder().encodeToString(bytes) + "data:$type;base64,$base64" + } catch (e: Exception) { + logger.warn("OpenAI fallback failed to read local image: {}", e.message) + null + } + } + + private fun guessContentType(path: String): String? { + return when { + path.endsWith(".jpg", true) || path.endsWith(".jpeg", true) -> "image/jpeg" + path.endsWith(".png", true) -> "image/png" + else -> null + } + } }