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