User reported that the old prompt could emit 'sell' on backward-looking
capitulation ('Kevin sold after a 20% drop') — exactly the false signal
to avoid. v2 reframes every per-ticker field as forward-looking and
adds an explicit expected_move enum for the trading bot to weight.
Changes:
- New ExpectedMove enum (up_strong/up_mild/sideways/down_mild/
down_strong/unknown) in shared/schemas + shared/models, with
matching kevin_expected_move Postgres enum + column on
kevin_stock_mentions (migration e5f6a7b8c9d0). NOT NULL with
server_default 'unknown' so existing rows backfill cleanly.
- SYSTEM_PROMPT rewritten: action semantics now require a FORWARD
view; reactive sells get downgraded to 'watch' or skipped; the
rationale_quote must contain forward reasoning. Quality
checklist updated.
- _ANALYSIS_TOOL JSON schema gains expected_move (required).
- prompt_version v1 → v2 in config + infra + ad-hoc CLI default.
- pipeline.py persists ticker.expected_move into the new column.
Migration safety: the column is NOT NULL DEFAULT 'unknown' so 96
existing mentions auto-fill with 'unknown' (no forward call known
for backward analyses) without breaking any reads.
Cost to backfill the 27 existing analyses with v2 prompt: ~$3 LLM
spend. A follow-up reanalyze script will replay them after this
ships.
3 tables (kevin_signal_bridge_state, kevin_backtest_runs,
kevin_backtest_trades) all UUID-keyed for consistency with Trade/Position.
KEVIN_STRATEGY_UUID constant pinned for FK joins from Trade.strategy_id.
Three issues caught during end-to-end manual QA against docker-compose:
1. SAEnum field columns serialized to Python enum NAMES ('DISCOVERED')
but the DB enum had VALUES ('discovered'). Added `values_callable`
to all 5 SAEnum() declarations in shared/models/meet_kevin.py so they
emit values, matching the migration's enum literals.
2. /dashboard's "last 7 days" / "last 14 days" filters used
`func.cast("7 days", type_=None)` which produced NullType DDL.
Replaced with `text("now() - interval '7 days'")`.
3. /dashboard's outlook trend query repeated `func.date_trunc("day", col)`
in SELECT, GROUP BY and ORDER BY — Postgres treats each as a separate
parameterized expression. Hoisted into a single `day_trunc` variable
so all three clauses reference the same SQL fragment.
All 11 /api/meet-kevin/* endpoints now return valid JSON against a
docker-compose Postgres seeded with one analyzed video + NVDA mention.