Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM, PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source files covering clients, berths, interests/pipeline, documents/EOI, expenses/invoices, email, notifications, dashboard, admin, and client portal. CI/CD via Gitea Actions with Docker builds. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
56 lines
1.5 KiB
TypeScript
56 lines
1.5 KiB
TypeScript
import OpenAI from 'openai';
|
|
import { logger } from '@/lib/logger';
|
|
|
|
const openai = new OpenAI(); // uses OPENAI_API_KEY from env
|
|
|
|
interface ScanResult {
|
|
establishment: string | null;
|
|
date: string | null;
|
|
amount: number | null;
|
|
currency: string | null;
|
|
lineItems: Array<{ description: string; amount: number }>;
|
|
confidence: number;
|
|
}
|
|
|
|
export async function scanReceipt(
|
|
imageBuffer: Buffer,
|
|
mimeType: string,
|
|
): Promise<ScanResult> {
|
|
try {
|
|
const base64 = imageBuffer.toString('base64');
|
|
const response = await openai.chat.completions.create({
|
|
model: 'gpt-4o',
|
|
messages: [
|
|
{
|
|
role: 'user',
|
|
content: [
|
|
{
|
|
type: 'text',
|
|
text: 'Extract receipt data as JSON: { establishment, date (ISO), amount (number), currency (3-letter code), lineItems: [{ description, amount }], confidence (0-1) }. Return ONLY valid JSON.',
|
|
},
|
|
{
|
|
type: 'image_url',
|
|
image_url: { url: `data:${mimeType};base64,${base64}` },
|
|
},
|
|
],
|
|
},
|
|
],
|
|
max_tokens: 1000,
|
|
});
|
|
|
|
const content = response.choices[0]?.message?.content ?? '{}';
|
|
const cleaned = content.replace(/```json\n?|\n?```/g, '').trim();
|
|
return JSON.parse(cleaned) as ScanResult;
|
|
} catch (err) {
|
|
logger.error({ err }, 'Receipt scan failed');
|
|
return {
|
|
establishment: null,
|
|
date: null,
|
|
amount: null,
|
|
currency: null,
|
|
lineItems: [],
|
|
confidence: 0,
|
|
};
|
|
}
|
|
}
|