From b4e1c5cd123964941c99022aa3f74cf168806162 Mon Sep 17 00:00:00 2001 From: Viktor Barzin Date: Thu, 28 May 2026 22:55:14 +0000 Subject: [PATCH] =?UTF-8?q?fix(scripts):=20kevin-analyze=20+=20reanalyze?= =?UTF-8?q?=20=E2=80=94=20self-contained=20+=20append-only?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two small follow-ups from the session: - kevin-analyze.sh: always cp the local analyze_kevin_video.py into the pod before exec, so the wrapper works even before CI ships an image with scripts/ baked in (saw this hit the user with ModuleNotFoundError on stale images). - reanalyze_kevin_videos.py: switch from DELETE+INSERT to append-only. The old behaviour violated the FK constraint on kevin_signal_bridge_state.mention_id; the new behaviour adds a fresh v2 analysis alongside the v1 row and lets the API surface the latest. Old v1 rows preserved for audit. --- scripts/kevin-analyze.sh | 20 +++++++++++++++++--- scripts/reanalyze_kevin_videos.py | 15 ++++++--------- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/scripts/kevin-analyze.sh b/scripts/kevin-analyze.sh index 328a42c..5458f95 100755 --- a/scripts/kevin-analyze.sh +++ b/scripts/kevin-analyze.sh @@ -5,8 +5,9 @@ # ./scripts/kevin-analyze.sh # # Picks the running meet-kevin-watcher container (which already has -# yt-dlp + ffmpeg + the Anthropic token + the right Python env) and -# runs scripts/analyze_kevin_video.py inside it. +# yt-dlp + ffmpeg + the Anthropic token + the right Python env), copies +# the local script in, and runs it. Works regardless of whether the +# image has scripts/ baked in yet. set -euo pipefail @@ -15,6 +16,14 @@ if [[ $# -ne 1 ]]; then exit 1 fi +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +LOCAL_SCRIPT="$SCRIPT_DIR/analyze_kevin_video.py" + +if [[ ! -f "$LOCAL_SCRIPT" ]]; then + echo "missing $LOCAL_SCRIPT" >&2 + exit 1 +fi + POD=$(kubectl -n trading-bot get pod -l app=trading-bot-workers \ -o jsonpath='{.items[0].metadata.name}') @@ -23,5 +32,10 @@ if [[ -z "$POD" ]]; then exit 1 fi +# Copy the latest local script into the pod, then exec it. This way the +# wrapper works even before CI ships an image with scripts/ baked in. +kubectl -n trading-bot cp "$LOCAL_SCRIPT" \ + "$POD:/tmp/kevin-analyze.py" -c meet-kevin-watcher >/dev/null + exec kubectl -n trading-bot exec "$POD" -c meet-kevin-watcher -- \ - python -m scripts.analyze_kevin_video "$1" + python /tmp/kevin-analyze.py "$1" diff --git a/scripts/reanalyze_kevin_videos.py b/scripts/reanalyze_kevin_videos.py index a9c08f4..3f6c9d7 100644 --- a/scripts/reanalyze_kevin_videos.py +++ b/scripts/reanalyze_kevin_videos.py @@ -29,7 +29,7 @@ from anthropic import AsyncAnthropic sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) -from sqlalchemy import delete, select +from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from services.meet_kevin_watcher.caption_extractor import extract_captions @@ -86,14 +86,11 @@ async def _reanalyze_one( if dry_run: return True, result.cost_usd, "dry_run_skipped_write" - # Replace old analysis + mentions for this video atomically. - await session.execute( - delete(KevinStockMention).where(KevinStockMention.video_id == video.id) - ) - await session.execute( - delete(KevinAnalysis).where(KevinAnalysis.video_id == video.id) - ) - + # Append-only: insert new analysis + mentions for this video. We do + # NOT delete the old v1 rows because kevin_signal_bridge_state has + # FKs into kevin_stock_mentions (would cascade-break the audit + # trail). The API surfaces the newest analysis per video and the + # bridge picks up the new mentions by ID > cursor. db_analysis = KevinAnalysis( video_id=video.id, model=analyzer._model,