fix: improve OCR CIP extraction to filter common TSI card text
Run Tests on Branches / Backend Tests (push) Successful in 3m32s
Run Tests on Branches / Frontend Tests (push) Successful in 3m27s

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.
This commit is contained in:
Antoni Nuñez Romeu
2026-07-06 15:10:58 +02:00
parent 792e6dd5b1
commit c91bffe5ea
+62 -5
View File
@@ -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: 830 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: 830 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);