"""1회성 마이그레이션 — agent_office.db.tarot_readings → tarot.db.tarot_readings. 멱등성: 이미 존재하는 id는 SKIP. 실행: docker exec agent-office python /app/scripts/migrate_tarot_to_lab.py 또는 호스트에서 직접: AGENT_OFFICE_DB=/path/to/agent_office.db TAROT_DB=/path/to/tarot.db \\ python scripts/migrate_tarot_to_lab.py """ import os import sqlite3 import sys SRC = os.getenv("AGENT_OFFICE_DB", "/app/data/agent_office.db") DST = os.getenv("TAROT_DB", "/app/data/tarot.db") SCHEMA = """ CREATE TABLE IF NOT EXISTS tarot_readings ( id INTEGER PRIMARY KEY AUTOINCREMENT, created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')), spread_type TEXT NOT NULL, category TEXT, question TEXT, cards TEXT NOT NULL, interpretation_json TEXT, summary TEXT, model TEXT, tokens_in INTEGER, tokens_out INTEGER, cost_usd REAL, confidence TEXT, favorite INTEGER NOT NULL DEFAULT 0, note TEXT ); """ def migrate() -> int: """이관된 row 수 반환.""" src = sqlite3.connect(SRC) src.row_factory = sqlite3.Row dst = sqlite3.connect(DST) dst.execute("PRAGMA journal_mode=WAL") dst.executescript(SCHEMA) rows = src.execute("SELECT * FROM tarot_readings").fetchall() if not rows: src.close(); dst.close() return 0 all_cols = list(rows[0].keys()) moved = 0 for r in rows: exists = dst.execute("SELECT 1 FROM tarot_readings WHERE id=?", (r["id"],)).fetchone() if exists: continue # NULL 값은 INSERT에서 제외 → 목적지 스키마의 DEFAULT가 적용되도록 함 # (예: created_at이 NULL이면 strftime() 기본값 사용) cols = [c for c in all_cols if r[c] is not None] placeholders = ",".join("?" * len(cols)) cols_str = ",".join(cols) dst.execute( f"INSERT INTO tarot_readings ({cols_str}) VALUES ({placeholders})", tuple(r[c] for c in cols), ) moved += 1 dst.commit() src.close(); dst.close() return moved if __name__ == "__main__": moved = migrate() total = sqlite3.connect(SRC).execute("SELECT COUNT(*) FROM tarot_readings").fetchone()[0] print(f"migrated {moved} / {total} rows from {SRC} to {DST}") sys.exit(0)