feat: add backend OCR manual proxy endpoint (refs #135)
Add POST /api/ocr/extract/manual endpoint that proxies to the Python OCR service's manual extraction pipeline. Includes Pro tier gating via document.scanMaintenanceSchedule, PDF-only validation, 200MB file size limit, and async 202 job response for polling via existing job status endpoint. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,8 @@ import { logger } from '../../../core/logging/logger';
|
||||
import { ocrClient, JobNotFoundError } from '../external/ocr-client';
|
||||
import type {
|
||||
JobResponse,
|
||||
ManualJobResponse,
|
||||
ManualJobSubmitRequest,
|
||||
OcrExtractRequest,
|
||||
OcrJobSubmitRequest,
|
||||
OcrResponse,
|
||||
@@ -278,6 +280,66 @@ export class OcrService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit an async manual extraction job for PDF owner's manuals.
|
||||
*
|
||||
* @param userId - User ID for logging
|
||||
* @param request - Manual job submission request
|
||||
* @returns Manual job response with job ID
|
||||
*/
|
||||
async submitManualJob(userId: string, request: ManualJobSubmitRequest): Promise<ManualJobResponse> {
|
||||
// Validate file size for async processing (200MB max)
|
||||
if (request.fileBuffer.length > MAX_ASYNC_SIZE) {
|
||||
const err: any = new Error(
|
||||
`File too large. Max: ${MAX_ASYNC_SIZE / (1024 * 1024)}MB.`
|
||||
);
|
||||
err.statusCode = 413;
|
||||
throw err;
|
||||
}
|
||||
|
||||
// Manual extraction only supports PDF
|
||||
if (request.contentType !== 'application/pdf') {
|
||||
const err: any = new Error(
|
||||
`Unsupported file type: ${request.contentType}. Manual extraction requires PDF files.`
|
||||
);
|
||||
err.statusCode = 400;
|
||||
throw err;
|
||||
}
|
||||
|
||||
logger.info('Manual job submit requested', {
|
||||
operation: 'ocr.service.submitManualJob',
|
||||
userId,
|
||||
contentType: request.contentType,
|
||||
fileSize: request.fileBuffer.length,
|
||||
hasVehicleId: !!request.vehicleId,
|
||||
});
|
||||
|
||||
try {
|
||||
const result = await ocrClient.submitManualJob(
|
||||
request.fileBuffer,
|
||||
request.contentType,
|
||||
request.vehicleId
|
||||
);
|
||||
|
||||
logger.info('Manual job submitted', {
|
||||
operation: 'ocr.service.submitManualJob.success',
|
||||
userId,
|
||||
jobId: result.jobId,
|
||||
status: result.status,
|
||||
estimatedSeconds: result.estimatedSeconds,
|
||||
});
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
logger.error('Manual job submit failed', {
|
||||
operation: 'ocr.service.submitManualJob.error',
|
||||
userId,
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the status of an async OCR job.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user