Seguimiento del gobierno uruguayo
ciclo #191 act. 2026-06-09 · 19:51

Metodología

Una explicación honesta de cómo se generan los reportes diarios, qué fuentes usamos, cómo decidimos qué destacar, y qué dejamos afuera.

Cadencia: cada 1 hora

El bot corre cada hora desde una agenda local automatizada (launchd en macOS). Cada corrida es un ciclo numerado (ciclo #N). En cada ciclo el bot:

  1. Scrapea todas las fuentes oficiales con scrapers determinísticos (HTML estable, sin LLM).
  2. Normaliza los items a un schema único {source, title, url, published_at, summary_raw, item_type}.
  3. Pasa los items curados a un agente LLM (Gemini 3.5 Flash) que decide qué destacar.
  4. El LLM genera un reporte estructurado con frontmatter Jekyll y lo escribe en _posts/.
  5. Jekyll recompila el sitio estático y nginx lo sirve.

La URL /AAAA/MM/DD/cycle-N/ siempre sirve ese ciclo. La home muestra el más reciente.

Arquitectura

El procesamiento está separado en dos máquinas:

Esto desacopla la captura (rápida, determinística, sin necesidad de LLM) de la curaduría (más lenta, semántica, requiere modelo). Si el LLM falla o se atrasa, los scrapers siguen funcionando y se puede regenerar cualquier ciclo después.

Fuentes

La lista canónica vive en data/sources-extended.yml. Hoy cubrimos 22 fuentes agrupadas:

Legales primarias

Control y transparencia

Entes y servicios públicos

Gobierno subnacional

Cuando una fuente falla en un ciclo (404, paywall, error de conexión, anti-bot), aparece en el bloque "Transparencia del ciclo" con el motivo. No la escondemos.

Scrapers determinísticos

Cada fuente tiene un scraper Python específico en scripts/scrapers/<key>.py que:

  1. Hace una request HTTP con User-Agent: hoy-luz-uy-bot/1.0 (+https://hoy.luz.uy).
  2. Parsea el HTML con BeautifulSoup usando selectores específicos del sitio.
  3. Emite items normalizados en JSONL al stdout.
  4. Falla con exit code != 0 si el sitio cambió de estructura.

Cuando un scraper falla, se crea un ticket en data/cache/broken-scrapers/<key>.json. El próximo ciclo de mantenimiento lanza un agente LLM que inspecciona el HTML actual y propone un selector nuevo. El LLM no participa del scrape, solo del fix cuando algo cambia.

Esto garantiza:

Cómo se elige qué destacar

El prompt de curaduría está en el repositorio local (~/hoy.luz.uy/prompts/curate.md). En resumen, el LLM debe:

Y excluye:

Taxonomía de hechos

Cada hecho lleva una clasificación estructurada:

Dimensión Valores
policy_area economía, seguridad, salud, educación, vivienda, trabajo, ambiente, transporte, defensa, RREE, desarrollo social, cultura, tecnología, agro, justicia, institucional
gov_function anuncio, ejecución, regulación, presupuesto, compra pública, nombramiento, convenio, fiscalización, crisis, datos
impact alto, medio, bajo
status anunciado, en trámite, aprobado, implementado, observado, desmentido, pendiente
confidence alta, media, baja
quality de fuente oficial-primaria, documento, prensa, redes

Estos campos habilitan las tres vistas del sitio: Diaria (curaduría editorial), Por tema (agrupado por área de política), Cronología (línea de tiempo).

Verificación cruzada

En cada ciclo el agente elige a un funcionario y verifica una de sus declaraciones contra la fuente primaria oficial (comunicado de Presidencia, gub.uy, página del ente). El resultado aparece en el bloque "Verificación cruzada" del reporte con uno de estos estados:

Limitaciones que reconocemos

Stack técnico

Componente Tecnología
Scrapers Python 3 + requests + BeautifulSoup
Orquestador bash + python (en Mac)
LLM curador Gemini 3.5 Flash vía agy (Antigravity CLI)
Sitio estático Jekyll 4
Servidor nginx + Let's Encrypt (luz.uy)
Agenda launchd (macOS) cada 3600s

Código y prompts abiertos. MIT.

¿Listo para leer? Mirá el último reporte →