730 lines
30 KiB
PHP
730 lines
30 KiB
PHP
<?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'];
|
|
|
|
$this->logRagStep('JOB_START', 'started', 'readFileAndCalculateCommission', ['policy_id' => $policyId]);
|
|
|
|
$readDoc = $this->readPolicyDocViaRag($policyId);
|
|
$calculateCommission = ['status' => 'failed'];
|
|
|
|
if ($readDoc['status'] === 'success') {
|
|
$extracted = $readDoc['data'];
|
|
$policyData = $this->mapExtractedDataToPolicyFields($extracted);
|
|
|
|
$this->logRagStep('DB_UPDATE', 'started', 'Updating policy from RAG data', [
|
|
'policy_id' => $policyId,
|
|
'payload' => $policyData,
|
|
]);
|
|
|
|
$updateStatus = $this->PolicyModel->update($policyId, $policyData);
|
|
|
|
if ($updateStatus) {
|
|
$this->logRagStep('DB_UPDATE', 'success', 'Policy updated from RAG data', ['policy_id' => $policyId]);
|
|
|
|
$this->logRagStep('COMMISSION', 'started', 'Calculating commission', ['policy_id' => $policyId]);
|
|
$policyController = new PolicyController();
|
|
$calculateCommission = $policyController->calculateCommission($policyId);
|
|
|
|
$commissionStatus = $calculateCommission['status'] ?? 'failed';
|
|
$this->logRagStep(
|
|
'COMMISSION',
|
|
$commissionStatus === 'success' ? 'success' : 'failed',
|
|
'Commission calculation finished',
|
|
['policy_id' => $policyId, 'result' => $commissionStatus]
|
|
);
|
|
} else {
|
|
$this->logRagStep('DB_UPDATE', 'failed', 'Failed to update policy from RAG data', ['policy_id' => $policyId]);
|
|
}
|
|
} else {
|
|
$this->logRagStep('JOB_READ', 'failed', 'readPolicyDocViaRag failed', [
|
|
'policy_id' => $policyId,
|
|
'response' => $readDoc,
|
|
]);
|
|
}
|
|
|
|
$this->logRagStep('JOB_END', 'completed', 'readFileAndCalculateCommission finished', [
|
|
'policy_id' => $policyId,
|
|
'readPolicyDocViaRag' => $readDoc['status'],
|
|
'calculateCommission' => $calculateCommission['status'] ?? 'failed',
|
|
]);
|
|
|
|
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;
|
|
|
|
$this->logRagStep('READ_START', 'started', 'readPolicyDocViaRag', ['policy_id' => $policyId]);
|
|
|
|
try {
|
|
$record = $this->PolicyModel->find((int) $policyId);
|
|
|
|
if (!$record) {
|
|
$this->logRagStep('POLICY_LOOKUP', 'failed', 'Policy not found', ['policy_id' => $policyId]);
|
|
return $this->formatReadResponse('failed', 'Policy not found');
|
|
}
|
|
|
|
$this->logRagStep('POLICY_LOOKUP', 'success', 'Policy found', [
|
|
'policy_id' => $policyId,
|
|
'pdf_file' => $record['policy_pdf_file_name'] ?? null,
|
|
]);
|
|
|
|
$pdfFileName = $record['policy_pdf_file_name'] ?? '';
|
|
|
|
if ($pdfFileName === '' || !policy_exists_by_type($pdfFileName, 'policy_pdf')) {
|
|
$pdfFilePath = $pdfFileName !== '' ? policy_local_pdf_path($pdfFileName) : '';
|
|
$this->logRagStep('PDF_CHECK', 'failed', 'PDF file not found', ['path' => $pdfFilePath]);
|
|
return $this->formatReadResponse('failed', "File not found at {$pdfFilePath}");
|
|
}
|
|
|
|
$pdfFilePath = policy_local_pdf_path($pdfFileName);
|
|
|
|
$this->logRagStep('PDF_CHECK', 'success', 'PDF file found', ['path' => $pdfFilePath]);
|
|
|
|
$this->logRagStep('UPLOAD', 'started', 'Uploading PDF to RAG API', ['path' => $pdfFilePath]);
|
|
$uploadResult = $this->uploadFileToRag($pdfFilePath, (int) $policyId);
|
|
if ($uploadResult['status'] !== 'success') {
|
|
return $this->formatReadResponse('failed', $uploadResult['message']);
|
|
}
|
|
|
|
$fileId = $uploadResult['data']['file_id'] ?? null;
|
|
if (empty($fileId)) {
|
|
$this->logRagStep('UPLOAD', 'failed', 'RAG upload did not return file_id', ['policy_id' => $policyId]);
|
|
return $this->formatReadResponse('failed', 'RAG upload did not return file_id');
|
|
}
|
|
|
|
$this->logRagStep('SAVE_RAG_FILE_ID', 'started', 'Saving rag_file_id', [
|
|
'policy_id' => $policyId,
|
|
'file_id' => $fileId,
|
|
]);
|
|
|
|
$updated = $this->PolicyModel->update((int) $policyId, ['rag_file_id' => $fileId]);
|
|
if ($updated === false) {
|
|
$this->logRagStep('SAVE_RAG_FILE_ID', 'failed', 'Failed to save rag_file_id', [
|
|
'policy_id' => $policyId,
|
|
'file_id' => $fileId,
|
|
]);
|
|
} else {
|
|
$this->logRagStep('SAVE_RAG_FILE_ID', 'success', 'rag_file_id saved', [
|
|
'policy_id' => $policyId,
|
|
'file_id' => $fileId,
|
|
]);
|
|
}
|
|
|
|
$this->logRagStep('INDEX_POLL', 'started', 'Waiting for RAG file to be indexed', [
|
|
'policy_id' => $policyId,
|
|
'file_id' => $fileId,
|
|
]);
|
|
|
|
$indexResult = $this->waitForFileIndexed($fileId, (int) $policyId);
|
|
if ($indexResult['status'] !== 'success') {
|
|
return $this->formatReadResponse('failed', $indexResult['message']);
|
|
}
|
|
|
|
$this->logRagStep('CHAT', 'started', 'Sending extraction prompt to RAG API', [
|
|
'policy_id' => $policyId,
|
|
'file_id' => $fileId,
|
|
]);
|
|
|
|
$chatResult = $this->chatWithRag($fileId, $this->getPolicyReadPrompt(), (int) $policyId);
|
|
if ($chatResult['status'] !== 'success') {
|
|
return $this->formatReadResponse('failed', $chatResult['message']);
|
|
}
|
|
|
|
$answer = $chatResult['data']['answer'] ?? '';
|
|
$finalData = $this->extractJsonFromText($answer, (int) $policyId);
|
|
|
|
if ($finalData === null) {
|
|
$this->logRagStep('JSON_EXTRACT', 'failed', 'Could not extract valid JSON from RAG answer', [
|
|
'policy_id' => $policyId,
|
|
'file_id' => $fileId,
|
|
'answer_snippet' => substr($answer, 0, 500),
|
|
]);
|
|
return $this->formatReadResponse('failed', 'Could not extract valid JSON from RAG answer');
|
|
}
|
|
|
|
$this->logRagStep('READ_COMPLETE', 'success', 'Policy document read successfully', [
|
|
'policy_id' => $policyId,
|
|
'file_id' => $fileId,
|
|
]);
|
|
|
|
return $this->formatReadResponse('success', 'Doc read success', $finalData);
|
|
} catch (\Throwable $e) {
|
|
$this->logRagStep('READ_ERROR', 'failed', $e->getMessage(), [
|
|
'policy_id' => $policyId,
|
|
'file_id' => $fileId,
|
|
'line' => $e->getLine(),
|
|
]);
|
|
return $this->formatReadResponse('failed', 'Error: ' . $e->getMessage());
|
|
} finally {
|
|
if (!empty($fileId)) {
|
|
$this->logRagStep('DELETE', 'started', 'Deleting RAG file', [
|
|
'policy_id' => $policyId,
|
|
'file_id' => $fileId,
|
|
]);
|
|
|
|
$deleteResult = $this->deleteRagFile($fileId, (int) $policyId);
|
|
if ($deleteResult['status'] !== 'success') {
|
|
$this->logRagStep('DELETE', 'failed', $deleteResult['message'] ?? 'RAG file delete failed', [
|
|
'policy_id' => $policyId,
|
|
'file_id' => $fileId,
|
|
]);
|
|
}
|
|
}
|
|
|
|
$this->logRagStep('READ_END', 'completed', 'readPolicyDocViaRag finished', [
|
|
'policy_id' => $policyId,
|
|
'file_id' => $fileId,
|
|
]);
|
|
}
|
|
}
|
|
|
|
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, ?int $policyId = null): 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) {
|
|
$this->logRagStep('UPLOAD', 'failed', 'cURL error: ' . $curlError, [
|
|
'policy_id' => $policyId,
|
|
'url' => $url,
|
|
]);
|
|
return ['status' => 'failed', 'message' => 'RAG upload cURL error: ' . $curlError];
|
|
}
|
|
|
|
if ($httpCode < 200 || $httpCode >= 300) {
|
|
$this->logRagStep('UPLOAD', 'failed', "HTTP {$httpCode}", [
|
|
'policy_id' => $policyId,
|
|
'response' => 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'])) {
|
|
$this->logRagStep('UPLOAD', 'failed', 'Invalid API response', [
|
|
'policy_id' => $policyId,
|
|
'response' => substr((string) $response, 0, 500),
|
|
]);
|
|
return ['status' => 'failed', 'message' => 'RAG upload returned invalid response'];
|
|
}
|
|
|
|
$this->logRagStep('UPLOAD', 'success', 'PDF uploaded to RAG API', [
|
|
'policy_id' => $policyId,
|
|
'file_id' => $responseData['file_id'] ?? null,
|
|
'filename' => $responseData['filename'] ?? null,
|
|
'status' => $responseData['status'] ?? null,
|
|
]);
|
|
|
|
return ['status' => 'success', 'data' => $responseData];
|
|
}
|
|
|
|
private function getFileStatus(string $fileId, ?int $policyId = null): 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) {
|
|
$this->logRagStep('INDEX_POLL', 'failed', 'Status check cURL error: ' . $curlError, [
|
|
'policy_id' => $policyId,
|
|
'file_id' => $fileId,
|
|
]);
|
|
return ['status' => 'failed', 'message' => 'RAG status cURL error: ' . $curlError];
|
|
}
|
|
|
|
if ($httpCode < 200 || $httpCode >= 300) {
|
|
$this->logRagStep('INDEX_POLL', 'failed', "Status check HTTP {$httpCode}", [
|
|
'policy_id' => $policyId,
|
|
'file_id' => $fileId,
|
|
]);
|
|
return ['status' => 'failed', 'message' => "RAG status check failed with HTTP {$httpCode}"];
|
|
}
|
|
|
|
$responseData = json_decode((string) $response, true);
|
|
if (!is_array($responseData) || empty($responseData['success'])) {
|
|
$this->logRagStep('INDEX_POLL', 'failed', 'Status check invalid response', [
|
|
'policy_id' => $policyId,
|
|
'file_id' => $fileId,
|
|
]);
|
|
return ['status' => 'failed', 'message' => 'RAG status returned invalid response'];
|
|
}
|
|
|
|
return ['status' => 'success', 'data' => $responseData];
|
|
}
|
|
|
|
private function waitForFileIndexed(string $fileId, ?int $policyId = null): 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, $policyId);
|
|
if ($statusResult['status'] !== 'success') {
|
|
return $statusResult;
|
|
}
|
|
|
|
$fileStatus = strtolower((string) ($statusResult['data']['status'] ?? ''));
|
|
$this->logRagStep('INDEX_POLL', 'progress', "Poll attempt {$attempt}/{$maxAttempts}", [
|
|
'policy_id' => $policyId,
|
|
'file_id' => $fileId,
|
|
'status' => $fileStatus,
|
|
]);
|
|
|
|
if ($fileStatus === 'indexed') {
|
|
$this->logRagStep('INDEX_POLL', 'success', 'File indexed', [
|
|
'policy_id' => $policyId,
|
|
'file_id' => $fileId,
|
|
'chunk_count' => $statusResult['data']['chunk_count'] ?? null,
|
|
'attempts' => $attempt,
|
|
]);
|
|
return $statusResult;
|
|
}
|
|
|
|
if (in_array($fileStatus, ['failed', 'error'], true)) {
|
|
$this->logRagStep('INDEX_POLL', 'failed', 'Indexing failed', [
|
|
'policy_id' => $policyId,
|
|
'file_id' => $fileId,
|
|
'status' => $fileStatus,
|
|
]);
|
|
return ['status' => 'failed', 'message' => 'RAG file indexing failed with status: ' . $fileStatus];
|
|
}
|
|
|
|
if ($attempt < $maxAttempts) {
|
|
sleep($interval);
|
|
}
|
|
}
|
|
|
|
$this->logRagStep('INDEX_POLL', 'failed', 'Indexing timed out', [
|
|
'policy_id' => $policyId,
|
|
'file_id' => $fileId,
|
|
'max_attempts' => $maxAttempts,
|
|
'interval_sec' => $interval,
|
|
]);
|
|
|
|
return ['status' => 'failed', 'message' => 'RAG file indexing timed out'];
|
|
}
|
|
|
|
private function chatWithRag(string $fileId, string $question, ?int $policyId = null): 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) {
|
|
$this->logRagStep('CHAT', 'failed', 'cURL error: ' . $curlError, [
|
|
'policy_id' => $policyId,
|
|
'file_id' => $fileId,
|
|
]);
|
|
return ['status' => 'failed', 'message' => 'RAG chat cURL error: ' . $curlError];
|
|
}
|
|
|
|
if ($httpCode < 200 || $httpCode >= 300) {
|
|
$this->logRagStep('CHAT', 'failed', "HTTP {$httpCode}", [
|
|
'policy_id' => $policyId,
|
|
'file_id' => $fileId,
|
|
'response' => 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'])) {
|
|
$this->logRagStep('CHAT', 'failed', 'Invalid API response', [
|
|
'policy_id' => $policyId,
|
|
'file_id' => $fileId,
|
|
]);
|
|
return ['status' => 'failed', 'message' => 'RAG chat returned invalid response'];
|
|
}
|
|
|
|
$this->logRagStep('CHAT', 'success', 'RAG chat response received', [
|
|
'policy_id' => $policyId,
|
|
'file_id' => $fileId,
|
|
'answer_length' => strlen((string) ($responseData['answer'] ?? '')),
|
|
]);
|
|
|
|
return ['status' => 'success', 'data' => $responseData];
|
|
}
|
|
|
|
private function deleteRagFile(string $fileId, ?int $policyId = null): 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) {
|
|
$this->logRagStep('DELETE', 'failed', 'cURL error: ' . $curlError, [
|
|
'policy_id' => $policyId,
|
|
'file_id' => $fileId,
|
|
]);
|
|
return ['status' => 'failed', 'message' => 'RAG delete cURL error: ' . $curlError];
|
|
}
|
|
|
|
if ($httpCode < 200 || $httpCode >= 300) {
|
|
$this->logRagStep('DELETE', 'failed', "HTTP {$httpCode}", [
|
|
'policy_id' => $policyId,
|
|
'file_id' => $fileId,
|
|
'response' => 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'])) {
|
|
$this->logRagStep('DELETE', 'failed', 'Invalid API response', [
|
|
'policy_id' => $policyId,
|
|
'file_id' => $fileId,
|
|
]);
|
|
return ['status' => 'failed', 'message' => 'RAG delete returned invalid response'];
|
|
}
|
|
|
|
$this->logRagStep('DELETE', 'success', 'RAG file deleted', [
|
|
'policy_id' => $policyId,
|
|
'file_id' => $fileId,
|
|
]);
|
|
|
|
return ['status' => 'success', 'data' => $responseData];
|
|
}
|
|
|
|
private function extractJsonFromText(string $text, ?int $policyId = null): ?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);
|
|
if (!is_array($decoded)) {
|
|
$this->logRagStep('JSON_EXTRACT', 'failed', 'JSON decode failed', [
|
|
'policy_id' => $policyId,
|
|
'json_snippet' => substr($jsonString, 0, 500),
|
|
]);
|
|
return null;
|
|
}
|
|
|
|
$this->logRagStep('JSON_EXTRACT', 'success', 'JSON extracted from RAG answer', [
|
|
'policy_id' => $policyId,
|
|
'field_keys' => array_keys($decoded),
|
|
]);
|
|
|
|
return $decoded;
|
|
}
|
|
|
|
private function logRagStep(string $step, string $status, string $message, array $context = []): void
|
|
{
|
|
$level = in_array($status, ['failed', 'error'], true) ? 'error' : 'info';
|
|
$contextJson = $context ? ' | ' . json_encode($context, JSON_UNESCAPED_SLASHES) : '';
|
|
|
|
log_message($level, "[PolicyRag][{$step}][{$status}] {$message}{$contextJson}");
|
|
}
|
|
|
|
private function formatReadResponse(string $status, string $message, ?array $data = null): array
|
|
{
|
|
$result = ['status' => $status, 'message' => $message];
|
|
if ($data !== null) {
|
|
$result['data'] = $data;
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
}
|