""" Zero-token backfill: - Mark old posts with prefilter_reason (rt_only / url_only / empty) where applicable - Fill ai_reasoning for posts that have a signal but empty reasoning - Stamp analysis_version='legacy' on anything still missing it Run once after the schema migration. Safe to re-run. """ from __future__ import annotations import asyncio import os import sys from typing import Optional sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from sqlalchemy import select from app.database import AsyncSessionLocal from app.models import Post def classify(text: str) -> Optional[str]: stripped = (text or "").strip() if not stripped: return "empty" if stripped.startswith("RT: https://") and len(stripped) < 60: return "rt_only" if stripped.startswith("https://") and " " not in stripped: return "url_only" return None PREFILTER_MSG = { "rt_only": "Pre-filtered: retweet with no added commentary.", "url_only": "Pre-filtered: bare URL with no text content.", "empty": "Pre-filtered: empty post body.", } async def main(): async with AsyncSessionLocal() as db: posts = (await db.execute(select(Post))).scalars().all() pre = ai_fix = ver = 0 for p in posts: reason = classify(p.text) if reason and not p.prefilter_reason: p.prefilter_reason = reason if not p.ai_reasoning: p.ai_reasoning = PREFILTER_MSG[reason] ai_fix += 1 pre += 1 # Any remaining signal'd post with empty reasoning: fill a minimal note if p.signal and not p.ai_reasoning: p.ai_reasoning = "(legacy) reasoning not recorded for this post." ai_fix += 1 if not p.analysis_version: p.analysis_version = "legacy" ver += 1 await db.commit() print(f"✅ prefilter_reason set on {pre} posts") print(f"✅ ai_reasoning filled on {ai_fix} posts") print(f"✅ analysis_version stamped 'legacy' on {ver} posts") if __name__ == "__main__": asyncio.run(main())