Saltar a contenido

Diagrama del Pipeline NER - Entity Extraction

Descripción

Diagrama detallado del pipeline de extracción de entidades (NER) que muestra el flujo desde el texto OCR hasta las entidades validadas, incluyendo el EntityExtractorEnsemble y la cadena de validadores.

Diagrama Principal

flowchart TB
    subgraph Input["📥 Entrada"]
        Text[Texto OCR<br/>contenido del documento]
    end

    subgraph SpaCyAdapter["🏷️ SpaCyAdapter (Orquestador)"]
        Load[Cargar Modelo<br/>es_core_news_lg]
        Doc[Crear spacy.Doc]
    end

    subgraph Extractors["🔍 Extractors (Strategy Pattern)"]
        Marker[MarkerExtractor<br/>priority=1]
        SpaCyNER[SpaCyNERExtractor<br/>priority=2]
        Regex[RegexExtractor<br/>priority=3]
        Address[AddressExtractor<br/>priority=4<br/>no ensemble vote]
        Contact[ContactExtractor<br/>priority=5<br/>no ensemble vote]
    end

    subgraph Ensemble["⚖️ EntityExtractorEnsemble"]
        Collect[Recopilar Candidatos<br/>por tipo de entidad]
        Voting[Votación Ponderada<br/>weighted_vote]
        Resolve[Resolver Conflictos<br/>highest_confidence]
    end

    subgraph Validators["✅ Validators (Chain of Responsibility)"]
        ValChain[ValidatorChain]
        Structural[StructuralValidator<br/>boost por sección]
        Temporal[TemporalValidator<br/>consistencia fechas]
        RadFormat[RadicadoFormatValidator<br/>formato 23 dígitos]
        Dedup[DeduplicationValidator<br/>fuzzy matching]
    end

    subgraph Utilities["🛠️ Utilities"]
        Truncator[EntityTruncator<br/>límite 100 chars]
        CtxScorer[ContextScorer<br/>clasificación contextual]
        EntLinker[EntityLinker<br/>agrupación fuzzy]
        RadVal[RadicadoValidator<br/>códigos DANE]
        DocStruct[DocumentStructureAnalyzer<br/>detección secciones]
    end

    subgraph Output["📤 Salida"]
        EntityResult[EntityResult<br/>demandante, demandado<br/>radicado, juzgado, fecha]
    end

    Text --> Load --> Doc
    Doc --> Marker & SpaCyNER & Regex & Address & Contact

    Marker --> Collect
    SpaCyNER --> Collect
    Regex --> Collect
    Address --> Collect
    Contact --> Collect

    Collect --> Voting --> Resolve

    Resolve --> ValChain
    ValChain --> Structural --> Temporal --> RadFormat --> Dedup

    Marker -.uses.-> Truncator
    SpaCyNER -.uses.-> CtxScorer
    Dedup -.uses.-> EntLinker
    RadFormat -.uses.-> RadVal
    Structural -.uses.-> DocStruct

    Dedup --> EntityResult

    style Input fill:#e3f2fd
    style Ensemble fill:#fff3e0
    style Validators fill:#e8f5e9
    style Output fill:#f3e5f5

Extractores (Strategy Pattern)

MarkerExtractor (priority=1, weight=0.70)

Responsabilidad: Extraer entidades de marcadores explícitos en el documento.

Marcadores soportados:

ACCIONANTE:, DEMANDANTE:, ACTOR:
ACCIONADO:, DEMANDADO:, ENTIDAD DEMANDADA:
RADICACIÓN:, RADICADO No.:, No. RADICADO:
JUZGADO:, CORPORACIÓN:, DESPACHO:

Proceso: 1. Buscar patrón MARCADOR: valor 2. Extraer texto después del marcador 3. Truncar con EntityTruncator 4. Asignar confidence=0.95 (alta confianza)

SpaCyNERExtractor (priority=2)

Responsabilidad: Extracción neuronal usando modelo SpaCy.

