from __future__ import annotations from sqlalchemy import and_, select from .shared import * from .helpers import _resolve_tenant_id from ...models import QAFeedback, QARecord from ...services.feedback_center import FeedbackCenterService @router.post("/qa-records/{qa_id}/feedback") async def submit_qa_feedback( qa_id: int, request: QAFeedbackCreateRequest, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), tenant_id: int = Depends(get_tenant_id), ): service = AdaptiveRAGService(db, tenant_id=_resolve_tenant_id(current_user, tenant_id)) try: result = await service.submit_feedback( qa_id=qa_id, rating=request.rating, helpfulness=request.helpfulness, error_types=request.error_types, correct_answer=request.correct_answer, submitted_by=int(current_user.id), ) except ValueError as exc: raise HTTPException(status_code=404, detail=str(exc)) from exc try: feedback_row = ( await db.execute( select(QAFeedback) .where( and_( QAFeedback.tenant_id == _resolve_tenant_id(current_user, tenant_id), QAFeedback.qa_id == qa_id, QAFeedback.submitted_by == int(current_user.id), ) ) .order_by(QAFeedback.created_at.desc(), QAFeedback.id.desc()) ) ).scalars().first() qa_row = ( await db.execute( select(QARecord).where( and_( QARecord.tenant_id == _resolve_tenant_id(current_user, tenant_id), QARecord.id == qa_id, ) ) ) ).scalar_one_or_none() if feedback_row and qa_row: await FeedbackCenterService(db).ingest_qa_feedback( qa_feedback=feedback_row, qa_record=qa_row, commit=True, ) except Exception: await db.rollback() return success_response(result) @router.get("/qa-records") async def list_qa_records( project_id: int | None = Query(default=None), date_from: datetime | None = Query(default=None), date_to: datetime | None = Query(default=None), rating: str | None = Query(default=None), page: int = Query(default=1, ge=1), page_size: int = Query(default=20, ge=1, le=100), current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), tenant_id: int = Depends(get_tenant_id), ): _ = current_user query = QAListQuery( project_id=project_id, date_from=date_from, date_to=date_to, rating=rating, page=page, page_size=page_size, ) service = AdaptiveRAGService(db, tenant_id=_resolve_tenant_id(current_user, tenant_id)) result = await service.list_qa_records( project_id=query.project_id, date_from=query.date_from, date_to=query.date_to, rating=query.rating, page=query.page, page_size=query.page_size, ) return success_response(result) @router.get("/qa-sessions") async def list_qa_sessions( date_from: datetime | None = Query(default=None), date_to: datetime | None = Query(default=None), page: int = Query(default=1, ge=1), page_size: int = Query(default=20, ge=1, le=100), current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), tenant_id: int = Depends(get_tenant_id), ): query = QASessionListQuery( date_from=date_from, date_to=date_to, page=page, page_size=page_size, ) service = AdaptiveRAGService(db, tenant_id=_resolve_tenant_id(current_user, tenant_id)) result = await service.list_qa_sessions( user_id=int(current_user.id), date_from=query.date_from, date_to=query.date_to, page=query.page, page_size=query.page_size, ) return success_response(result) @router.get("/qa-sessions/{session_id}") async def qa_session_detail( session_id: int, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), tenant_id: int = Depends(get_tenant_id), ): service = AdaptiveRAGService(db, tenant_id=_resolve_tenant_id(current_user, tenant_id)) try: result = await service.get_qa_session_detail(session_id=session_id, user_id=int(current_user.id)) except ValueError as exc: raise HTTPException(status_code=404, detail=str(exc)) from exc return success_response(result) @router.get("/qa-sessions/{session_id}/state") async def qa_session_state_replay( session_id: int, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), tenant_id: int = Depends(get_tenant_id), ): service = AdaptiveRAGService(db, tenant_id=_resolve_tenant_id(current_user, tenant_id)) try: result = await service.get_session_state_replay( session_id=session_id, user_id=int(current_user.id), ) except ValueError as exc: raise HTTPException(status_code=404, detail=str(exc)) from exc return success_response(result) @router.patch("/qa-sessions/{session_id}/title") async def rename_qa_session( session_id: int, request: QASessionRenameRequest, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), tenant_id: int = Depends(get_tenant_id), ): service = AdaptiveRAGService(db, tenant_id=_resolve_tenant_id(current_user, tenant_id)) try: result = await service.rename_qa_session( session_id=session_id, user_id=int(current_user.id), title=request.title, ) except ValueError as exc: raise HTTPException(status_code=404, detail=str(exc)) from exc return success_response(result) @router.delete("/qa-sessions/{session_id}") async def delete_qa_session( session_id: int, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), tenant_id: int = Depends(get_tenant_id), ): service = AdaptiveRAGService(db, tenant_id=_resolve_tenant_id(current_user, tenant_id)) deleted = await service.delete_qa_session(session_id=session_id, user_id=int(current_user.id)) if not deleted: raise HTTPException(status_code=404, detail="qa session 不存在") return success_response({"deleted": True, "session_id": session_id}) @router.get("/qa-records/stats") async def qa_records_stats( project_id: int | None = Query(default=None), date_from: datetime | None = Query(default=None), date_to: datetime | None = Query(default=None), current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), tenant_id: int = Depends(get_tenant_id), ): service = AdaptiveRAGService(db, tenant_id=_resolve_tenant_id(current_user, tenant_id)) result = await service.get_qa_stats( project_id=project_id, date_from=date_from, date_to=date_to, ) return success_response(result) @router.get("/qa-records/export") async def export_qa_records_jsonl( include_unrated: bool = Query(default=False), project_id: int | None = Query(default=None), rating: str | None = Query(default=None), domain: str | None = Query(default=None), date_from: datetime | None = Query(default=None), date_to: datetime | None = Query(default=None), current_user: User = Depends(require_roles("REVIEWER", "OWNER")), db: AsyncSession = Depends(get_db), tenant_id: int = Depends(get_tenant_id), ): _ = current_user query = QAExportQuery( include_unrated=include_unrated, project_id=project_id, rating=rating, domain=domain, date_from=date_from, date_to=date_to, ) service = AdaptiveRAGService(db, tenant_id=_resolve_tenant_id(current_user, tenant_id)) payload = await service.export_qa_jsonl( include_unrated=query.include_unrated, project_id=query.project_id, rating=query.rating, domain=query.domain, date_from=query.date_from, date_to=query.date_to, ) return Response(content=payload, media_type="application/x-ndjson")