"""S-4 초기 백필 — 모든 study_memo_card_progress row 를 발행 outbox 에 적재. ★ALL row(필터 없음) — due_at NULL sentinel(암-on-new)·terminal(졸업) 포함. due-only 백필은 sentinel 누락 → viewer 미확인 오분류. 멱등(워커 (payload_hash, deleted) 디둡). flag on 시 워커 drain. 실행 (GPU 서버): docker exec hyungi_document_server-fastapi-1 python /app/scripts/backfill_publish_card_progress.py docker exec hyungi_document_server-fastapi-1 python /app/scripts/backfill_publish_card_progress.py --dry-run """ import argparse import asyncio import os import sys sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) from sqlalchemy import func, select from core.config import settings from core.database import async_session from models.study_memo_card_progress import StudyMemoCardProgress from services.study.publish_enqueue import backfill_publish_card_progress # 개인 학습툴 progress row 대비 넉넉. 도달 시 가드 경보. PAGE = 100000 async def run(dry_run: bool) -> None: async with async_session() as session: total = ( await session.execute( select(func.count()).select_from(StudyMemoCardProgress) ) ).scalar() or 0 print(f"[info] study_publish_enabled={settings.study_publish_enabled} " f"(False 면 적재는 되나 워커가 drain 안 함)") print(f"[info] card progress row {total}건 (ALL row 발행)") if dry_run: print("[dry-run] 적재 안 함. 실제 실행은 --dry-run 제거.") return async with async_session() as session: n = await backfill_publish_card_progress(session, after_id=0, limit=PAGE) await session.commit() print(f"\n[ok] outbox 적재 {n}건 — 발행 워커가 drain(flag on 시) 하며 rev 부여.") if n >= PAGE: print(f"[warn] PAGE({PAGE}) 도달 — progress 가 더 있을 수 있음. after_id 페이징 추가 필요.") def main() -> None: parser = argparse.ArgumentParser(description="S-4 pub_card_progress 초기 백필") parser.add_argument("--dry-run", action="store_true", default=False) args = parser.parse_args() asyncio.run(run(args.dry_run)) if __name__ == "__main__": main()