Queue guest document extraction
All checks were successful
build-and-deploy / build-deploy (push) Successful in 3m36s

This commit is contained in:
androidlover5842
2026-01-30 15:08:47 +05:30
parent 69e51deeca
commit 21721d3905
2 changed files with 88 additions and 55 deletions

View File

@@ -0,0 +1,27 @@
package com.android.trisolarisserver.component
import jakarta.annotation.PreDestroy
import org.springframework.stereotype.Component
import java.util.concurrent.Executors
@Component
class ExtractionQueue {
private val executor = Executors.newSingleThreadExecutor { runnable ->
Thread(runnable, "doc-extraction-queue").apply { isDaemon = true }
}
fun enqueue(task: () -> Unit) {
executor.submit {
try {
task()
} catch (_: Exception) {
// Best-effort processing; failures should not crash the worker.
}
}
}
@PreDestroy
fun shutdown() {
executor.shutdown()
}
}

View File

@@ -2,6 +2,7 @@ package com.android.trisolarisserver.controller
import com.android.trisolarisserver.component.DocumentStorage
import com.android.trisolarisserver.component.DocumentTokenService
import com.android.trisolarisserver.component.ExtractionQueue
import com.android.trisolarisserver.component.LlamaClient
import com.android.trisolarisserver.component.PropertyAccess
import com.android.trisolarisserver.db.repo.BookingRepo
@@ -38,6 +39,7 @@ class GuestDocuments(
private val appUserRepo: AppUserRepo,
private val storage: DocumentStorage,
private val tokenService: DocumentTokenService,
private val extractionQueue: ExtractionQueue,
private val llamaClient: LlamaClient,
private val objectMapper: ObjectMapper,
@org.springframework.beans.factory.annotation.Value("\${storage.documents.publicBaseUrl}")
@@ -88,8 +90,8 @@ class GuestDocuments(
storagePath = stored.storagePath
)
val saved = guestDocumentRepo.save(document)
runExtraction(saved, propertyId, guestId)
return guestDocumentRepo.save(saved).toResponse(objectMapper)
runExtraction(saved.id!!, propertyId, guestId)
return saved.toResponse(objectMapper)
}
@GetMapping
@@ -135,63 +137,67 @@ class GuestDocuments(
.body(resource)
}
private fun runExtraction(document: GuestDocument, propertyId: UUID, guestId: UUID) {
try {
val token = tokenService.createToken(document.id.toString())
val imageUrl =
"${publicBaseUrl}/properties/$propertyId/guests/$guestId/documents/${document.id}/file?token=$token"
private fun runExtraction(documentId: UUID, propertyId: UUID, guestId: UUID) {
extractionQueue.enqueue {
val document = guestDocumentRepo.findById(documentId).orElse(null) ?: return@enqueue
try {
val token = tokenService.createToken(document.id.toString())
val imageUrl =
"${publicBaseUrl}/properties/$propertyId/guests/$guestId/documents/${document.id}/file?token=$token"
val results = linkedMapOf<String, String>()
results["hasAadhar"] = llamaClient.ask(imageUrl, "CONTAINS AADHAAR? Answer YES or NO only.")
results["hasUidai"] = llamaClient.ask(imageUrl, "CONTAINS UIDAI? Answer YES or NO only.")
results["hasTransportDept"] = llamaClient.ask(imageUrl, "CONTAINS TRANSPORT DEPARTMENT? Answer YES or NO only.")
results["hasIncomeTaxDept"] = llamaClient.ask(imageUrl, "CONTAINS INCOME TAX DEPARTMENT? Answer YES or NO only.")
results["hasElectionCommission"] = llamaClient.ask(imageUrl, "CONTAINS ELECTION COMMISSION OF INDIA? Answer YES or NO only.")
results["hasDrivingLicence"] = llamaClient.ask(imageUrl, "CONTAINS DRIVING LICENCE? Answer YES or NO only.")
results["hasPassport"] = llamaClient.ask(imageUrl, "CONTAINS PASSPORT? Answer YES or NO only.")
results["hasPolice"] = llamaClient.ask(imageUrl, "CONTAINS POLICE? Answer YES or NO only.")
results["hasCourt"] = llamaClient.ask(imageUrl, "CONTAINS COURT? Answer YES or NO only.")
results["hasHighCourt"] = llamaClient.ask(imageUrl, "CONTAINS HIGH COURT? Answer YES or NO only.")
results["hasSupremeCourt"] = llamaClient.ask(imageUrl, "CONTAINS SUPREME COURT? Answer YES or NO only.")
results["hasJudiciary"] = llamaClient.ask(imageUrl, "CONTAINS JUDICIARY? Answer YES or NO only.")
results["hasAddress"] = llamaClient.ask(imageUrl, "ADDRESS PRESENT? Answer YES or NO only.")
results["hasGender"] = llamaClient.ask(imageUrl, "GENDER PRESENT? Answer YES or NO only.")
results["hasNationality"] = llamaClient.ask(imageUrl, "NATIONALITY PRESENT? Answer YES or NO only.")
results["name"] = llamaClient.ask(imageUrl, "NAME? Reply only the name or NONE.")
results["dob"] = llamaClient.ask(imageUrl, "DOB? Reply only date or NONE.")
results["idNumber"] = llamaClient.ask(imageUrl, "ID NUMBER? Reply only number or NONE.")
results["address"] = llamaClient.ask(imageUrl, "ADDRESS? Reply only address or NONE.")
results["vehicleNumber"] = llamaClient.ask(imageUrl, "VEHICLE NUMBER? Reply only number or NONE.")
results["isVehiclePhoto"] = llamaClient.ask(imageUrl, "IS THIS A VEHICLE PHOTO? Answer YES or NO only.")
results["pinCode"] = llamaClient.ask(imageUrl, "PIN CODE? Reply only pin or NONE.")
results["city"] = llamaClient.ask(imageUrl, "CITY? Reply only city or NONE.")
results["gender"] = llamaClient.ask(imageUrl, "GENDER? Reply only MALE/FEMALE/OTHER or NONE.")
results["nationality"] = llamaClient.ask(imageUrl, "NATIONALITY? Reply only nationality or NONE.")
val results = linkedMapOf<String, String>()
results["hasAadhar"] = llamaClient.ask(imageUrl, "CONTAINS AADHAAR? Answer YES or NO only.")
results["hasUidai"] = llamaClient.ask(imageUrl, "CONTAINS UIDAI? Answer YES or NO only.")
results["hasTransportDept"] = llamaClient.ask(imageUrl, "CONTAINS TRANSPORT DEPARTMENT? Answer YES or NO only.")
results["hasIncomeTaxDept"] = llamaClient.ask(imageUrl, "CONTAINS INCOME TAX DEPARTMENT? Answer YES or NO only.")
results["hasElectionCommission"] = llamaClient.ask(imageUrl, "CONTAINS ELECTION COMMISSION OF INDIA? Answer YES or NO only.")
results["hasDrivingLicence"] = llamaClient.ask(imageUrl, "CONTAINS DRIVING LICENCE? Answer YES or NO only.")
results["hasPassport"] = llamaClient.ask(imageUrl, "CONTAINS PASSPORT? Answer YES or NO only.")
results["hasPolice"] = llamaClient.ask(imageUrl, "CONTAINS POLICE? Answer YES or NO only.")
results["hasCourt"] = llamaClient.ask(imageUrl, "CONTAINS COURT? Answer YES or NO only.")
results["hasHighCourt"] = llamaClient.ask(imageUrl, "CONTAINS HIGH COURT? Answer YES or NO only.")
results["hasSupremeCourt"] = llamaClient.ask(imageUrl, "CONTAINS SUPREME COURT? Answer YES or NO only.")
results["hasJudiciary"] = llamaClient.ask(imageUrl, "CONTAINS JUDICIARY? Answer YES or NO only.")
results["hasAddress"] = llamaClient.ask(imageUrl, "ADDRESS PRESENT? Answer YES or NO only.")
results["hasGender"] = llamaClient.ask(imageUrl, "GENDER PRESENT? Answer YES or NO only.")
results["hasNationality"] = llamaClient.ask(imageUrl, "NATIONALITY PRESENT? Answer YES or NO only.")
results["name"] = llamaClient.ask(imageUrl, "NAME? Reply only the name or NONE.")
results["dob"] = llamaClient.ask(imageUrl, "DOB? Reply only date or NONE.")
results["idNumber"] = llamaClient.ask(imageUrl, "ID NUMBER? Reply only number or NONE.")
results["address"] = llamaClient.ask(imageUrl, "ADDRESS? Reply only address or NONE.")
results["vehicleNumber"] = llamaClient.ask(imageUrl, "VEHICLE NUMBER? Reply only number or NONE.")
results["isVehiclePhoto"] = llamaClient.ask(imageUrl, "IS THIS A VEHICLE PHOTO? Answer YES or NO only.")
results["pinCode"] = llamaClient.ask(imageUrl, "PIN CODE? Reply only pin or NONE.")
results["city"] = llamaClient.ask(imageUrl, "CITY? Reply only city or NONE.")
results["gender"] = llamaClient.ask(imageUrl, "GENDER? Reply only MALE/FEMALE/OTHER or NONE.")
results["nationality"] = llamaClient.ask(imageUrl, "NATIONALITY? Reply only nationality or NONE.")
results["docType"] = when {
results["hasCourt"].orEmpty().contains("YES", ignoreCase = true) ||
results["hasHighCourt"].orEmpty().contains("YES", ignoreCase = true) ||
results["hasSupremeCourt"].orEmpty().contains("YES", ignoreCase = true) ||
results["hasJudiciary"].orEmpty().contains("YES", ignoreCase = true) -> "COURT_ID"
results["hasPolice"].orEmpty().contains("YES", ignoreCase = true) -> "POLICE_ID"
results["hasPassport"].orEmpty().contains("YES", ignoreCase = true) -> "PASSPORT"
results["hasTransportDept"].orEmpty().contains("YES", ignoreCase = true) ||
results["hasDrivingLicence"].orEmpty().contains("YES", ignoreCase = true) -> "TRANSPORT"
results["hasIncomeTaxDept"].orEmpty().contains("YES", ignoreCase = true) -> "PAN"
results["hasElectionCommission"].orEmpty().contains("YES", ignoreCase = true) -> "VOTER_ID"
results["hasAadhar"].orEmpty().contains("YES", ignoreCase = true) ||
results["hasUidai"].orEmpty().contains("YES", ignoreCase = true) -> {
if (results["hasAddress"].orEmpty().contains("YES", ignoreCase = true)) "AADHAR_BACK" else "AADHAR_FRONT"
results["docType"] = when {
results["hasCourt"].orEmpty().contains("YES", ignoreCase = true) ||
results["hasHighCourt"].orEmpty().contains("YES", ignoreCase = true) ||
results["hasSupremeCourt"].orEmpty().contains("YES", ignoreCase = true) ||
results["hasJudiciary"].orEmpty().contains("YES", ignoreCase = true) -> "COURT_ID"
results["hasPolice"].orEmpty().contains("YES", ignoreCase = true) -> "POLICE_ID"
results["hasPassport"].orEmpty().contains("YES", ignoreCase = true) -> "PASSPORT"
results["hasTransportDept"].orEmpty().contains("YES", ignoreCase = true) ||
results["hasDrivingLicence"].orEmpty().contains("YES", ignoreCase = true) -> "TRANSPORT"
results["hasIncomeTaxDept"].orEmpty().contains("YES", ignoreCase = true) -> "PAN"
results["hasElectionCommission"].orEmpty().contains("YES", ignoreCase = true) -> "VOTER_ID"
results["hasAadhar"].orEmpty().contains("YES", ignoreCase = true) ||
results["hasUidai"].orEmpty().contains("YES", ignoreCase = true) -> {
if (results["hasAddress"].orEmpty().contains("YES", ignoreCase = true)) "AADHAR_BACK" else "AADHAR_FRONT"
}
results["vehicleNumber"].orEmpty().isNotBlank() && !results["vehicleNumber"]!!.contains("NONE", true) -> "VEHICLE"
results["isVehiclePhoto"].orEmpty().contains("YES", ignoreCase = true) -> "VEHICLE_PHOTO"
else -> "UNKNOWN"
}
results["vehicleNumber"].orEmpty().isNotBlank() && !results["vehicleNumber"]!!.contains("NONE", true) -> "VEHICLE"
results["isVehiclePhoto"].orEmpty().contains("YES", ignoreCase = true) -> "VEHICLE_PHOTO"
else -> "UNKNOWN"
}
document.extractedData = objectMapper.writeValueAsString(results)
document.extractedAt = OffsetDateTime.now()
} catch (_: Exception) {
// Keep upload successful even if AI extraction fails.
document.extractedData = objectMapper.writeValueAsString(results)
document.extractedAt = OffsetDateTime.now()
guestDocumentRepo.save(document)
} catch (_: Exception) {
// Keep upload successful even if AI extraction fails.
}
}
}