fix(short): cerrar el bucle de edición y el desfase spec/vídeo en la subida
Build & Deploy ResearchOwl / build-and-push (push) Successful in 1m17s

Dos huecos de la revisión:

1. /short_spec prometía re-renderizar el spec editado pero no existía el
   camino de vuelta. Ahora un .json adjunto se valida contra el contrato
   vivo (errores con su ruta verbatim), se re-comprueba el fundamento (la
   edición pudo meter una cifra nueva), se guarda como output nuevo y se
   renderiza. Sin LLM: este camino es gratis. La sesión sale del nombre
   del fichero (short_{id}_spec.json), que Telegram conserva al reenviar.

2. /upload_short podía subir un vídeo viejo con metadatos nuevos: produce
   guarda el spec ANTES de renderizar, así que un re-intento con render
   fallido deja en disco el MP4 de la vuelta anterior. Ahora se compara el
   mtime del vídeo con el created_at del spec y se niega (force lo salta).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
ChemaVX
2026-08-05 15:46:06 +00:00
co-authored by Claude Fable 5
parent ec3e6d05c0
commit cc1f0cab93
5 changed files with 363 additions and 34 deletions
+32 -1
View File
@@ -6,7 +6,7 @@ tiene test propio aparte del pipeline.
"""
import json
from src.bot.bot import _claims_message
from src.bot.bot import _claims_message, _session_from_filename, _video_predates_spec
from src.generator.grounding import check_grounding
from src.generator.short import ShortResult
@@ -109,3 +109,34 @@ def test_upload_message_is_plain_text():
from src.bot.bot import _upload_message
text = _upload_message(_uploaded(title="JAL 1628: *three* radars_"), {}, None)
assert "*three*" in text and "radars_" in text
# --- guard de vídeo viejo en /upload_short ----------------------------------
class TestStaleVideoGuard:
"""`produce` guarda el spec ANTES de renderizar: si un re-intento falla,
en disco queda el vídeo de la vuelta anterior y subirlo le pondría los
metadatos del spec nuevo a un vídeo viejo."""
def test_a_fresh_render_is_never_stale(self):
# El MP4 se escribe ~1 min después de guardarse el spec.
assert not _video_predates_spec(1000.0 + 60, 1000.0)
def test_clock_jitter_does_not_cry_wolf(self):
assert not _video_predates_spec(1000.0 - 3, 1000.0)
def test_a_video_hours_older_than_the_spec_is_flagged(self):
assert _video_predates_spec(1000.0 - 3600, 1000.0)
class TestSessionFromFilename:
"""Telegram conserva el nombre del fichero al reenviarlo: el id que puso
/short_spec manda sobre la sesión activa del chat."""
def test_the_short_spec_filename_declares_its_session(self):
assert _session_from_filename("short_166_spec.json") == 166
def test_a_foreign_filename_falls_back_to_none(self):
assert _session_from_filename("myspec.json") is None
assert _session_from_filename("") is None
assert _session_from_filename(None) is None