Labels utilizados: - PER → demandante/demandado (requiere clasificación) - ORG → demandado (entidades corporativas) - LOC → direcciones

Proceso: 1. Ejecutar NER sobre spacy.Doc 2. Clasificar entidades PER como demandante/demandado 3. Aplicar ContextScorer para validar 4. Filtrar por Part-of-Speech (POS)

Clasificación contextual:

# Palabras clave positivas para demandante
PLAINTIFF_KEYWORDS = ["accionante", "demandante", "actor", "solicita"]

# Palabras clave positivas para demandado
DEFENDANT_KEYWORDS = ["accionado", "demandado", "entidad", "contra"]

RegexExtractor (priority=3, weight=0.70)

Responsabilidad: Extracción de entidades estructuradas mediante patrones regex.

Patrones:

Entidad Patrón Ejemplo
Radicado \d{23} 11001020300020260001200
Fecha \d{1,2}/\d{1,2}/\d{4} 15/02/2026
Juzgado Juzgado\s+\d+\s+\w+ Juzgado 12 Civil

EntityExtractorEnsemble

Recopilación de Candidatos

def _collect_candidates(self, text: str) -> Dict[str, List[Entity]]:
    """Recopila candidatos de todos los extractores."""
    candidates = defaultdict(list)

    for extractor in self._extractors:
        entities = extractor.extract(text)
        for entity in entities:
            candidates[entity.label].append(entity)

    return candidates

Votación Ponderada

def _weighted_vote(self, candidates: List[Entity]) -> Entity:
    """Selecciona el mejor candidato por votación ponderada."""
    scores = {}

    for candidate in candidates:
        weight = self._weights.get(candidate.source, 0.5)
        score = candidate.confidence * weight
        scores[candidate.text] = scores.get(candidate.text, 0) + score

    best_text = max(scores, key=scores.get)
    return next(c for c in candidates if c.text == best_text)

AddressExtractor (priority=4, weight=0.65)

Responsabilidad: Extraer direcciones de demandante y demandado.

Proceso: 1. Detectar patrones de dirección colombiana (Calle, Carrera, Avenida, etc.) 2. Clasificar como dirección de demandante o demandado por contexto 3. Asignar confidence según proximidad a marcadores

ContactExtractor (priority=5, weight=0.80)

Responsabilidad: Extraer correo electrónico y cédula (CC/CE/NIT).

Patrones: - Email: regex estándar de correo electrónico - Cédula: patrones C.C., CC, CE, NIT seguidos de número

Pesos por Extractor (Ensemble Voting)

Los pesos varían según el tipo de entidad. Solo 3 extractores participan en el ensemble voting:

Extractor Peso (persona) Peso (estructurado) Nota
MarkerExtractor 0.70 0.20 Alta precisión con marcadores explícitos
SpaCyNERExtractor 0.20 0.10 Menor peso por ruido en documentos legales
RegexExtractor 0.10 0.70 Alta precisión para radicados/fechas
AddressExtractor No participa en voting (extrae direcciones directamente)
ContactExtractor No participa en voting (extrae cédula/correo directamente)
  • Persona: demandante, demandado (prioriza MarkerExtractor)
  • Estructurado: radicado, fecha, juzgado (prioriza RegexExtractor)

Validadores (Chain of Responsibility)

StructuralValidator

Responsabilidad: Ajustar confianza según la sección del documento.

Boost por sección:

SECTION_BOOSTS = {
    "encabezado": {"demandante": 0.15, "demandado": 0.15, "radicado": 0.20},
    "partes": {"demandante": 0.20, "demandado": 0.20},
    "hechos": {"demandante": 0.05, "demandado": 0.05},
    "pretensiones": {"demandante": 0.10},
}

TemporalValidator

Responsabilidad: Validar consistencia de fechas.

Validaciones: - Año entre 2000-2030 - Fecha no futura - Consistencia con fecha de radicación

RadicadoFormatValidator

Responsabilidad: Validar formato de radicados colombianos.

