Files
ai-contract-platform/backend/app/api/reviews.py
T
2026-05-03 12:28:38 +08:00

215 lines
7.3 KiB
Python

from typing import Optional
from urllib.parse import quote
from fastapi import APIRouter, Depends, File, Form, Header, HTTPException, Request, Response, UploadFile
from sqlalchemy.orm import Session
from app.core.response import success_response
from app.db import get_db_session
from app.services.audit_log_service import AuditLogService
from app.services.auth_service import AuthService
from app.services.review_service import ReviewService
router = APIRouter(prefix="/reviews", tags=["reviews"])
ROUTE_PERMISSION_SCOPE = "review_workflow"
ROUTE_PERMISSION_ACTIONS = {
"create": "reviews:create",
"list": "reviews:list",
"detail": "reviews:detail",
"result": "reviews:result",
"report": "reviews:report:get",
"download_report": "reviews:report:download",
"retry": "reviews:retry",
}
def get_review_service(db: Session = Depends(get_db_session)) -> ReviewService:
return ReviewService(db)
def get_audit_log_service(db: Session = Depends(get_db_session)) -> AuditLogService:
return AuditLogService(db)
def require_current_user(
authorization: Optional[str] = Header(default=None),
db: Session = Depends(get_db_session),
):
return AuthService(db).require_current_user(authorization)
@router.post("")
async def create_review(
request: Request,
file: Optional[UploadFile] = File(default=None),
text: Optional[str] = Form(default=None),
contract_type: Optional[str] = Form(default=None),
review_as: str = Form(default="party_a"),
review_service: ReviewService = Depends(get_review_service),
audit_log_service: AuditLogService = Depends(get_audit_log_service),
current_user: dict = Depends(require_current_user),
):
normalized_text = text.strip() if text else None
file_name = file.filename if file else None
file_text: Optional[str] = None
if file is not None:
review_service.validate_review_upload(file_name=file_name, mime_type=file.content_type)
file_bytes = await file.read()
file_text = review_service.extract_text_from_file(file_bytes=file_bytes, file_name=file_name)
combined_text = review_service.merge_review_text(text=normalized_text, file_text=file_text)
if file is None and not combined_text:
return {"code": 40001, "message": "file 和 text 至少提供一个", "data": None}
if file is not None and not file_text and not normalized_text:
return {
"code": 40002,
"message": "无法从上传文件中提取可审查文本,请改为可复制文本 PDF / DOCX / TXT,或直接粘贴合同正文。",
"data": None,
}
result = review_service.create_review_task(
tenant_id=current_user["tenant_id"],
user_id=current_user["id"],
review_as=review_as,
contract_type=contract_type,
text=combined_text,
file_name=file_name,
)
audit_log_service.record_event(
tenant_id=current_user.get("tenant_id"),
user_id=current_user.get("id"),
action="create_review",
resource_type="review",
resource_id=result["id"],
detail_json={
"contract_type": contract_type,
"review_as": review_as,
"source_type": "file" if file is not None else "text",
"file_name": file_name,
"text_length": len(combined_text or ""),
"status": result.get("status"),
},
ip_address=request.client.host if request.client else None,
user_agent=request.headers.get("user-agent"),
)
return success_response(result)
@router.get("")
def list_reviews(
review_service: ReviewService = Depends(get_review_service),
_current_user: dict = Depends(require_current_user),
):
return success_response(review_service.list_reviews(_current_user))
@router.get("/{review_id}")
def get_review_detail(
review_id: str,
review_service: ReviewService = Depends(get_review_service),
_current_user: dict = Depends(require_current_user),
):
detail = review_service.get_review_detail(_current_user, review_id)
if detail is None:
raise HTTPException(status_code=404, detail="review not found")
return success_response(detail)
@router.post("/{review_id}/retry")
def retry_review(
review_id: str,
request: Request,
review_service: ReviewService = Depends(get_review_service),
audit_log_service: AuditLogService = Depends(get_audit_log_service),
current_user: dict = Depends(require_current_user),
):
result = review_service.retry_review_task(current_user, review_id)
if result is None:
raise HTTPException(status_code=404, detail="review not found")
audit_log_service.record_event(
tenant_id=current_user.get("tenant_id"),
user_id=current_user.get("id"),
action="retry_review",
resource_type="review",
resource_id=result["id"],
detail_json={
"original_review_id": review_id,
"new_review_id": result["id"],
"status": result.get("status"),
"current_stage": result.get("current_stage"),
"next_action": result.get("next_action"),
},
ip_address=request.client.host if request.client else None,
user_agent=request.headers.get("user-agent"),
)
return success_response(result, message="审查任务已重新发起")
@router.get("/{review_id}/result")
def get_review_result(
review_id: str,
review_service: ReviewService = Depends(get_review_service),
_current_user: dict = Depends(require_current_user),
):
result = review_service.get_review_result(_current_user, review_id)
if result is None:
raise HTTPException(status_code=404, detail="review not found")
return success_response(result)
@router.get("/{review_id}/report")
def get_review_report(
review_id: str,
review_service: ReviewService = Depends(get_review_service),
_current_user: dict = Depends(require_current_user),
):
report = review_service.get_review_report(_current_user, review_id)
if report is None:
raise HTTPException(status_code=404, detail="review not found")
return success_response(report)
@router.get("/{review_id}/report/download")
def download_review_report(
review_id: str,
request: Request,
review_service: ReviewService = Depends(get_review_service),
audit_log_service: AuditLogService = Depends(get_audit_log_service),
current_user: dict = Depends(require_current_user),
):
report = review_service.get_review_report(current_user, review_id)
if report is None:
raise HTTPException(status_code=404, detail="review not found")
content = review_service.build_report_pdf(current_user, review_id)
if content is None:
raise HTTPException(status_code=404, detail="review not found")
audit_log_service.record_event(
tenant_id=current_user.get("tenant_id"),
user_id=current_user.get("id"),
action="download_report",
resource_type="review",
resource_id=review_id,
detail_json={
"file_name": report["file_name"],
"download_url": report["download_url"],
},
ip_address=request.client.host if request.client else None,
user_agent=request.headers.get("user-agent"),
)
headers = {"Content-Disposition": f"attachment; filename*=UTF-8''{quote(report['file_name'])}"}
return Response(content=content, media_type="application/pdf", headers=headers)