From ac7de71ecd41664d7089659f46c46aaa123555e2 Mon Sep 17 00:00:00 2001 From: hyungi Date: Mon, 15 Jun 2026 15:36:56 +0900 Subject: [PATCH] =?UTF-8?q?feat(review):=20=EA=B2=80=ED=86=A0=20=EB=8C=80?= =?UTF-8?q?=EA=B8=B0=20=EC=9E=90=EB=8F=99=EA=B2=80=ED=86=A0=20=EC=9B=8C?= =?UTF-8?q?=EC=BB=A4=20=E2=80=94=20=EA=B3=A0=EC=8B=A0=EB=A2=B0=20=EC=9E=90?= =?UTF-8?q?=EB=8F=99=EC=8A=B9=EC=9D=B8=20+=20=EC=A0=80=EC=8B=A0=EB=A2=B0?= =?UTF-8?q?=20=EC=9E=94=EB=A5=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit auto_review_worker(interval 3분·배치 300): review_status='pending' + ai_domain + ai_confidence>=0.9 인 문서를 review_status='approved' 자동승인 + audit (source_metadata.auto_reviewed). 저신뢰/미분류는 수동 큐 잔류. 재-LLM 호출 없음 (classify confidence 게이트 = 맥미니 부하 0). review_status 는 검색/RAG/digest 필터 미사용(게이트 실측) → 노출 변동 없이 검토 큐만 드레인. 되돌리기=audit 마커로 식별. Co-Authored-By: Claude Opus 4.8 (1M context) --- app/main.py | 3 ++ app/workers/auto_review_worker.py | 72 +++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 app/workers/auto_review_worker.py diff --git a/app/main.py b/app/main.py index fadb1bb..3c240ac 100644 --- a/app/main.py +++ b/app/main.py @@ -78,6 +78,7 @@ async def lifespan(app: FastAPI): from workers.tier_backfill import run as tier_backfill_run from workers.upload_cleanup import cleanup_orphan_uploads from workers.memo_draft_worker import run as memo_draft_run + from workers.auto_review_worker import run as auto_review_run # 시작: DB 연결 확인 await init_db() @@ -108,6 +109,8 @@ async def lifespan(app: FastAPI): scheduler.add_job(cleanup_orphan_uploads, "interval", minutes=10, id="upload_cleanup") # P2: 메모→문서 승격분 26B 문서화 (needs_draft 마커 → md_content). 26B 콜이라 소량·2분 간격. scheduler.add_job(memo_draft_run, "interval", minutes=2, id="memo_draft", max_instances=1) + # 검토 대기 자동검토: 고신뢰(ai_confidence>=0.9) 자동승인 + 저신뢰 수동 잔류. 순수 DB(LLM 없음). + scheduler.add_job(auto_review_run, "interval", minutes=3, id="auto_review", max_instances=1) # PR-4: study_questions 자동 임베딩 (status='none/failed/stale' 행을 batch=10 처리). # 별도 큐 테이블 없이 status 자체가 큐. backfill 도 cron 이 'none' 행을 자연스럽게 처리. scheduler.add_job(study_q_embed_run, "interval", minutes=1, id="study_q_embed") diff --git a/app/workers/auto_review_worker.py b/app/workers/auto_review_worker.py new file mode 100644 index 0000000..1946342 --- /dev/null +++ b/app/workers/auto_review_worker.py @@ -0,0 +1,72 @@ +"""검토 대기(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, + ) -- 2.52.0