talently-api / services /ocr_normalizer.py
SantiCan's picture
feat: date parser, seniority por reglas y extracción de skills mejorada
b8dfcb6
"""
Normaliza errores típicos del OCR sobre el texto extraído de CVs.
Tesseract comete errores predecibles cuando el PDF es rasterizado:
caracteres similares (l/I, 0/O), separadores que se pierden ('React.js'
sale como 'Reactjs'), letras que se confunden con dígitos. En vez de
inflar la lista de skills con cada variante rota, normalizamos el texto
ANTES de buscar entidades.
Es una utilidad pura: recibe un string y devuelve un string. No conoce
CVs ni NER. La tabla de equivalencias se mantiene chica a propósito —
solo se agregan errores OBSERVADOS en CVs reales, no especulativos.
"""
import re
# Mapping de "patrón roto del OCR" → "forma canónica".
# La key es un regex con \b en los bordes (word boundary) para no romper
# palabras que contengan el patrón como substring (ej. "Reactjs" no debe
# matchear dentro de "Reactjsx"). Las keys se aplican en orden, pero como
# son palabras independientes el orden no importa hoy.
OCR_NORMALIZATIONS: dict[str, str] = {
# React: el OCR pega "React" + "js" sin punto.
r"\bReactjs\b": "React",
r"\bReact\.js\b": "React", # forma canónica que SÍ vamos a tener en KNOWN_SKILLS
# MaterialUI: la I mayúscula muchas veces se lee como L minúscula.
r"\bMaterialUl\b": "MaterialUI",
r"\bMaterial Ul\b": "MaterialUI",
r"\bMaterial UI\b": "MaterialUI",
# Node.js: mismo patrón que React.
r"\bNodejs\b": "Node.js",
# Next.js
r"\bNextjs\b": "Next.js",
# Vue.js
r"\bVuejs\b": "Vue",
}
def normalize(text: str) -> str:
"""Aplica todas las normalizaciones OCR conocidas sobre el texto."""
for pattern, replacement in OCR_NORMALIZATIONS.items():
text = re.sub(pattern, replacement, text, flags=re.IGNORECASE)
return text