---
id: "0175"
title: "EDA relational: precisión de FK inference (falsos positivos) + filtrar VIEWs + test ATTACH"
status: completado
type: bugfix
domain:
  - registry-quality
scope: registry-only
priority: alta
depends: []
blocks: []
related: ["0173", "0174", "0176", "0177"]
created: 2026-06-29
updated: 2026-06-29
tags: [eda, datascience, infer_fk_containment_duckdb, build_join_graph, profile_database, duckdb, benchmark]
---
# 0175 — EDA relational: precisión de FK inference + filtrar VIEWs

## Contexto

El benchmark `/eda` (29/06/2026, `temp/eda_benchmark/EVALUATION.md`) confirmó que la inferencia de
claves foráneas a nivel de base es **inútil por falsos positivos masivos** y que las VISTAS se
perfilan como tablas base. El join graph resultante necesita filtrado manual para ser legible.

Hallazgos cubiertos:

| Hallazgo | Severidad | Evidencia del benchmark |
|---|---|---|
| H3 — FK inference por contención: 10-20× falsos positivos | crítico | chinook 111 candidatas vs ~11 reales; sakila 565 vs ~30. Casos absurdos: `InvoiceLine.Quantity→Album.AlbumId`, `Genre.GenreId→{Album,Artist,Customer,…}` |
| H5 — VIEWs perfiladas como tablas base | alto | sakila `n_tables=21` incluye 5 VISTAS (`customer_list`, `film_list` 5462 filas, `staff_list`, `sales_by_store`, `sales_by_film_category`) + `film_text` (FTS, 0 filas) |
| H10 — coste relacional gastado en computar FK falsas | medio | sakila 31.82s: la mayoría en INTERSECT de los 565 pares candidatos, casi todos falsos |
| H14 — bug `sqlite_master does not exist` tras ATTACH (ya parcheado, falta test) | bajo (resuelto) | `_run.log`: `profile_database` falló con `Catalog Error: src.sqlite_master`; re-run posterior `ok` |

### Causa raíz (verificada en código, READ-ONLY)

- `python/functions/datascience/infer_fk_containment_duckdb.py:217-285` emite una FK candidata si
  `inclusion(A⊆B) ≥ min_inclusion` **y** B "parece clave" (unicidad ≥0.95). **No usa el nombre de
  la columna**, que es la señal más fuerte de FK (`AlbumId→Album.AlbumId`), ni excluye columnas
  no-clave (cantidades, importes) como ORIGEN. Enteros pequeños (`GenreId` 1..25) están contenidos
  en casi todo → ruido.
- `python/functions/pipelines/profile_database.py:155-159` lista tablas con `duckdb_list_tables`
  sin filtrar `table_type` → perfila VIEWs y tablas FTS como base (H5), lo que infla el universo de
  pares y multiplica las FK falsas (relaciona H10).
- H10 es el **mismo cambio** que H3: filtrar candidatos por nombre **antes** del INTERSECT reduce
  pares (más rápido) y falsos positivos (más preciso) a la vez.

## Tareas

1. **H3+H10 — señal de nombre en `infer_fk_containment_duckdb.py:217-285`:** antes de lanzar el
   INTERSECT, exigir coincidencia/patrón de nombre entre origen y destino (`from_col` casa con
   `to_table`/`to_col`, patrón `<X>Id → <X>.<X>Id`; case-insensitive). Excluir como ORIGEN columnas
   claramente no-clave (cantidades, importes, flags) por heurística de nombre/tipo. Esto poda el
   O(tablas²×columnas²) y elimina la mayoría de los falsos positivos. Validar mejor la cardinalidad
   (los `1:1` imposibles del benchmark).
2. **H5 — filtrar VIEWs** antes de perfilar e inferir FK: filtrar `table_type='BASE TABLE'` vía
   `information_schema.tables` / `duckdb_tables()`. Decidir (a confirmar al implementar) si el filtro
   va como flag nuevo en `duckdb_list_tables` (infra, reutilizable) o en `profile_database.py` tras
   listar. Preferir el flag en `duckdb_list_tables` si no rompe consumidores.
3. **H3 — propagar al join graph:** verificar que `build_join_graph.py` recibe la lista ya filtrada
   y que el diagrama Mermaid resultante es legible (sin nodos VIEW ni aristas espurias).
4. **H14 — test de regresión:** añadir test (en `profile_database_test.py` o
   `infer_fk_containment_duckdb_test.py`) que haga `ATTACH` de una base SQLite pequeña en DuckDB y
   perfile, confirmando que se usa `information_schema`/`duckdb_tables()` y nunca `sqlite_master`.
   (A confirmar: localizar la función que hace el ATTACH —probablemente `summarize_table_duckdb.py`
   o una primitiva infra `duckdb_*`— para cubrirla.)
5. Tests: casos sintéticos con tablas que tengan columnas tipo `XId` (FK real) y columnas de
   cantidad contenidas en claves (falso positivo) → confirmar que solo emite las reales.

## Definition of Done

| Escenario | Tipo | Comando / evidencia | Resultado esperado |
|---|---|---|---|
| Golden: FK reales sin ruido | e2e | re-correr `profile_database` sobre chinook | ~11 FK candidatas (no 111); incluyen `Album.ArtistId→Artist.ArtistId`, `Invoice.CustomerId→Customer.CustomerId`; NO incluyen `InvoiceLine.Quantity→Album.AlbumId` |
| Edge: VIEWs excluidas | e2e | re-correr `profile_database` sobre sakila | `n_tables` cuenta solo BASE TABLE (sin `customer_list`/`film_list`/…); FK candidatas ≪ 565 |
| Edge: cantidad vs clave | unit | `infer_fk_containment_duckdb_test.py` con columna `Quantity` contenida en una clave | NO emite FK desde `Quantity` |
| Error: ATTACH SQLite | unit | test de regresión ATTACH SQLite→DuckDB | perfila sin `sqlite_master does not exist`; usa information_schema |
| Rendimiento (H10) | e2e | medir duración de `profile_database` sobre sakila | menor que el baseline 31.82s (menos INTERSECT) |
| Mecánica | — | `./fn run infer_fk_containment_duckdb_py_datascience`, `./fn run profile_database_py_pipelines`; `fn index` | tests verdes; índice limpio |

Re-correr el benchmark sobre chinook y sakila y confirmar que las FK reales son distinguibles del
ruido y que las VIEWs no se cuentan como tablas.

## Notas

Issue derivado de `temp/eda_benchmark/EDA_ISSUES.md`. Tres síntomas (H3/H5/H10) con un núcleo común:
la capa de inferencia de relaciones inter-tabla. Atacarlos juntos en una rama; filtrar VIEWs reduce
el universo de pares y filtrar candidatos por nombre arregla precisión y velocidad a la vez. H14 ya
está parcheado en producción; este issue solo añade el test de regresión que faltaba.
Hermanos: 0173, 0174, 0176, 0177.


## Resolucion (2026-06-29, sesion /ausente)
Resuelto y verificado con re-corrida del benchmark EDA. Commit principal: e142ef02. Detalle en reports/ausente-eda-benchmark-2026-06-29.md y temp/eda_benchmark/EVALUATION.md.
