feat(0035c): web_search crea Group cuando excede umbral

Cuando un enricher web_search produce >= 50 resultados, los primeros 10
quedan sueltos colgando del source (preview Twitter/Reddit) y los
restantes entran como hijos de un nuevo nodo Group cuadrado.

Cambios:
- enrichers/web_search/run.py:
  - DEFAULT_GROUP_THRESHOLD=50, GROUP_PREVIEW_K=10 (constantes globales).
  - has_group_id_column(): detecta si el schema soporta agrupacion.
  - insert_group_entity(): crea nodo Group con metadata
    {enricher, query, count, batch_id}.
  - insert_url_entity() acepta batch_id y group_id; los inyecta en
    metadata/columna respectivamente. Nodos existentes mantienen su
    group_id actual (no se machaca).
  - Generacion de batch_id (UUID4 hex) por ejecucion, compartido por
    todos los nodos creados (group + sueltos + agrupados).
  - Cada hijo del grupo conserva su relacion individual SEARCH_RESULT_OF
    al source original — la procedencia es la relacion real, no el
    contenedor.
  - El JSON de salida añade batch_id, group_id, grouped.

- tests/conftest.py: añade columna entities.group_id al SCHEMA_SQL y
  expone group_id en list_entities() para que los tests lo verifiquen.

- tests/test_web_search.py: 3 tests nuevos
  - below_threshold_no_group: 5 resultados → 0 Groups, comportamiento clasico.
  - above_threshold_creates_group_and_preview: 100 resultados → 1 Group +
    10 sueltos + 90 con group_id, todos con SEARCH_RESULT_OF al source.
  - batch_id_shared_across_outputs: group + preview + hijos comparten
    batch_id.
  - _build_lite_html() genera HTML sintetico con N resultados sin
    necesidad de fixture estatico grande.

Tests: 35 passed (32 previos + 3 nuevos) en WSL.
       24 passed + 11 skipped en Windows.

Refs: issues/0035c-web-search-creates-groups.md
This commit is contained in:
2026-05-03 14:52:29 +02:00
parent 784b56ba10
commit 67f10a8afd
3 changed files with 275 additions and 7 deletions
+4 -3
View File
@@ -139,6 +139,7 @@ CREATE TABLE entities (
source TEXT NOT NULL,
metadata TEXT NOT NULL DEFAULT '{}',
notes TEXT NOT NULL DEFAULT '',
group_id TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
@@ -229,11 +230,11 @@ def list_entities(ops_db: Path, type_ref: str | None = None) -> list[dict]:
try:
if type_ref:
cur = conn.execute(
"SELECT id, name, type_ref, source, metadata "
"SELECT id, name, type_ref, source, metadata, group_id "
"FROM entities WHERE type_ref=? ORDER BY id", (type_ref,))
else:
cur = conn.execute(
"SELECT id, name, type_ref, source, metadata "
"SELECT id, name, type_ref, source, metadata, group_id "
"FROM entities ORDER BY id")
rows = cur.fetchall()
finally:
@@ -245,7 +246,7 @@ def list_entities(ops_db: Path, type_ref: str | None = None) -> list[dict]:
except Exception:
md = {}
out.append({"id": r[0], "name": r[1], "type_ref": r[2],
"source": r[3], "metadata": md})
"source": r[3], "metadata": md, "group_id": r[5]})
return out