RAG based policy pdf read : GWM

This commit is contained in:
Gowtham M 2026-06-30 17:03:21 +05:30
parent f4b172ca61
commit 6a13032714
4 changed files with 593 additions and 33 deletions

View File

@ -261,6 +261,7 @@ $routes->group('api', ['filter' => ["jwtAuth:1,2,3,4,agent","appSignature"] ], f
$routes->group('test', [ ], function ($routes) {
$routes->get("policyCommissionCalculationAndGeminiPolicyRead", "PolicyController::policyCommissionCalculationAndGeminiPolicyRead");
$routes->get("policyRagRead", "PolicyRagController::policyRagRead");
});

View File

@ -18,7 +18,7 @@ class JobWorker extends BaseController
[
'readFileAndCalculateCommission' => [
'type' => 'CC', // Handler Category (Possible values: HC, CC, HF)
'handler' => 'App\Controllers\PolicyController',
'handler' => 'App\Controllers\PolicyRagController',
],
];
@ -39,29 +39,32 @@ class JobWorker extends BaseController
public static function processJobs(array $jobdata = [])
{
// echo 'listen';//die();
$db = \Config\Database::connect();
$runningJob = $db->query(
"SELECT id FROM partner_jobs WHERE status=? LIMIT 1",
[self::STATUS_RUNNING]
)->getRow();
if ($runningJob !== null) {
SELF::streamOutput("Another job is already running (id: {$runningJob->id}). Skipping.\n");
return false;
}
$query = "
SELECT id, name, payload, uuid
FROM partner_jobs
WHERE status=?
ORDER BY created_dt ASC";
$where_condition = [self::STATUS_QUEUED];
$db = \Config\Database::connect();
$jobs = $db->query($query, $where_condition)->getResult();
ORDER BY created_dt ASC
LIMIT 1";
$jobs = $db->query($query, [self::STATUS_QUEUED])->getResult();
if(count($jobs))
{
// echo count($jobs);
// print_r($jobs);die;
foreach($jobs as $key => $job)
{
// echo $job->id.' - '.$job->name;
//sleep(1);
SELF::processjob(['id' => $job->id,'uuid' => $job->uuid]);
// usleep( 500000 );
}
if (count($jobs)) {
$job = $jobs[0];
return SELF::processJob(['id' => $job->id, 'uuid' => $job->uuid]);
}
return false;
}
/**
* process jobs
@ -72,27 +75,42 @@ class JobWorker extends BaseController
// print_r($jobdata);
// echo 'listen';
// die();
$query = "
$db = \Config\Database::connect();
$db->transStart();
// Do not start a new job while another job is already running.
$runningJob = $db->query(
"SELECT id FROM partner_jobs WHERE status=? LIMIT 1",
[self::STATUS_RUNNING]
)->getRow();
if ($runningJob !== null) {
$db->transComplete();
SELF::streamOutput("Another job is already running (id: {$runningJob->id}). Skipping.\n");
return false;
}
if (isset($jobdata) && count($jobdata)) {
$query = "
SELECT id, name, payload, uuid
FROM partner_jobs
WHERE status=? AND id=? AND uuid=?
ORDER BY created_dt ASC
LIMIT 1 FOR UPDATE";
$where_condition = [self::STATUS_QUEUED, $jobdata['id'], $jobdata['uuid']];
} else {
$query = "
SELECT id, name, payload, uuid
FROM partner_jobs
WHERE status=?
ORDER BY created_dt ASC
LIMIT 1 FOR UPDATE";
$where_condition = [self::STATUS_QUEUED];
if(isset($jobdata) && count($jobdata))
{
$query = "
SELECT id, name, payload, uuid
FROM partner_jobs
WHERE status=? AND id=? AND uuid=?
ORDER BY created_dt ASC
LIMIT 1 FOR UPDATE";
$where_condition = [self::STATUS_QUEUED,$jobdata['id'],$jobdata['uuid']];
$where_condition = [self::STATUS_QUEUED];
}
$db = \Config\Database::connect();
$job = $db->query($query, $where_condition)->getResult();
$db->transComplete();
// print_r($job);die();
if ($job !== [])
{

View File

@ -0,0 +1,540 @@
<?php
namespace App\Controllers;
use CodeIgniter\RESTful\ResourceController;
use App\Models\PolicyModel;
class PolicyRagController extends ResourceController
{
protected PolicyModel $PolicyModel;
protected array $tier1VehicleTypeMap = [
'two wheeler' => 'Two Wheeler',
'2 wheeler' => 'Two Wheeler',
'2w' => 'Two Wheeler',
'motorcycle' => 'Two Wheeler',
'bike' => 'Two Wheeler',
'scooter' => 'Two Wheeler',
'moped' => 'Two Wheeler',
'four wheeler' => 'Four Wheeler',
'4 wheeler' => 'Four Wheeler',
'4w' => 'Four Wheeler',
'private car' => 'Four Wheeler',
'car' => 'Four Wheeler',
'jeep' => 'Four Wheeler',
'goods vehicle' => 'Goods Vehicle',
'good vehicle' => 'Goods Vehicle',
'goods carrier' => 'Goods Vehicle',
'goods carrying vehicle' => 'Goods Vehicle',
'cargo vehicle' => 'Goods Vehicle',
'lorry' => 'Goods Vehicle',
'truck' => 'Goods Vehicle',
'transport vehicle goods'=> 'Goods Vehicle',
'commercial vehicle' => 'Commercial Vehicle',
'commercial car' => 'Commercial Vehicle',
'taxi' => 'Commercial Vehicle',
'cab' => 'Commercial Vehicle',
'hire vehicle' => 'Commercial Vehicle',
'passenger commercial' => 'Commercial Vehicle',
'bus' => 'Bus',
'passenger bus' => 'Bus',
'school bus' => 'Bus',
'private bus' => 'Bus',
'stage carriage' => 'Bus',
'tractor' => 'Tractor',
'agricultural tractor' => 'Tractor',
'farm tractor' => 'Tractor',
];
protected array $fuelType = [
'Petrol' => 'Petrol',
'Diesel' => 'Diesel',
'CNG' => 'CNG',
'LPG' => 'LPG',
];
public function __construct()
{
$this->PolicyModel = new PolicyModel();
}
/**
* Job handler: read policy PDF via RAG API, update DB, then calculate commission.
*/
public function readFileAndCalculateCommission(array $data): array
{
$policyId = $data['policy_id'];
log_message('debug', 'PolicyRag readFileAndCalculateCommission started for policy_id: ' . $policyId);
$readDoc = $this->readPolicyDocViaRag($policyId);
$calculateCommission = ['status' => 'failed'];
if ($readDoc['status'] === 'success') {
$extracted = $readDoc['data'];
$policyData = $this->mapExtractedDataToPolicyFields($extracted);
log_message('debug', 'Policy RAG Update Payload: ' . json_encode($policyData));
$updateStatus = $this->PolicyModel->update($policyId, $policyData);
if ($updateStatus) {
log_message('debug', 'Policy updated successfully via RAG for ID: ' . $policyId);
$policyController = new PolicyController();
$calculateCommission = $policyController->calculateCommission($policyId);
} else {
log_message('error', 'Failed to update policy via RAG for ID: ' . $policyId);
}
} else {
log_message('error', 'readPolicyDocViaRag returned failure: ' . json_encode($readDoc));
}
return [
'readPolicyDocViaRag' => $readDoc['status'],
'calculateCommission' => $calculateCommission['status'] ?? 'failed',
];
}
/**
* Test endpoint: ?type=read&policy_id=123
*/
public function policyRagRead()
{
$type = $this->request->getGet('type');
$policyId = $this->request->getGet('policy_id');
if ($type === 'read') {
$readDoc = $this->readPolicyDocViaRag($policyId);
if ($readDoc['status'] === 'success') {
$policyData = $this->mapExtractedDataToPolicyFields($readDoc['data']);
$this->PolicyModel->update($policyId, $policyData);
}
return $this->respond(['status' => $readDoc['status'], 'data' => $readDoc], 200);
}
if ($type === 'commission') {
$policyController = new PolicyController();
return $policyController->calculateCommission($policyId, true);
}
return $this->respond(['status' => 'failed', 'message' => 'Invalid type. Use type=read or type=commission'], 200);
}
/**
* Upload PDF to RAG, poll until indexed, extract JSON via chat, then delete RAG file.
*/
public function readPolicyDocViaRag($policyId = null)
{
$fileId = null;
try {
$record = $this->PolicyModel->find((int) $policyId);
if (!$record) {
return $this->formatReadResponse('failed', 'Policy not found');
}
$uploadedPath = WRITEPATH . 'uploads/policy/policy_pdf/';
$pdfFilePath = $uploadedPath . $record['policy_pdf_file_name'];
if (!file_exists($pdfFilePath)) {
log_message('info', "RAG read: PDF not found at {$pdfFilePath}");
return $this->formatReadResponse('failed', "File not found at {$pdfFilePath}");
}
$uploadResult = $this->uploadFileToRag($pdfFilePath);
if ($uploadResult['status'] !== 'success') {
return $this->formatReadResponse('failed', $uploadResult['message']);
}
$fileId = $uploadResult['data']['file_id'] ?? null;
if (empty($fileId)) {
return $this->formatReadResponse('failed', 'RAG upload did not return file_id');
}
$updated = $this->PolicyModel->update((int) $policyId, ['rag_file_id' => $fileId]);
if ($updated === false) {
log_message('error', "Failed to save rag_file_id={$fileId} for policy_id={$policyId}");
}
log_message('info', "RAG file uploaded. file_id={$fileId}, policy_id={$policyId}");
$indexResult = $this->waitForFileIndexed($fileId);
if ($indexResult['status'] !== 'success') {
return $this->formatReadResponse('failed', $indexResult['message']);
}
$chatResult = $this->chatWithRag($fileId, $this->getPolicyReadPrompt());
if ($chatResult['status'] !== 'success') {
return $this->formatReadResponse('failed', $chatResult['message']);
}
$answer = $chatResult['data']['answer'] ?? '';
// dd($answer);
$finalData = $this->extractJsonFromText($answer);
if ($finalData === null) {
return $this->formatReadResponse('failed', 'Could not extract valid JSON from RAG answer');
}
log_message('info', 'RAG policy doc read successful for policy_id=' . $policyId);
return $this->formatReadResponse('success', 'Doc read success', $finalData);
} catch (\Throwable $e) {
log_message('error', 'PolicyRag readPolicyDocViaRag error: ' . $e->getMessage());
return $this->formatReadResponse('failed', 'Error: ' . $e->getMessage());
} finally {
if (!empty($fileId)) {
$deleteResult = $this->deleteRagFile($fileId);
if ($deleteResult['status'] !== 'success') {
log_message('error', 'RAG file delete failed for file_id=' . $fileId . ': ' . ($deleteResult['message'] ?? ''));
}
}
}
}
private function getPolicyReadPrompt(): string
{
$sampleJson = json_encode([
'policy_number' => '',
'issued_date' => '',
'start_date' => '',
'end_date' => '',
'broker_name' => '',
'insured_name' => '',
'reg_no' => '',
'rto_state_code' => '',
'rto_city_code' => '',
'weight' => '',
'fuel_type' => '',
'date_of_registration' => '',
'year_of_manufacture' => '',
'engine_no' => '',
'chassis_no' => '',
'make' => '',
'model' => '',
'cubic_capacity' => '',
'vehicle_type' => '',
'tp' => 0,
'od' => 0,
'pa' => 0,
'cgst' => 0,
'sgst' => 0,
'igst' => 0,
'premium_amount' => 0,
], JSON_UNESCAPED_SLASHES);
return 'Read the motor policy document and return only valid JSON using this exact structure (no explanation): '
. $sampleJson
. '. Rules: vehicle_type must be one of Two Wheeler, Four Wheeler, Goods Vehicle, Bus, Tractor, Commercial Vehicle. '
. 'rto_state_code is first 2 characters of reg_no. rto_city_code is 3rd and 4th characters of reg_no. '
. 'All dates must be in YYYY-MM-DD format. TP premium must not include PA amount.';
}
private function mapExtractedDataToPolicyFields(array $data): array
{
$data = $this->normalizeExtractedData($data);
$vehicleType = $data['vehicle_type'] ?? null;
$fuelType = $data['fuel_type'] ?? null;
if ($vehicleType !== null && $vehicleType !== '') {
$vehicleType = $this->postProcessExtractedData((string) $vehicleType, $this->tier1VehicleTypeMap);
} else {
$vehicleType = 'UN_IDEN_DOC';
}
if ($fuelType !== null && $fuelType !== '') {
$fuelType = $this->postProcessExtractedData((string) $fuelType, $this->fuelType);
} else {
$fuelType = 'UN_IDEN_DOC';
}
return [
'policy_number' => $this->nullIfEmpty($data['policy_number'] ?? null),
'issued_date' => $this->nullIfEmpty($data['issued_date'] ?? null),
'start_date' => $this->nullIfEmpty($data['start_date'] ?? null),
'end_date' => $this->nullIfEmpty($data['end_date'] ?? null),
'broker_name' => $this->nullIfEmpty($data['broker_name'] ?? null),
'tp' => $this->nullIfEmpty($data['tp'] ?? null),
'od' => $this->nullIfEmpty($data['od'] ?? null),
'pa' => $this->nullIfEmpty($data['pa'] ?? null),
'cgst' => $this->nullIfEmpty($data['cgst'] ?? 0),
'sgst' => $this->nullIfEmpty($data['sgst'] ?? 0),
'igst' => $this->nullIfEmpty($data['igst'] ?? 0),
'premium_amount' => $this->nullIfEmpty($data['premium_amount'] ?? null),
'rto_state_code' => $this->nullIfEmpty($data['rto_state_code'] ?? null),
'rto_city_code' => $this->nullIfEmpty($data['rto_city_code'] ?? null),
'weight' => $this->nullIfEmpty($data['weight'] ?? null),
'fuel_type' => $fuelType,
'date_of_registration' => $this->nullIfEmpty($data['date_of_registration'] ?? null),
'year_of_manufacture' => $this->nullIfEmpty($data['year_of_manufacture'] ?? null),
'engine_no' => $this->nullIfEmpty($data['engine_no'] ?? null),
'chassis_no' => $this->nullIfEmpty($data['chassis_no'] ?? null),
'make' => $this->nullIfEmpty($data['make'] ?? null),
'model' => $this->nullIfEmpty($data['model'] ?? null),
'cubic_capacity' => $this->nullIfEmpty($data['cubic_capacity'] ?? null),
'product' => $vehicleType,
'rc_no' => $this->nullIfEmpty($data['reg_no'] ?? null),
'insured_name' => $this->nullIfEmpty($data['insured_name'] ?? null),
];
}
private function normalizeExtractedData(array $data): array
{
if (!isset($data['policy']) && !isset($data['vehicle']) && !isset($data['premium'])) {
return $data;
}
return [
'policy_number' => $data['policy']['policy_number'] ?? null,
'issued_date' => $data['policy']['issue_date'] ?? null,
'start_date' => $data['policy']['period']['start'] ?? null,
'end_date' => $data['policy']['period']['end'] ?? null,
'broker_name' => $data['policy']['intermediary_name'] ?? null,
'insured_name' => $data['insured']['name'] ?? null,
'reg_no' => $data['vehicle']['reg_no'] ?? null,
'rto_state_code' => $data['vehicle']['rto_state_code'] ?? null,
'rto_city_code' => $data['vehicle']['rto_city_code'] ?? null,
'weight' => $data['vehicle']['weight'] ?? null,
'fuel_type' => $data['vehicle']['fuel_type'] ?? null,
'date_of_registration' => $data['vehicle']['date_of_registration'] ?? null,
'year_of_manufacture' => $data['vehicle']['year_of_manufacture'] ?? null,
'engine_no' => $data['vehicle']['engine_no'] ?? null,
'chassis_no' => $data['vehicle']['chassis_no'] ?? null,
'make' => $data['vehicle']['make'] ?? null,
'model' => $data['vehicle']['model'] ?? null,
'cubic_capacity' => $data['vehicle']['cubic_capacity'] ?? null,
'vehicle_type' => $data['vehicle']['vehicle_type'] ?? null,
'tp' => $data['premium']['tp'] ?? null,
'od' => $data['premium']['od'] ?? null,
'pa' => $data['premium']['pa'] ?? null,
'cgst' => $data['premium']['taxes']['cgst'] ?? 0,
'sgst' => $data['premium']['taxes']['sgst'] ?? 0,
'igst' => $data['premium']['taxes']['igst'] ?? 0,
'premium_amount' => $data['premium']['total'] ?? null,
];
}
private function nullIfEmpty($value)
{
if ($value === '' || $value === null) {
return null;
}
return $value;
}
private function postProcessExtractedData(string $incoming, array $masterArray): string
{
$value = strtolower(trim($incoming));
$value = preg_replace('/\s+/', ' ', $value);
return $masterArray[$value] ?? 'UN_IDEN_TYPE';
}
private function ragApiBaseUrl(): string
{
$baseUrl = rtrim((string) getenv('RAG_API_BASE_URL'), '/');
if ($baseUrl === '') {
throw new \RuntimeException('RAG_API_BASE_URL is not configured');
}
return $baseUrl;
}
private function uploadFileToRag(string $pdfFilePath): array
{
$url = $this->ragApiBaseUrl() . '/api/files/upload';
$cfile = new \CURLFile($pdfFilePath, 'application/pdf', basename($pdfFilePath));
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, [
'uploaded_file' => $cfile,
'auto_detect' => 'true',
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
$curlError = curl_error($ch);
$httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($curlError) {
log_message('error', 'RAG upload cURL error: ' . $curlError);
return ['status' => 'failed', 'message' => 'RAG upload cURL error: ' . $curlError];
}
if ($httpCode < 200 || $httpCode >= 300) {
log_message('error', "RAG upload HTTP {$httpCode}: " . substr((string) $response, 0, 500));
return ['status' => 'failed', 'message' => "RAG upload failed with HTTP {$httpCode}"];
}
$responseData = json_decode((string) $response, true);
if (!is_array($responseData) || empty($responseData['success'])) {
return ['status' => 'failed', 'message' => 'RAG upload returned invalid response'];
}
return ['status' => 'success', 'data' => $responseData];
}
private function getFileStatus(string $fileId): array
{
$url = $this->ragApiBaseUrl() . '/api/files/status?file_id=' . urlencode($fileId);
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
$curlError = curl_error($ch);
$httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($curlError) {
return ['status' => 'failed', 'message' => 'RAG status cURL error: ' . $curlError];
}
if ($httpCode < 200 || $httpCode >= 300) {
return ['status' => 'failed', 'message' => "RAG status check failed with HTTP {$httpCode}"];
}
$responseData = json_decode((string) $response, true);
if (!is_array($responseData) || empty($responseData['success'])) {
return ['status' => 'failed', 'message' => 'RAG status returned invalid response'];
}
return ['status' => 'success', 'data' => $responseData];
}
private function waitForFileIndexed(string $fileId): array
{
$interval = (int) (getenv('RAG_API_STATUS_POLL_INTERVAL') ?: 10);
$maxAttempts = (int) (getenv('RAG_API_STATUS_MAX_ATTEMPTS') ?: 60);
for ($attempt = 1; $attempt <= $maxAttempts; $attempt++) {
$statusResult = $this->getFileStatus($fileId);
if ($statusResult['status'] !== 'success') {
return $statusResult;
}
$fileStatus = strtolower((string) ($statusResult['data']['status'] ?? ''));
log_message('info', "RAG file status poll attempt {$attempt}/{$maxAttempts}: file_id={$fileId}, status={$fileStatus}");
if ($fileStatus === 'indexed') {
return $statusResult;
}
if (in_array($fileStatus, ['failed', 'error'], true)) {
return ['status' => 'failed', 'message' => 'RAG file indexing failed with status: ' . $fileStatus];
}
if ($attempt < $maxAttempts) {
sleep($interval);
}
}
return ['status' => 'failed', 'message' => 'RAG file indexing timed out'];
}
private function chatWithRag(string $fileId, string $question): array
{
$url = $this->ragApiBaseUrl() . '/api/chat';
$payload = json_encode([
'question' => $question,
'file_id' => $fileId,
]);
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
$curlError = curl_error($ch);
$httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($curlError) {
return ['status' => 'failed', 'message' => 'RAG chat cURL error: ' . $curlError];
}
if ($httpCode < 200 || $httpCode >= 300) {
log_message('error', "RAG chat HTTP {$httpCode}: " . substr((string) $response, 0, 500));
return ['status' => 'failed', 'message' => "RAG chat failed with HTTP {$httpCode}"];
}
$responseData = json_decode((string) $response, true);
if (!is_array($responseData) || empty($responseData['success'])) {
return ['status' => 'failed', 'message' => 'RAG chat returned invalid response'];
}
return ['status' => 'success', 'data' => $responseData];
}
private function deleteRagFile(string $fileId): array
{
$url = $this->ragApiBaseUrl() . '/api/files/id/' . urlencode($fileId);
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
$curlError = curl_error($ch);
$httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($curlError) {
return ['status' => 'failed', 'message' => 'RAG delete cURL error: ' . $curlError];
}
if ($httpCode < 200 || $httpCode >= 300) {
log_message('error', "RAG delete HTTP {$httpCode}: " . substr((string) $response, 0, 500));
return ['status' => 'failed', 'message' => "RAG delete failed with HTTP {$httpCode}"];
}
$responseData = json_decode((string) $response, true);
if (!is_array($responseData) || empty($responseData['success'])) {
return ['status' => 'failed', 'message' => 'RAG delete returned invalid response'];
}
log_message('info', 'RAG file deleted: file_id=' . $fileId);
return ['status' => 'success', 'data' => $responseData];
}
private function extractJsonFromText(string $text): ?array
{
$jsonString = '';
if (preg_match('/```json\s*(.*?)\s*```/s', $text, $matches)) {
$jsonString = trim($matches[1]);
} else {
$start = strpos($text, '{');
$end = strrpos($text, '}');
if ($start !== false && $end !== false && $end > $start) {
$jsonString = substr($text, $start, $end - $start + 1);
}
}
if ($jsonString === '') {
return null;
}
$decoded = json_decode($jsonString, true);
return is_array($decoded) ? $decoded : null;
}
private function formatReadResponse(string $status, string $message, ?array $data = null): array
{
$result = ['status' => $status, 'message' => $message];
if ($data !== null) {
$result['data'] = $data;
}
return $result;
}
}

View File

@ -59,7 +59,8 @@ class PolicyModel extends Model
'commission_from_insurer',
'agent_retention_rate',
'manager_retention_rate',
'is_invoice_generated'
'is_invoice_generated',
'rag_file_id'
];
protected $useTimestamps = false;