# Deduplicate Entities (fuzzy matching)

Diseño original para fuzzygraph.

## Funcion a implementar

### deduplicate_entities

- **Dominio:** datascience
- **Lang:** Python
- **Purity:** pure
- **Signature:**
  ```python
  def deduplicate_entities(
      candidates: list[EntityCandidate],
      name_threshold: float = 0.85,
      same_type_only: bool = True,
  ) -> DeduplicationResult:
  ```
- **Retorno:** `DeduplicationResult` con entities deduplicadas y merge log
- **Descripcion:** Agrupa entidades candidatas que refieren a la misma entidad real usando fuzzy matching de nombres. Cuando dos entidades son suficientemente similares (y del mismo tipo si `same_type_only=True`), se mergean en una sola combinando atributos.
- **Algoritmo:**
  1. **Normalizar nombres:** `normalize_entity_name()` sobre cada candidato
  2. **Agrupar por tipo** (si same_type_only): reducir comparaciones
  3. **Comparacion pairwise dentro de cada grupo:**
     - Calcular similitud: `1.0 - (levenshtein_distance(a, b) / max(len(a), len(b)))`
     - Tambien comparar por tokens: `jaccard_similarity(tokens_a, tokens_b)`
     - Score final: `max(levenshtein_sim, jaccard_sim)`
     - Si score >= `name_threshold`: agrupar como misma entidad
  4. **Union-Find** para clusters transitivos: si A~B y B~C, entonces {A,B,C} es un cluster
  5. **Merge por cluster:**
     - Nombre: el del candidato con mayor confidence
     - Atributos: `merge_entity_attributes()` — combinar non-null de todos los candidatos
     - Confidence: max del cluster
     - Source chunks: union de todos
  6. Retornar DeduplicationResult con entidades merged y log de merges
- **Optimizacion:** Para N candidatos, pairwise es O(N^2). Aceptable para <1000 entidades (caso tipico por documento). Si necesita escalar: pre-filtrar por primera letra o n-gram index.
- **Heuristicas adicionales de match:**
  - Nombre contenido: "John" matchea "John Smith" si son del mismo tipo (score bonus +0.3)
  - Acronimos: "FBI" matchea "Federal Bureau of Investigation" — detectar si un nombre es acronimo de otro
  - IPs/emails/domains: matching exacto normalizado (no fuzzy)
- **Deps:** `levenshtein_distance`, `jaccard_similarity` (ya en registry)
- **Tests:**
  - "John Smith" y "Smith, John" → merge
  - "Google" y "Google LLC" → merge
  - "192.168.1.1" y "192.168.1.1" → merge (exacto)
  - "John Smith" (Person) y "John Smith" (Organization) → NO merge (distinto tipo)
  - Clusters transitivos: A~B, B~C → {A,B,C}
  - Entidades sin duplicados → sin cambios
  - Confidence y atributos se mergean correctamente
