From c91bffe5eab23f88b24179b5d8973b97835a5b4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoni=20Nu=C3=B1ez=20Romeu?= Date: Mon, 6 Jul 2026 15:10:58 +0200 Subject: [PATCH] fix: improve OCR CIP extraction to filter common TSI card text The extractCip function was incorrectly matching 'TARJETASANITARIA' instead of the actual barcode/CIP number because it's 16 alphanumeric characters that matched the regex. Added filtering for common Spanish text on TSI cards and prioritized numeric-heavy patterns typical of barcode codes. --- backend/server.js | 67 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 62 insertions(+), 5 deletions(-) diff --git a/backend/server.js b/backend/server.js index 1a45bcc..8b9846b 100644 --- a/backend/server.js +++ b/backend/server.js @@ -531,12 +531,63 @@ const upload = multer({ // Extract CIP from OCR text — looks for a long alphanumeric token (16 chars typical for CIP) function extractCip(text) { const cleaned = text.replace(/\s+/g, ''); - // Try exact 16-char alphanumeric match first + + // Common Spanish text on TSI cards that should NOT be treated as CIP + const CARD_TEXT_PATTERNS = [ + /TARJETASANITARIA/i, + /TARJETASANIT/i, + /TARJETASEGURIDAD/i, + /SISTEMASALUD/i, + /SERVICIOANDALUZ/i, + /SALUD/i, + /NUMERO/i, + /NUM/i, + /CIP/i, + /CODIGO/i, + /IDENTIFICACION/i, + /PERSONAL/i, + /TITULAR/i, + /FECHA/i, + /NACIMIENTO/i, + /CADUCIDAD/i, + /VALIDA/i, + /ESPANA/i, + /ESPAÑA/i, + ]; + + // Check if OCR result is just common card text + const isCardText = (candidate) => { + const upper = candidate.toUpperCase(); + return CARD_TEXT_PATTERNS.some(pattern => pattern.test(upper)); + }; + + // Try to find barcodes first (typically numeric-heavy) + // TSI barcodes are usually numeric or alphanumeric with mostly numbers + const numericMatches = cleaned.match(/\d{10,30}/g) || []; + for (const match of numericMatches) { + if (match.length >= 10 && match.length <= 30) { + return match.toUpperCase(); + } + } + + // Try exact 16-char alphanumeric match, but filter card text const exact = cleaned.match(/[A-Z0-9]{16}/i); - if (exact) return exact[0].toUpperCase(); - // Fallback: 8–30 alphanumeric chars - const loose = cleaned.match(/[A-Z0-9]{8,30}/i); - if (loose) return loose[0].toUpperCase(); + if (exact) { + const candidate = exact[0].toUpperCase(); + if (!isCardText(candidate)) { + return candidate; + } + } + + // Fallback: 8–30 alphanumeric chars, filter card text + const looseMatches = cleaned.match(/[A-Z0-9]{8,30}/gi) || []; + for (const match of looseMatches) { + const candidate = match.toUpperCase(); + if (!isCardText(candidate)) { + return candidate; + } + } + return null; } @@ -546,11 +597,17 @@ app.post('/api/tsi/ocr', upload.single('photo'), async (req, res) => { return res.status(400).json({ error: 'No image file provided' }); } + console.log('[OCR] Processing image, size:', req.file.size, 'bytes'); + const worker = await createWorker('spa+eng'); const { data } = await worker.recognize(req.file.buffer); await worker.terminate(); console.log('[OCR] Raw text:', data.text); + console.log('[OCR] Words:', data.words?.length || 0, 'words detected'); + if (data.words && data.words.length > 0) { + console.log('[OCR] First 10 words:', data.words.slice(0, 10).map(w => `${w.text}(${w.confidence?.toFixed(2)})`).join(', ')); + } const cip = extractCip(data.text); console.log('[OCR] Extracted CIP:', cip); -- 2.52.0