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
Anade enricher web_search aplicable a nodos text/Concept/Topic. Hace
POST a html.duckduckgo.com con la query del nodo, parsea resultados
con HTMLParser stdlib, decodifica el redirect uddg= y crea N nodos
Url con relacion SEARCH_RESULT_OF apuntando al nodo origen.
Encadenable: tras web_search, fetch_webpage sobre cada Url completa
el pipeline search -> fetch -> extract.
Defensa contra ops_db_path mal resuelto: normaliza backslashes,
resuelve relativo contra app_dir, valida que la tabla entities
exista antes de tocar nada (exit codes 7/8/9 con JSON resumen).
Tests pytest (16/16 verde): conftest con operations.db temp +
schema minimo, stub de requests via PYTHONPATH para mockear red.
Cubre los 5 enrichers (extract_domain, fetch_webpage, extract_links,
extract_text_entities, web_search) + sanity check de manifests.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>