Saltar a contenido

Arquitectura y Componentes Técnicos

Capas (Clean Architecture)

Interfaces (GUI 9 páginas + CLI 20 comandos + API REST 22 endpoints)
Application (3 UseCases + 5 Services + 24 DTOs frozen + ServiceContainer DI)
Domain (Document, DocumentType, DocumentStatus, RecommendedAction, ImportStatus, ValueObjects)
Infrastructure (OCR Router, SpaCy NER, Hybrid Dedup, SQLite+FTS5)

Application Layer detalle: - Use Cases: ProcessDocument, ImportExcel, ExportExcel (pipeline completo) - Services: StatisticsService, SearchService, ValidationService, ManualEntryService, DedupConfigService — todos con Result[ResponseDTO, AppError] (migrados desde Result[T, str] en Sprint 27.2) - DTOs: 24 frozen (@dataclass(frozen=True)) — Request + Response + ProcessingMetrics, JSON-serializables via dataclasses.asdict() - ServiceContainer: 14 properties lazy-loaded (repository, ocr_router, ner_adapter, duplicate_detector, audit_service + 5 services + 3 use cases), thread-safe con RLock

API Layer (FastAPI — Sprint 24+25+25.1):

HTTP Request → FastAPI Router → Depends(get_container, get_current_user)
Route function → container.{service}.*_result(RequestDTO)
result_mapper.result_to_response(Result) → JSONResponse
    ├── Success → {data: ...} (200/201/202)
    └── Failure → {error: {code, message}} (400/404/422/500)

Componentes API: - main.py: App factory + lifespan (preload models) + exception handler global + router registration - config.py: ApiSettings (Pydantic BaseSettings, env prefix SHERLOCK_) — JWT, CORS, upload limits - auth.py: JWT encode/decode, credential loading desde YAML, refresh con limite - dependencies.py: get_container, get_current_user, get_task_manager (FastAPI Depends) - result_mapper.py: Result[T,E]JSONResponse con clasificacion automatica de error → HTTP status - tasks.py: TaskManager (ThreadPoolExecutor + dict de futures + polling) - routes/: 11 routers (health, auth, documents, search, process, validate, manual, duplicates, import_export, config_routes, tasks) - schemas/: 8 modulos Pydantic (common, auth, documents, health, tasks, validation, duplicates, config)

Flujo GUI (original):

GUI → container.{service} → service.*_result(RequestDTO) → Result[ResponseDTO, str]
                                   ↓                              ↓
                           repository (via DI)           Success(frozen DTO) / Failure(str)

Errores de Dominio Tipados (core/errors.py)

AppError es el error de dominio tipado que reemplaza los strings planos en Failure(str):

@dataclass(frozen=True)
class AppError:
    code: ErrorCode          # Enum: NOT_FOUND, VALIDATION, CONFLICT, INTERNAL, ...
    message: str             # Mensaje legible para logs/usuario
    details: dict | None     # Contexto adicional opcional

    @property
    def http_status(self) -> int:  # Mapeo directo a HTTP status code
        ...

    def __str__(self) -> str:
        return self.message   # Backward compat: str(AppError) == str antiguo

ErrorCode (enum): NOT_FOUND (404), VALIDATION (422), CONFLICT (409), INTERNAL (500), UNAUTHORIZED (401), FORBIDDEN (403).

Migración completada: Todos los servicios de application/ y persistence/ migraron de Result[T, str] a Result[T, AppError]. El __str__ de AppError retorna message para backward compatibility con código que hacía str(failure).

Persistence layer — operaciones que ahora retornan Result[T, AppError]: - delete(doc_id)Result[bool, AppError] - update_status(doc_id, status)Result[bool, AppError] - update_document_field(doc_id, field, value)Result[bool, AppError] - save_processing_metrics(doc_id, metrics)Result[bool, AppError]

Esto cierra la brecha donde persistence lanzaba excepciones o retornaba booleanos sin contexto de error tipado.

Patrones implementados

Patrón Implementación
Ports & Adapters OCRPort, NERPort, DuplicateDetectorPort, DocumentRepository
ISP 5 protocols: Reader, Writer, Searcher, Correction, Metrics
ROP returns.Result en use cases + 5 application services + persistence (Result[T, AppError])
Strategy OCRRouter, NER Extractors (5)
Template Method PageBase (GUI), BaseOCRAdapter
Factory PageFactory (singleton)
DI ServiceContainer con lazy-loading
Adapter (API) result_mapper — Result[T,E] → JSONResponse
Middleware JWT auth, CORS, global exception handler

Pipeline de Procesamiento

PDF → Gate Pre-OCR (SHA-256+size) → OCR Híbrido → NER Ensemble
    → Secciones/Medida Provisional → Dedup Ensemble → BD + FTS5

Detección de Duplicados

Gate pre-OCR (ProcessDocumentUseCase): - SHA-256 binario + file_size → short-circuit sin OCR/NER

Ensemble post-OCR (HybridEnsembleDetector, 3 niveles): 1. MinHash LSH (peso 0.25): Jaccard por shingles 2. TF-IDF Coseno (peso 0.40): Similitud léxica 3. Entity Matching (peso 0.35): Demandante + Demandado + Cédula + Correo

Boosts: +0.20 ambas partes, +0.10 cédula, +0.05 correo. Auto-exclusión: documento no aparece como su propio duplicado. Pesos configurables via JSON: data/config/dedup_config.json

Extracción Híbrida PDF

# Paso 1: Texto nativo por página (PyMuPDF)
# Página con ≥50 chars → usar directo (confidence=0.99, engine=NATIVE)
# Página con <50 chars → marcar para OCR

# Paso 2: OCR solo para páginas sin texto nativo
# Alta calidad → Tesseract, fallback PaddleOCR si confidence <70%
# Baja calidad → PaddleOCR directo

Caché TF-IDF (CRÍTICO)

Sin caché: 2-5 min con 10,000 docs. Con caché: <2 seg.

# ✅ Caché incremental
cached_matrix = load_from_cache("data/cache/tfidf.pkl")
new_vector = vectorizer.transform([new_doc])
updated = vstack([cached_matrix, new_vector])
save_to_cache(updated)

Rebuild si >10% documentos nuevos. Test: test_similarity_search_under_2_seconds_with_1000_docs()

NER Ensemble

5 extractores (Strategy): Marker(p=1), SpaCy(p=2), Regex(p=3), Address(p=4), Contact(p=5) 4 validadores (Pipeline, no CoR — todos siempre ejecutan): Structural, Temporal, RadicadoFormat, Dedup F1 Global: 85.3% | Correo/Cédula: 100% | Demandante: 74%

Base de Datos

SQLite + FTS5 + WAL mode. 5 tablas, 33 columnas documents, 11 índices. FK ON DELETE CASCADE en corrections y processing_stats. Ver diagrama completo: docs/diagrams/07_database_schema.md

Diagramas

9 diagramas actualizados en docs/diagrams/: 01-System Context, 02-Architecture, 03-Processing Flow, 04-Dedup, 05-Classes, 06-Components, 07-DB Schema, 08-GUI Nav, 09-NER Pipeline