fix(db): 90 días, porque 30 no era una política sino una suposición
Build & Deploy ResearchOwl / build-and-push (push) Successful in 9s

30 días no medía cuánto hay que guardar: medía cuánto se creía que duraba el
material. Los 17 specs `short_en` del catálogo se escribieron entre el 2 y el
13 de agosto y seguían siendo el material de trabajo el 1 de septiembre —tres
semanas después de que su ventana empezara a correr—, y el 31 de agosto se
re-renderizaron tres de ellos. Con 90 días, lo que se purga es lo que de
verdad nadie va a volver a mirar.

El plazo se escribe UNA vez. Estaba en el arranque, en `/purge` y en la firma
por defecto: tres copias de un plazo son tres plazos esperando a divergir, y
ese es el mismo error de clase que tenía el tope de palabras por línea antes de
derivarse de la duración del plano.

El test nuevo fija la ventana por COMPORTAMIENTO y no por el número: un output
de 45 días sobrevive y uno de 200 no. Estrecharla por debajo de mes y medio lo
tumba, que es donde empieza a llevarse cosas que aún se usan.

Y el fixture de purga pasa a llamar al valor por defecto cuando no se le dice
otro, porque pasarle el plazo a mano en cada test dejaba el de producción sin
que ninguna prueba lo mirara.

Suite: 279 pasan.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
ChemaVX
2026-09-01 17:30:01 +00:00
co-authored by Claude Opus 5
parent a14e5b99f9
commit 7c18ae68f1
3 changed files with 46 additions and 6 deletions
+5 -3
View File
@@ -21,7 +21,9 @@ from telegram.ext import (
from telegram.constants import ParseMode
from src.config import settings
from src.db.database import get_db, close_db, ResearchDB, ResearchStatus, OutputType
from src.db.database import (
get_db, close_db, ResearchDB, ResearchStatus, OutputType, RETENTION_DAYS,
)
from src.scraper.exhaustive import ExhaustiveScraper
from src.processor.processor import OllamaClient, ContentProcessor
from src.generator.generator import OutputGenerator
@@ -1346,7 +1348,7 @@ async def _purge_on_startup(app: Application) -> None:
db_conn = await get_db()
try:
db = ResearchDB(db_conn)
result = await db.purge_old_data(30)
result = await db.purge_old_data(RETENTION_DAYS)
# Cualquier borrado, no sólo el de sesiones. Con la retención por fecha
# de output, una pasada puede llevarse 27 outputs y CERO sesiones — y
# con la condición anterior eso no dejaba ni una línea de log. Una purga
@@ -1590,7 +1592,7 @@ async def cmd_purge(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
args = ctx.args or []
if not args:
days = 30
days = RETENTION_DAYS
else:
try:
days = int(args[0])
+13 -1
View File
@@ -12,6 +12,18 @@ from src.config import settings
logger = structlog.get_logger()
#: Cuánto se guarda. 90 días, no 30, y el motivo es que 30 no era una política
#: de retención: era una suposición sobre cuánto duraba el material. Los 17
#: specs `short_en` del catálogo se escribieron entre el 2 y el 13 de agosto y
#: seguían siendo el material de trabajo el 1 de septiembre — tres semanas
#: después de que su ventana de 30 días empezara a correr. Con 90, lo que se
#: purga es lo que de verdad nadie va a volver a mirar.
#:
#: Escrito una sola vez a propósito: estaba en el arranque, en `/purge` y en la
#: firma por defecto, y tres copias de un plazo son tres plazos esperando a
#: divergir.
RETENTION_DAYS = 90
class ResearchStatus(str, Enum):
RUNNING = "running"
@@ -648,7 +660,7 @@ class ResearchDB:
# --- Maintenance ---
async def purge_old_data(self, max_age_days: int = 30) -> dict:
async def purge_old_data(self, max_age_days: int = RETENTION_DAYS) -> dict:
"""Retención en dos fases: primero los outputs por SU fecha, luego las
sesiones que ya no sostienen nada.