""" User Progress Management Utilities Handles tracking and resuming review progress for Phase 2 reviewers """ from datetime import datetime from sqlalchemy.orm import Session from sqlalchemy import func, and_ from data.models import UserProgress, Annotation, Annotator, Validation from utils.logger import get_logger log = get_logger(__name__) def get_user_progress(db: Session, user_id: int, target_annotator_id: int) -> UserProgress: """Get or create a progress record for the user reviewing the target annotator""" progress = db.query(UserProgress).filter( and_( UserProgress.user_id == user_id, UserProgress.target_annotator_id == target_annotator_id ) ).first() if not progress: # Create new progress record progress = UserProgress( user_id=user_id, target_annotator_id=target_annotator_id, last_reviewed_annotation_id=None, last_position=0, updated_at=datetime.now() ) db.add(progress) db.commit() log.info(f"Created new progress record for user {user_id} reviewing annotator {target_annotator_id}") return progress def update_user_progress(db: Session, user_id: int, target_annotator_id: int, annotation_id: int, position: int): """Update user progress after they review an annotation""" progress = get_user_progress(db, user_id, target_annotator_id) progress.last_reviewed_annotation_id = annotation_id progress.last_position = position progress.updated_at = datetime.now() db.commit() log.info(f"Updated progress for user {user_id}: annotation {annotation_id} at position {position}") def get_next_unreviewed_annotation(db: Session, user_id: int, target_annotator_id: int): """Find the next annotation that needs to be reviewed by this user""" # Get all annotations for the target annotator that haven't been reviewed by this user unreviewed_query = db.query(Annotation).filter( Annotation.annotator_id == target_annotator_id ).outerjoin( Validation, and_( Validation.annotation_id == Annotation.id, Validation.validator_id == user_id ) ).filter( Validation.id.is_(None) # No validation record means not reviewed ).order_by(Annotation.id) first_unreviewed = unreviewed_query.first() if first_unreviewed: # Calculate the position of this annotation in the full list position = db.query(Annotation).filter( and_( Annotation.annotator_id == target_annotator_id, Annotation.id <= first_unreviewed.id ) ).count() - 1 # Convert to 0-based index return first_unreviewed.id, position else: # All annotations have been reviewed, return the last position total_count = db.query(Annotation).filter( Annotation.annotator_id == target_annotator_id ).count() return None, max(0, total_count - 1) def get_last_reviewed_position(db: Session, user_id: int, target_annotator_id: int) -> int: """Get the last position this user reviewed""" progress = get_user_progress(db, user_id, target_annotator_id) return progress.last_position def get_annotations_from_position(db: Session, target_annotator_id: int, start_position: int, batch_size: int = 10): """Load annotations starting from a specific position""" annotations_query = db.query( Annotation ).join( Annotation.tts_data ).filter( Annotation.annotator_id == target_annotator_id ).order_by(Annotation.id).offset(start_position).limit(batch_size) return annotations_query.all() def get_review_summary(db: Session, user_id: int, target_annotator_id: int): """Get a summary of review progress""" # Total annotations for target annotator total_count = db.query(Annotation).filter( Annotation.annotator_id == target_annotator_id ).count() # Reviewed count by this user reviewed_count = db.query(Annotation).join( Validation, Annotation.id == Validation.annotation_id ).filter( and_( Annotation.annotator_id == target_annotator_id, Validation.validator_id == user_id ) ).count() # Approved count approved_count = db.query(Annotation).join( Validation, Annotation.id == Validation.annotation_id ).filter( and_( Annotation.annotator_id == target_annotator_id, Validation.validator_id == user_id, Validation.validated == True ) ).count() # Rejected count rejected_count = reviewed_count - approved_count return { "total": total_count, "reviewed": reviewed_count, "approved": approved_count, "rejected": rejected_count, "remaining": total_count - reviewed_count, "progress_percentage": (reviewed_count / total_count * 100) if total_count > 0 else 0 }