#1 batch scoring (processor):
- _score_quality_batch puntúa hasta 25 chunks por llamada a Claude en vez
de una por chunk (una fuente de 19 chunks pasaba de 19 llamadas a 1)
- parser robusto (último número por línea, padding neutro si faltan)
- fallback por-chunk con Ollama si el batch falla
#2 fuentes opcionales (config + scraper):
- ENABLE_YOUTUBE / ENABLE_REDDIT, default False: la IP del homelab está
bloqueada por Reddit (403) y YouTube (transcripts vacíos), eran peso muerto
- se saltan también las URLs de yt/reddit descubiertas dentro de webs, sin
gastar petición de red
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
simple_chunk:
- parte por \n+ (no solo \n\n): Wikipedia/trafilatura usan \n simple, lo
que colapsaba cada fuente en un único chunk gigante
- subdivide párrafos que superan chunk_size
- el overlap arrastra un tail de N palabras en vez del párrafo completo
(evita chunks inflados a ~2x cuando los párrafos son grandes)
processor: embedding sobre el chunk completo (antes truncaba a 1000 chars,
el vector solo representaba el principio del chunk → ranking RAG pobre)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- .gitignore para Python, .env, *.db y artefactos de editor/OS
- git rm --cached de todos los __pycache__/*.pyc previamente trackeados
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sección crítica:
- is_blacklisted: match por dominio/subdominio exacto (antes "x.com" como
substring bloqueaba netflix.com, phoenix.com, etc.)
- normalize_url: conserva el query string (rompía YouTube watch?v= y URLs
con ?id=); solo borra el fragment
- get_db: PRAGMA busy_timeout=5000 para evitar "database is locked" en
/compare y watches solapados
- OllamaClient.embed: usa OLLAMA_EMBED_MODEL en vez del modelo de chat
- log_api_call: coste por modelo (opus/sonnet/haiku) en vez de Haiku fijo
Mejoras:
- src/llm.py: cliente Anthropic compartido y cacheado (antes se instanciaba
uno por cada llamada/chunk)
- SEARXNG_URL configurable via env
- get_running_loop() en vez de get_event_loop() (deprecado)
- soup.title.get_text() robusto ante <title> con tags anidados
- limpieza: import muerto, total_words duplicado, w_id no usado
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
El resumen de diff generado por Claude se enviaba con parse_mode=MARKDOWN;
texto libre con entidades desbalanceadas provocaba BadRequest y el mensaje
no llegaba. Además el send_message estaba fuera del try/except de _task y el
task se retenía en _active_tasks sin await, así que la excepción se tragaba
sin log alguno.
- _safe_send(): intenta Markdown y reintenta en texto plano si falla
- _task: except de nivel superior que loguea cualquier fallo
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
La purga de sesiones >30d fallaba con FOREIGN KEY constraint failed:
api_usage.session_id referencia research_sessions(id) pero nunca se
borraba antes de la sesión padre (con PRAGMA foreign_keys = ON).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Mismo patrón que cmd_generate: si hay sesión activa registrada
para el chat, consulta por id; si no, fallback a created_at DESC.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Si hay una sesión activa registrada para el chat, se consulta
directamente por id en lugar de por created_at DESC, evitando
que /generate use la sesión más reciente en vez de la actual.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
El session dict se obtiene antes de generator.generate() y puede
contener datos de la sesión anterior. Ahora se extrae el topic
directamente de la línea "Topic:" del header del output generado,
que siempre refleja la sesión actual usada en la generación.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Ghost añade el título del post automáticamente en el frontend,
por lo que el <h1> generado desde el markdown aparecía duplicado.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
El campo "html" en Ghost Admin API v5 (Lexical editor) es de solo
lectura. El contenido se debe enviar via mobiledoc con HTML card,
que Ghost acepta en todas las versiones de v5 y renderiza sin
conversión. Añadidos logs de diagnóstico y validación de HTML vacío.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The synchronous get_transcript() call was blocking the asyncio event
loop indefinitely, freezing the entire bot (including Telegram polling).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>