"""검토 대기(review_status='pending') 자동 검토 — 고신뢰 자동승인 + 저신뢰 수동 잔류. classify 가 이미 부여한 ai_confidence 를 게이트로 사용 — **재-LLM 호출 없음**(대량 2천건에 맥미니/GPU 부하 0, 분류 confidence 가 곧 AI 의 자기-신뢰도). ai_domain 보유 + ai_confidence >= THRESHOLD 인 pending 문서를 review_status='approved' 로 자동승인하고 audit(source_metadata.auto_reviewed)를 남긴다. 저신뢰/미분류는 그대로 두어 수동 검토 큐(/inbox)에 잔류. 설계 근거(게이트 실측): - review_status 는 inbox 카운트(dashboard) + 수집기 ingest 에서만 사용, 검색/RAG/digest/ ask 경로 필터에 **미사용** → 자동승인은 노출(검색결과) 변동 없이 검토 큐만 비운다. - pending 2,161 중 ai_suggestion 보유 0 → 이 큐는 '분류 변경 제안'(accept_suggestion)이 아니라 '미검토 자동분류'. 승인 = review_status 플립. 배치·interval 점진 드레인(관찰·중단 가능). 되돌리기 = source_metadata.auto_reviewed 마커로 대상 식별 후 review_status='pending' 복원. """ import logging from datetime import datetime, timezone from sqlalchemy import select from core.database import async_session from models.document import Document logger = logging.getLogger(__name__) # 고신뢰 자동승인 바 (튜닝 가능). 실측 분포: >=0.9 → 1,981건 자동 / 저신뢰·미분류 ~180건 수동 잔류. _CONFIDENCE_THRESHOLD = 0.9 # 한 틱 처리량 — 순수 DB UPDATE(LLM 없음)라 가볍지만, 2천 행 일괄 락 회피 위해 배치. _BATCH = 300 async def run() -> None: """pending 고신뢰 문서를 배치 자동승인 (interval job, no-arg).""" async with async_session() as session: rows = ( await session.execute( select(Document) .where( Document.review_status == "pending", Document.deleted_at.is_(None), Document.ai_domain.isnot(None), Document.ai_confidence.isnot(None), Document.ai_confidence >= _CONFIDENCE_THRESHOLD, ) .order_by(Document.id) .limit(_BATCH) ) ).scalars().all() if not rows: return now = datetime.now(timezone.utc) for doc in rows: doc.review_status = "approved" doc.source_metadata = { **(doc.source_metadata or {}), "auto_reviewed": { "by": "confidence_gate", "confidence": float(doc.ai_confidence), "threshold": _CONFIDENCE_THRESHOLD, "at": now.isoformat(), }, } doc.updated_at = now await session.commit() logger.info( "auto_review: approved %d pending docs (ai_confidence >= %.2f)", len(rows), _CONFIDENCE_THRESHOLD, )