import sharp from 'sharp' const MAX_DIMENSION = 1800 const JPEG_QUALITY = 80 // Shared by every image-ingestion entry point (slip queue, payment-slip // reconciliation, ad-hoc expense parsing) — runs before any LLM call and // before any persistence. sharp strips EXIF on re-encode by default, which // also drops embedded GPS data as a privacy side-benefit. export async function compressImage(base64: string): Promise { const inputBuffer = Buffer.from(base64, 'base64') const outputBuffer = await sharp(inputBuffer) .resize(MAX_DIMENSION, MAX_DIMENSION, { fit: 'inside', withoutEnlargement: true }) .jpeg({ quality: JPEG_QUALITY }) .toBuffer() return outputBuffer.toString('base64') } // Cheap real-image check — reads only the header (via sharp's metadata // probe), not the full pixel data, so it's far cheaper than compressImage. // Used at enqueue time now that compression itself has moved to after OCR // succeeds (see slip-queue.service.ts), so something still rejects a // non-image upload immediately instead of silently queuing it. export async function validateImage(base64: string): Promise { await sharp(Buffer.from(base64, 'base64')).metadata() }