Files
hyungi_document_server/scripts/backfill_publish_card_progress.py
T
hyungi 5b5353c751 fix(publish): 백필 스크립트 전 모델 import (standalone mapper 레지스트리 완성)
app 은 라우터 경유로 전 모델을 import 하지만 standalone 백필 스크립트는 부분만 import →
SQLAlchemy mapper 의 string 관계(StudyTopic.sessions->StudySession 등) 해소 실패로
InvalidRequestError. pkgutil 로 models/* 전 모듈 import 해 레지스트리 완성(전부 컨테이너서
import 가능 = app 기동 시 로드되는 것과 동일). 백필 3종 실행 검증: topics 1·cards 65·progress 22 적재.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 22:54:40 +00:00

71 lines
2.7 KiB
Python

"""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__), ".."))
# standalone-model-registry-fix: app(라우터 경유 전 모델 import)과 달리 script 는 부분 모델만
# import → SQLAlchemy mapper string 관계(StudyTopic.sessions->StudySession 등) 해소 실패.
# 전 모델 모듈 import 로 레지스트리 완성(전부 컨테이너서 import 가능 = app 이 기동 시 로드).
import importlib as _il, pkgutil as _pu
import models as _mp
for _m in _pu.iter_modules(_mp.__path__):
_il.import_module("models." + _m.name)
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()