The OCR service (backend/app/services/ocr_service.py) uses Tesseract 5.x with LSTM neural network mode.
OCR Engine Setup
# Automatic tesseract path resolutioncandidates = [ os.getenv("TESSERACT_CMD"), shutil.which("tesseract"), r"C:\Program Files\Tesseract-OCR\tesseract.exe",]pytesseract.pytesseract.tesseract_cmd = next(c for c in candidates if c and os.path.exists(c))# OCR configurationOCR_CONFIG = "--oem 3 --psm 6"# OEM 3: Default LSTM neural net mode# PSM 6: Assume uniform block of text
The system attempts Spanish (spa) first, then Spanish+English (spa+eng), and finally English (eng) for maximum accuracy.
# Pattern 1: Explicit label"Iniciales: J.D.""Iniciales del paciente: AB"# Pattern 2: From full name"Paciente: Juan David Pérez" → "J.D."# Regexre.search( r"\biniciales(?:\s+del\s+paciente)?\s*[:\-]?\s*([A-ZÁÉÍÓÚÑ]{1,2})", text)
def _norm_peso(v: Any) -> Optional[float]: """Normalize weight, handle g→kg conversion""" if isinstance(v, (int, float)): f = float(v) if f > 300: # Likely grams return round(f / 1000.0, 2) return round(f, 2) # Parse from string s = str(v).lower() num = _to_number(s) # Extract first number if "g" in s or "gramo" in s: return round(num / 1000.0, 2) return round(num, 2)def _norm_edad(v: Any) -> int: """Validate age, use 30 as default""" n = _to_number(v) if n is None or n < 0 or n > 120: return 30 return int(round(n))
def _norm_fecha(fecha_str, email_text): """Parse dates with 'este año' context awareness""" force_current_year = ("este año" in email_text.lower()) # Try ISO format first try: d = datetime.fromisoformat(fecha_str).date() if force_current_year and d.year != datetime.now().year: d = d.replace(year=datetime.now().year) return d.strftime("%Y-%m-%d") except: pass # Try DMY format m = re.match(r"(\d{1,2})[/-](\d{1,2})[/-](\d{2,4})$", fecha_str) if m: day, month, year = map(int, m.groups()) if force_current_year: year = datetime.now().year elif year < 100: year += 2000 return datetime(year, month, day).strftime("%Y-%m-%d")def _norm_gravedad(g, source_text): """Classify severity with text analysis""" val = str(g or "").strip().lower() # Explicit values if val in {"leve", "ligera", "mild"}: return "Leve" if val.startswith("modera"): return "Moderada" if val in {"grave", "severa", "severe"}: return "Grave" # Context clues txt = source_text.lower() if re.search(r"\bhospital|uci|urgencia|shock|muerte\b", txt): return "Grave" if re.search(r"\bmoderad", txt): return "Moderada" return "Leve" # Conservative default
The translator (backend/app/services/translator.py) provides multi-provider translation with fallbacks.
Configuration
Usage
Translation Setup
VIGIA_TRANSLATE="1" # Enable translation# Provider 1: Google Translate (via deep-translator)pip install deep-translator# Provider 2: Gemini AI (fallback)GEMINI_API_KEY="AIzaSy..."GEMINI_MODEL="gemini-1.5-pro"
Translation API
from app.services.translator import translate_en_to_es, translate_es_to_en# English → Spanishtext_es = translate_en_to_es("Urticaria and pruritus after drug intake")# → "Urticaria y prurito tras la ingesta del medicamento"# Spanish → English (with context)text_en = translate_es_to_en("presentó tos")# → "presented cough" (NOT "Terms of Service")
Translation Strategy:
1
Google Translate (Primary)
Fast, free, forced source language to avoid ambiguity
2
Google Auto-Detect (Secondary)
Fallback with automatic language detection
3
Gemini AI (Tertiary)
High-quality translation with medical terminology awareness
4
Original Text (Final)
Return untranslated if all providers fail
Force Spanish source language (source='es') when translating ES→EN to prevent false positives like “tos” → “TOS” (Terms of Service).