Un monitor de indicadores cortado en dos mitades. La primera, determinista y probada, recolecta cada semana y arma un briefing cada mes en GitHub Actions, sin ninguna llave. La segunda es el juicio: una sesión de Claude Code lee el briefing contra un criterio versionado y emite un JSON que el código valida y guarda como predicciones falsables en un ledger SQLite. Cinco vistas: contenedores, datos, flujos, despliegue y decisiones.
Cada caja es una pieza que corre por separado: un runner de GitHub Actions, el CLI, el ledger, una sesión de juicio. Las líneas son llamadas reales: protocolo y qué viaja.
La mitad automática no emite ningún juicio, así que cada pieza se prueba sin modelo en el camino: dedup, ventanas, selección, migraciones. La mitad de juicio tiene como entrada un archivo, el briefing, que queda congelado en disco y en la base. La costura entre las dos es ese archivo.
Cada señal que afirma un movimiento deja una predicción binaria con fecha de vencimiento. El sistema existe para producir una tasa de acierto honesta: un indicador con diez o más predicciones resueltas que no supera el azar queda marcado como candidato a eliminar.
El código no llama a ninguna API de modelos: el juicio lo aporta la sesión de Claude Code y el código solo valida y persiste. Los workflows no necesitan secretos, solo el token del repositorio. Clonar el repo y correr los comandos funciona sin configurar nada.
UNIQUE (domain, content_hash) sobre el contenido normalizado. Nunca por URL: las URL de un agregador de noticias son redirecciones distintas para el mismo artículo.signals.judged_by y signals.analyze_version dicen quién juzgó y bajo qué versión del criterio. Sin la versión, el histórico de precisión mezclaría criterios distintos.CHECK: no_change exige confidence NULL; trigger y movement la exigen no nula. La confianza es una apuesta sobre una predicción; sin predicción no hay nada que calibrar.briefings.window_start/end es un dato, no una inferencia desde la etiqueta YYYY-MM: si se salta una corrida, el briefing siguiente cubre más de un período y se ve.briefing_indicators guarda found (antes del tope) y rendered, con CHECK (rendered <= found): el juicio tiene que dar cuenta de cada observación que el briefing listó.collection_stats registra cuántas entradas trajo una fuente y cuántas cayeron dentro de la ventana: "trajo 100 y ninguna sirve" no deja rastro en observations.CREATE TABLE IF NOT EXISTS; todo cambio va en core/migrations/NNN_*.sql, transaccional, con respaldo previo. Las nueve vistas se borran y se recrean siempre.resolve está declarado en el CLI y falla con un mensaje explícito.Nueve vistas SQL (v_prediction_scoring, v_indicator_accuracy, v_confidence_calibration, v_overdue_predictions, v_indicator_noise, v_indicator_collection y otras) concentran la política de métricas en SQL, no dispersa en el código.
Las transiciones hacia resolved y unresolvable están en el esquema y en las vistas, pero el código que las ejecuta no existe: el CLI declara resolve y expire y falla diciéndolo.
No hay servidor ni hosting: el sistema es un repositorio. La base signal.db se versiona a propósito, porque es el histórico de precisión y los workflows dependen de ella. Según el README, los dos workflows están escritos pero todavía no se han ejecutado en GitHub.
pip install -r requirements.txtubuntu-latestCalidad: pruebas con pytest, ruff y mypy en modo estricto, también sobre los tests.
Qué gana: horarios libres y estado fuera del historial de git. Qué cuesta: un servidor que mantener, justo lo que hoy no existe.
| Decisión | Por qué | Descartado |
|---|---|---|
| Dos mitades unidas por un archivo .md | La mitad automática se prueba entera porque no hay modelo en el camino; el briefing congelado permite re-juzgar el mismo insumo con otro criterio | Un pipeline único que recolecta y juzga: no testeable y no repetible |
| Sin llamadas a la API de un modelo | Costo variable cero, sin acoplamiento a un proveedor y sin secretos: el juicio entra por una sesión de Claude Code y el código solo valida y persiste | Un paso del workflow que llame a un modelo: llaves, gasto por corrida y una dependencia más |
| Colecta semanal, juicio mensual | Los feeds RSS tienen profundidad limitada y lo que sale del feed se pierde sin dejar hueco visible; las predicciones tienen horizonte de 180 días | Todo mensual: pérdida silenciosa. Juicio semanal: desajuste de escalas |
| Los dominios son datos, no código | Un área nueva es un domain.yaml con indicadores, condición de disparo y collectors; el id de un indicador nunca se renombra porque es la llave del histórico | Un módulo Python por dominio |
| El tope de observaciones está en el render | El collector devuelve todo lo de la ventana; el briefing corta y reporta la pérdida. La resolución necesita la evidencia completa del período | Cortar en el collector: el ledger resolvería contra una vista parcial del mundo |
| Diversidad de fuente antes que recencia | Primero entra una observación por fuente distinta; si no, una nota de agencia replicada por doce sitios consume todo el presupuesto del indicador | Ordenar solo por fecha |
| Dedup por contenido, nunca por URL | Las URL de un agregador son redirecciones distintas para el mismo artículo | Restricción única sobre la URL |
| Anti-anclaje por tipo | La estructura de las predicciones vencidas no tiene campos de confianza ni de razonamiento, así que el renderer no puede imprimirlos ni por error | Omitirlos en la plantilla: depende de la disciplina de quien la edite |
judge nunca lee la tabla de señales | Re-juzgar el mismo briefing no se contamina con el juicio anterior; tres pruebas defienden la propiedad | Mostrar el juicio previo como contexto |
| Superseder conserva lo resuelto | Re-juzgar cancela las predicciones abiertas pero lo ya resuelto sigue contando bajo su versión de criterio; lo que pasó en el mundo no cambia | Borrar el juicio viejo: un botón de limpiar historial |
| Pre-registro del experimento | Predicciones, umbrales y reintentos escritos antes de correr; un criterio escrito después del resultado no significa nada | Ajustar el criterio mirando el mes concreto |