From 8ba4a6c43e31c24ef320d5c8032d43ac48b2342e Mon Sep 17 00:00:00 2001 From: huangzhuang_3rd Date: Wed, 12 Aug 2026 13:31:03 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20AI=20Worker=20=E5=B9=B3=E5=8F=B0=20MVP?= =?UTF-8?q?=20v1.0.0=20-=20=E5=A4=9A=E7=A7=9F=E6=88=B7/=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E7=AE=A1=E7=90=86/AI=20Worker/=E4=BB=BB=E5=8A=A1=E7=BC=96?= =?UTF-8?q?=E6=8E=92/HITL=E5=AE=A1=E6=A0=B8/=E6=88=90=E6=9C=AC=E6=B2=BB?= =?UTF-8?q?=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/services/review_service.py | 92 ++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 backend/app/services/review_service.py diff --git a/backend/app/services/review_service.py b/backend/app/services/review_service.py new file mode 100644 index 0000000..aa9d83b --- /dev/null +++ b/backend/app/services/review_service.py @@ -0,0 +1,92 @@ +"""Review service: HITL approval/rejection flow.""" +from sqlalchemy.orm import Session +from typing import List, Optional +from datetime import datetime +from app.models.review import Review +from app.models.task import Task +from app.schemas.review import ReviewCreate, ReviewDecision + + +class ReviewService: + @staticmethod + def list_reviews( + db: Session, + tenant_id: int, + status: Optional[str] = None, + project_id: Optional[int] = None, + skip: int = 0, + limit: int = 100, + ) -> List[Review]: + q = db.query(Review).filter(Review.tenant_id == tenant_id) + if status: + q = q.filter(Review.status == status) + if project_id: + q = q.filter(Review.project_id == project_id) + return q.order_by(Review.created_at.desc()).offset(skip).limit(limit).all() + + @staticmethod + def get_review(db: Session, tenant_id: int, review_id: int) -> Optional[Review]: + return ( + db.query(Review) + .filter(Review.id == review_id, Review.tenant_id == tenant_id) + .first() + ) + + @staticmethod + def create_review( + db: Session, tenant_id: int, req: ReviewCreate + ) -> Review: + review = Review( + tenant_id=tenant_id, + task_id=req.task_id, + project_id=req.project_id, + review_type=req.review_type, + status="pending", + review_content=req.review_content, + auto_check_result=req.auto_check_result, + submitted_at=datetime.utcnow(), + ) + db.add(review) + db.commit() + db.refresh(review) + return review + + @staticmethod + def make_decision( + db: Session, + tenant_id: int, + review_id: int, + reviewer_id: int, + reviewer_role: str, + req: ReviewDecision, + ) -> Review: + review = ReviewService.get_review(db, tenant_id, review_id) + if not review: + raise ValueError(f"Review #{review_id} not found") + if review.status != "pending": + raise ValueError(f"Review already {review.status}") + + review.status = req.status + review.reviewer_id = reviewer_id + review.reviewer_role = reviewer_role + review.reviewer_comment = req.comment + review.reviewed_at = datetime.utcnow() + + # Update task status + task = ( + db.query(Task) + .filter(Task.id == review.task_id, Task.tenant_id == tenant_id) + .first() + ) + if task: + if req.status == "approved": + task.status = "done" + task.completed_at = datetime.utcnow() + task.review_status = "approved" + else: + task.status = "rejected" + task.review_status = "rejected" + + db.commit() + db.refresh(review) + return review