Formato (23 dígitos):

[DANE 5][AÑO 4][CORP 5][TIPO 2][CONSEC 7]

Ejemplo: 11001020300020260001200
         ├─────┤├────┤├─────┤├──┤├───────┤
         DANE   AÑO   CORP   TIPO CONSEC

Filtrado: - Elimina radicados con prefijo T- (sentencias, no radicados) - Elimina radicados con prefijo SU-

DeduplicationValidator

Responsabilidad: Eliminar entidades duplicadas o muy similares.

Proceso: 1. Agrupar entidades por tipo 2. Calcular similitud Jaccard 3. Fusionar si similitud > 0.85 4. Mantener la de mayor confianza

Flujo de Datos Detallado

sequenceDiagram
    participant T as Texto OCR
    participant SA as SpaCyAdapter
    participant ME as MarkerExtractor
    participant SE as SpaCyNERExtractor
    participant RE as RegexExtractor
    participant AE as AddressExtractor
    participant CE as ContactExtractor
    participant EN as Ensemble
    participant VC as ValidatorChain
    participant ER as EntityResult

    T->>SA: extract_entities(text)
    SA->>SA: _get_nlp() lazy load
    SA->>ME: extract(text)
    ME-->>EN: [Entity(demandante, 0.95), ...]
    SA->>SE: extract(text, doc)
    SE-->>EN: [Entity(demandante, 0.70), ...]
    SA->>RE: extract(text)
    RE-->>EN: [Entity(radicado, 0.90), ...]
    SA->>AE: extract(text)
    AE-->>EN: [Entity(direccion, 0.80), ...]
    SA->>CE: extract(text)
    CE-->>EN: [Entity(cedula, 0.95), Entity(correo, 0.95)]

    EN->>EN: _collect_candidates()
    EN->>EN: _weighted_vote() por tipo
    EN->>EN: _resolve_conflicts()

    EN->>VC: validate(entities, context)
    VC->>VC: StructuralValidator
    VC->>VC: TemporalValidator
    VC->>VC: RadicadoFormatValidator
    VC->>VC: DeduplicationValidator
    VC-->>ER: EntityResult validado

Métricas de Precisión

Entidad Precision Recall F1 Score
Demandante 0.82 0.78 0.74
Demandado 0.75 0.72 0.73
Radicado 0.95 0.90 0.92
Juzgado 0.88 0.85 0.86
Fecha 0.85 0.78 0.81
Correo 1.00 1.00 1.00
Cédula 1.00 1.00 1.00

F1 Global: 85.3%

Configuración

@dataclass
class NERConfig:
    """Configuración del pipeline NER."""

    model_name: str = "es_core_news_lg"
    # Pesos reales del ensemble (solo 3 extractores votan)
    person_weights: Dict[str, float] = field(default_factory=lambda: {
        "MarkerExtractor": 0.70,
        "SpaCyNERExtractor": 0.20,
        "RegexExtractor": 0.10,
    })
    structured_weights: Dict[str, float] = field(default_factory=lambda: {
        "MarkerExtractor": 0.20,
        "SpaCyNERExtractor": 0.10,
        "RegexExtractor": 0.70,
    })
    validation_enabled: bool = True
    dedup_threshold: float = 0.85
    max_entity_length: int = 100

Extensibilidad

Agregar Nuevo Extractor

  1. Implementar EntityExtractor Protocol
  2. Definir name, priority, extract()
  3. Registrar en SpaCyAdapter._create_default_extractors()
  4. Agregar peso en NERConfig.ensemble_weights

Agregar Nuevo Validador

  1. Implementar EntityValidator Protocol
  2. Definir name, validate()
  3. Agregar a ValidatorChain en orden deseado

Leyenda de Estados

Símbolo Significado
Implementado y funcional
📅 Planificado para fase futura

Última actualización: 2026-03-05 (Fix pesos ensemble: solo 3 extractores votan, pesos diferenciados persona/estructurado)