forked from StellarSplit/StellarSplit
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathocr.processor.ts
More file actions
153 lines (132 loc) · 4.34 KB
/
Copy pathocr.processor.ts
File metadata and controls
153 lines (132 loc) · 4.34 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
import { Process, Processor } from "@nestjs/bull";
import { Job } from "bull";
import { Injectable, Logger } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { Repository } from "typeorm";
import { OcrJob, OcrJobStatus } from "./entities/ocr-job.entity";
import { OcrService } from "./ocr.service";
/**
* OCR Queue Job Data Interface
*/
export interface OcrJobData {
jobId: string;
imageBuffer?: string; // Base64 encoded image (for smaller images)
imageUrl?: string;
itemId?: string;
splitId?: string;
uploadedBy?: string;
originalFilename?: string;
}
/**
* OCR Queue Processor using Bull
* Handles async OCR job processing with retries
*/
@Processor("ocr")
@Injectable()
export class OcrProcessor {
private readonly logger = new Logger(OcrProcessor.name);
constructor(
@InjectRepository(OcrJob)
private readonly ocrJobRepository: Repository<OcrJob>,
private readonly ocrService: OcrService,
) {}
/**
* Process an OCR job from the queue
*/
@Process({
concurrency: 3, // Process up to 3 jobs concurrently
})
async processOcrJob(job: Job<OcrJobData>): Promise<void> {
const {
jobId,
imageUrl,
imageBuffer,
itemId,
splitId,
uploadedBy,
originalFilename,
} = job.data;
this.logger.log(`Processing OCR job ${jobId}`);
try {
// Update job status to processing
const ocrJob = await this.ocrJobRepository.findOne({
where: { id: jobId },
});
if (!ocrJob) {
this.logger.error(`OCR Job ${jobId} not found in database`);
return;
}
ocrJob.status = OcrJobStatus.PROCESSING;
ocrJob.progress = 10;
ocrJob.startedAt = new Date();
ocrJob.queueJobId = String(job.id);
await this.ocrJobRepository.save(ocrJob);
// Get the image buffer
let imageBufferData: Buffer;
if (imageBuffer) {
imageBufferData = Buffer.from(imageBuffer, "base64");
} else if (imageUrl) {
// Fetch image from URL
const response = await fetch(imageUrl);
if (!response.ok) {
throw new Error(
`Failed to fetch image from URL: ${response.statusText}`,
);
}
const arrayBuffer = await response.arrayBuffer();
imageBufferData = Buffer.from(arrayBuffer);
} else {
throw new Error("No image data provided");
}
// Update progress
ocrJob.progress = 30;
await this.ocrJobRepository.save(ocrJob);
// Ensure OCR service is initialized
await this.ocrService.initialize();
ocrJob.progress = 50;
await this.ocrJobRepository.save(ocrJob);
// Perform OCR
const parsedReceipt = await this.ocrService.scanReceipt(imageBufferData);
ocrJob.progress = 90;
// Store results
ocrJob.status = OcrJobStatus.COMPLETED;
ocrJob.progress = 100;
ocrJob.confidence = parsedReceipt.confidence;
ocrJob.totalAmount = parsedReceipt.total;
ocrJob.subtotal = parsedReceipt.subtotal;
ocrJob.taxAmount = parsedReceipt.tax;
ocrJob.tipAmount = parsedReceipt.tip;
ocrJob.extractedItems = parsedReceipt.items;
ocrJob.completedAt = new Date();
// Determine if manual review is needed based on confidence
if (parsedReceipt.confidence < 0.5) {
ocrJob.needsManualReview = true;
ocrJob.status = OcrJobStatus.NEEDS_REVIEW;
}
await this.ocrJobRepository.save(ocrJob);
this.logger.log(
`OCR job ${jobId} completed with confidence ${parsedReceipt.confidence.toFixed(2)}`,
);
} catch (error) {
this.logger.error(`OCR job ${jobId} failed:`, error);
// Update job with error
const ocrJob = await this.ocrJobRepository.findOne({
where: { id: jobId },
});
if (ocrJob) {
ocrJob.status = OcrJobStatus.FAILED;
ocrJob.errorMessage =
error instanceof Error ? error.message : "Unknown error";
ocrJob.retryCount += 1;
// If retries are still available, the job will be retried by Bull
// If max retries reached, mark as failed permanently
if (ocrJob.retryCount >= ocrJob.maxRetries) {
this.logger.error(`OCR job ${jobId} exceeded max retries`);
}
await this.ocrJobRepository.save(ocrJob);
}
// Re-throw to let Bull handle retry
throw error;
}
}
}