"""One-time migration: convert existing timestamped UserNotes into profile summaries. Run with: python scripts/migrate_notes_to_profiles.py Requires .env with DB_CONNECTION_STRING and LLM env vars. """ import asyncio import os import sys sys.path.insert(0, os.path.dirname(os.path.dirname(__file__))) from dotenv import load_dotenv load_dotenv() from utils.database import Database from utils.llm_client import LLMClient async def main(): db = Database() if not await db.init(): print("Database not available.") return llm = LLMClient( base_url=os.getenv("LLM_BASE_URL", ""), model=os.getenv("LLM_MODEL", "gpt-4o-mini"), api_key=os.getenv("LLM_API_KEY", "not-needed"), ) states = await db.load_all_user_states() migrated = 0 for state in states: notes = state.get("user_notes", "") if not notes or not notes.strip(): continue # Check if already looks like a profile (no timestamps) if not any(line.strip().startswith("[") for line in notes.split("\n")): print(f" User {state['user_id']}: already looks like a profile, skipping.") continue print(f" User {state['user_id']}: migrating notes...") print(f" Old: {notes[:200]}") # Ask LLM to summarize notes into a profile result = await llm.extract_memories( conversation=[{"role": "user", "content": f"Here are observation notes about a user:\n{notes}"}], username="unknown", current_profile="", ) if result and result.get("profile_update"): profile = result["profile_update"] print(f" New: {profile[:200]}") await db.save_user_state( user_id=state["user_id"], offense_count=state["offense_count"], immune=state["immune"], off_topic_count=state["off_topic_count"], baseline_coherence=state.get("baseline_coherence", 0.85), user_notes=profile, warned=state.get("warned", False), last_offense_at=state.get("last_offense_at"), ) migrated += 1 else: print(f" No profile generated, keeping existing notes.") await llm.close() await db.close() print(f"\nMigrated {migrated}/{len(states)} user profiles.") if __name__ == "__main__": asyncio.run(main())