vargha commited on
Commit
b21ba3c
Β·
1 Parent(s): aad2e16

progress tracking

Browse files
Files changed (2) hide show
  1. components/review_dashboard_page.py +69 -54
  2. data/models.py +20 -1
components/review_dashboard_page.py CHANGED
@@ -12,6 +12,11 @@ from config import conf
12
  from utils.database import get_db
13
  from data.models import Annotation, TTSData, Annotator, Validation
14
  from data.repository.annotator_workload_repo import AnnotatorWorkloadRepo
 
 
 
 
 
15
 
16
  log = Logger()
17
  LOADER = CloudServerAudioLoader(conf.FTP_URL)
@@ -302,7 +307,7 @@ class ReviewDashboardPage:
302
  log.warning(f"No target annotator found for reviewer {username}")
303
  return [], 0, "", "", "", "", "", "", "", "", gr.update(value=None, autoplay=False), gr.update(visible=False, value=""), False, gr.update(value="❌ Reject")
304
 
305
- # Load annotations from target annotator with FAST INITIAL LOADING
306
  with get_db() as db:
307
  # Get target annotator's ID
308
  target_annotator_obj = db.query(Annotator).filter_by(name=target_annotator).first()
@@ -312,33 +317,28 @@ class ReviewDashboardPage:
312
 
313
  log.info(f"Found target annotator with ID: {target_annotator_obj.id}")
314
 
315
- # FAST INITIAL QUERY: Load only essential data without complex validation processing
316
- # Reduced batch size for instant loading in HuggingFace spaces
317
- INITIAL_BATCH_SIZE = 5 # Load only 5 items initially for instant response
318
 
319
- # Simple query to get basic annotation data quickly
320
- initial_query = db.query(
321
- Annotation,
322
- TTSData.filename,
323
- TTSData.sentence
324
- ).join(
325
- TTSData, Annotation.tts_data_id == TTSData.id
326
- ).filter(
327
- Annotation.annotator_id == target_annotator_obj.id
328
- ).order_by(Annotation.id).limit(INITIAL_BATCH_SIZE)
329
-
330
- initial_results = initial_query.all()
331
 
332
  # Get total count for progress info (this is fast)
333
  total_count = db.query(Annotation).filter(
334
  Annotation.annotator_id == target_annotator_obj.id
335
  ).count()
336
 
337
- log.info(f"Fast initial load: {len(initial_results)} annotations out of {total_count} total for target annotator ID {target_annotator_obj.id}")
338
 
339
  # Process items with minimal data - validation status will be loaded on-demand
340
  items = []
341
- for annotation, filename, sentence in initial_results:
 
 
 
342
  # Check if annotation is deleted (minimal processing)
343
  is_deleted = not annotation.annotated_sentence or annotation.annotated_sentence.strip() == ""
344
  annotated_sentence_display = "[DELETED ANNOTATION]" if is_deleted else annotation.annotated_sentence
@@ -346,8 +346,8 @@ class ReviewDashboardPage:
346
  items.append({
347
  "annotation_id": annotation.id,
348
  "tts_id": annotation.tts_data_id,
349
- "filename": filename,
350
- "sentence": sentence,
351
  "annotated_sentence": annotated_sentence_display,
352
  "is_deleted": is_deleted,
353
  "annotated_at": annotation.annotated_at.isoformat() if annotation.annotated_at else "",
@@ -355,36 +355,28 @@ class ReviewDashboardPage:
355
  "validation_loaded": False # Track if validation status has been loaded
356
  })
357
 
358
- # Find the first item that is not reviewed (prioritize non-deleted annotations)
359
  initial_idx = 0
360
- if items:
361
- found_unreviewed = False
362
- # First, try to find unreviewed non-deleted annotations
363
- for i, item_data in enumerate(items):
364
- if (item_data["validation_status"] == "Not Reviewed" and
365
- not item_data.get("is_deleted", False)):
366
- initial_idx = i
367
- found_unreviewed = True
368
- break
369
-
370
- # If no unreviewed non-deleted items, look for any unreviewed items
371
- if not found_unreviewed:
372
- for i, item_data in enumerate(items):
373
- if item_data["validation_status"].startswith("Not Reviewed"):
374
- initial_idx = i
375
- found_unreviewed = True
376
- break
377
-
378
- # If no unreviewed items at all, use the last item
379
- if not found_unreviewed:
380
- initial_idx = len(items) - 1 if items else 0
381
 
382
  # Set initial display
383
  if items:
384
  initial_item = items[initial_idx]
385
- review_info_text = f"πŸ” **Phase 2 Review Mode** - Reviewing assigned annotations. Loaded {len(items)} of {total_count} total items."
386
- # Ensure correct order of return values for 12 outputs
387
- # items, idx, review_info, tts_id, filename, sentence, ann_sentence, annotated_at, validation_status, annotator_placeholder, audio_update, rejection_reason_update
 
 
 
 
 
 
 
 
 
 
 
 
 
388
  rejection_reason_val = ""
389
  rejection_visible_val = False
390
  if initial_item["validation_status"].startswith("Rejected"):
@@ -394,6 +386,8 @@ class ReviewDashboardPage:
394
  rejection_reason_val = initial_item["validation_status"][start_paren+1:end_paren]
395
  rejection_visible_val = True
396
 
 
 
397
  return (
398
  items,
399
  initial_idx,
@@ -411,15 +405,9 @@ class ReviewDashboardPage:
411
  gr.update(value="❌ Reject") # Reset reject button
412
  )
413
  else:
414
- # Ensure correct order and number of return values for empty items (14 outputs)
415
- return [], 0, f"πŸ” **Phase 2 Review Mode** - No annotations found for review.", "", "", "", "", "", "", "", gr.update(value=None, autoplay=False), gr.update(visible=False, value=""), False, gr.update(value="❌ Reject")
416
-
417
- # except Exception as e:
418
- # log.error(f"Error loading review items: {e}")
419
- # sentry_sdk.capture_exception(e)
420
- # gr.Error(f"Failed to load review data: {e}")
421
- # # Ensure correct order and number of return values for error case (14 outputs)
422
- # return [], 0, "", "", "", "", "", "", "", "", gr.update(value=None, autoplay=False), gr.update(visible=False, value=""), False, gr.update(value="❌ Reject")
423
 
424
  def show_current_review_item_fn(items, idx, session):
425
  if not items or idx >= len(items) or idx < 0:
@@ -549,6 +537,33 @@ class ReviewDashboardPage:
549
  db.commit()
550
  log.info(f"Validation saved successfully for annotation_id: {annotation_id}")
551
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
552
  items[idx]["validation_status"] = "Approved" if approved else f"Rejected ({rejection_reason})" if rejection_reason else "Rejected"
553
 
554
  # Show rejection reason input only if rejected, otherwise hide and clear
 
12
  from utils.database import get_db
13
  from data.models import Annotation, TTSData, Annotator, Validation
14
  from data.repository.annotator_workload_repo import AnnotatorWorkloadRepo
15
+ from utils.user_progress import (
16
+ get_next_unreviewed_annotation,
17
+ update_user_progress,
18
+ get_annotations_from_position
19
+ )
20
 
21
  log = Logger()
22
  LOADER = CloudServerAudioLoader(conf.FTP_URL)
 
307
  log.warning(f"No target annotator found for reviewer {username}")
308
  return [], 0, "", "", "", "", "", "", "", "", gr.update(value=None, autoplay=False), gr.update(visible=False, value=""), False, gr.update(value="❌ Reject")
309
 
310
+ # Load annotations with PROGRESS TRACKING
311
  with get_db() as db:
312
  # Get target annotator's ID
313
  target_annotator_obj = db.query(Annotator).filter_by(name=target_annotator).first()
 
317
 
318
  log.info(f"Found target annotator with ID: {target_annotator_obj.id}")
319
 
320
+ # 🎯 PROGRESS TRACKING: Find next unreviewed annotation position
321
+ next_annotation_id, next_position = get_next_unreviewed_annotation(db, user_id, target_annotator_obj.id)
 
322
 
323
+ # Load batch size for responsive loading
324
+ INITIAL_BATCH_SIZE = 10 # Increased from 5 to 10 for better UX
325
+
326
+ # Load annotations starting from the next unreviewed position
327
+ annotations_data = get_annotations_from_position(db, target_annotator_obj.id, next_position, INITIAL_BATCH_SIZE)
 
 
 
 
 
 
 
328
 
329
  # Get total count for progress info (this is fast)
330
  total_count = db.query(Annotation).filter(
331
  Annotation.annotator_id == target_annotator_obj.id
332
  ).count()
333
 
334
+ log.info(f"Progress-aware load: Starting from position {next_position}, loaded {len(annotations_data)} annotations out of {total_count} total for target annotator ID {target_annotator_obj.id}")
335
 
336
  # Process items with minimal data - validation status will be loaded on-demand
337
  items = []
338
+ for annotation in annotations_data:
339
+ # Get TTS data
340
+ tts_data = annotation.tts_data
341
+
342
  # Check if annotation is deleted (minimal processing)
343
  is_deleted = not annotation.annotated_sentence or annotation.annotated_sentence.strip() == ""
344
  annotated_sentence_display = "[DELETED ANNOTATION]" if is_deleted else annotation.annotated_sentence
 
346
  items.append({
347
  "annotation_id": annotation.id,
348
  "tts_id": annotation.tts_data_id,
349
+ "filename": tts_data.filename,
350
+ "sentence": tts_data.sentence,
351
  "annotated_sentence": annotated_sentence_display,
352
  "is_deleted": is_deleted,
353
  "annotated_at": annotation.annotated_at.isoformat() if annotation.annotated_at else "",
 
355
  "validation_loaded": False # Track if validation status has been loaded
356
  })
357
 
358
+ # 🎯 PROGRESS TRACKING: Start from first item (index 0) since we loaded from the correct position
359
  initial_idx = 0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
360
 
361
  # Set initial display
362
  if items:
363
  initial_item = items[initial_idx]
364
+ review_info_text = f"πŸ” **Phase 2 Review Mode** - Continuing from position {next_position + 1}/{total_count}. Loaded {len(items)} items."
365
+
366
+ # Load validation status for the first item immediately
367
+ try:
368
+ annotation_obj = db.query(Annotation).filter_by(id=initial_item["annotation_id"]).first()
369
+ if annotation_obj:
370
+ validation_status, is_deleted = get_validation_status_for_item(db, initial_item["annotation_id"], user_id, annotation_obj)
371
+ initial_item["validation_status"] = validation_status
372
+ initial_item["is_deleted"] = is_deleted
373
+ initial_item["validation_loaded"] = True
374
+
375
+ if is_deleted:
376
+ initial_item["annotated_sentence"] = "[DELETED ANNOTATION]"
377
+ except Exception as e:
378
+ log.warning(f"Failed to load initial validation status: {e}")
379
+
380
  rejection_reason_val = ""
381
  rejection_visible_val = False
382
  if initial_item["validation_status"].startswith("Rejected"):
 
386
  rejection_reason_val = initial_item["validation_status"][start_paren+1:end_paren]
387
  rejection_visible_val = True
388
 
389
+ log.info(f"🎯 User {username} resuming review from position {next_position}, annotation ID {initial_item['annotation_id']}")
390
+
391
  return (
392
  items,
393
  initial_idx,
 
405
  gr.update(value="❌ Reject") # Reset reject button
406
  )
407
  else:
408
+ # All items have been reviewed
409
+ review_info_text = f"πŸŽ‰ **Review Complete!** - All {total_count} annotations have been reviewed for {target_annotator}."
410
+ return [], 0, review_info_text, "", "", "", "", "", "", "", gr.update(value=None, autoplay=False), gr.update(visible=False, value=""), False, gr.update(value="❌ Reject")
 
 
 
 
 
 
411
 
412
  def show_current_review_item_fn(items, idx, session):
413
  if not items or idx >= len(items) or idx < 0:
 
537
  db.commit()
538
  log.info(f"Validation saved successfully for annotation_id: {annotation_id}")
539
 
540
+ # 🎯 UPDATE USER PROGRESS TRACKING
541
+ try:
542
+ username = session.get("username")
543
+ if username:
544
+ # Find target annotator for this user
545
+ target_annotator = None
546
+ for annotator_name, reviewer_name in conf.REVIEW_MAPPING.items():
547
+ if reviewer_name == username:
548
+ target_annotator = annotator_name
549
+ break
550
+
551
+ if target_annotator:
552
+ target_annotator_obj = db.query(Annotator).filter_by(name=target_annotator).first()
553
+ if target_annotator_obj:
554
+ # Calculate the current position in the review list
555
+ current_position = db.query(Annotation).filter(
556
+ Annotation.annotator_id == target_annotator_obj.id,
557
+ Annotation.id <= annotation_id
558
+ ).count() - 1 # Convert to 0-based index
559
+
560
+ # Update user progress
561
+ update_user_progress(db, user_id, target_annotator_obj.id, annotation_id, current_position)
562
+ log.info(f"🎯 Updated progress for user {user_id}: annotation {annotation_id} at position {current_position}")
563
+ except Exception as e:
564
+ log.warning(f"Failed to update user progress: {e}")
565
+ # Don't fail the validation save if progress tracking fails
566
+
567
  items[idx]["validation_status"] = "Approved" if approved else f"Rejected ({rejection_reason})" if rejection_reason else "Rejected"
568
 
569
  # Show rejection reason input only if rejected, otherwise hide and clear
data/models.py CHANGED
@@ -158,4 +158,23 @@ class Validation(Base):
158
  validated_at = Column(DateTime, nullable=False)
159
 
160
  annotation = relationship("Annotation")
161
- validator = relationship("Annotator", foreign_keys=[validator_id]) # Fixed: should reference Annotator
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
  validated_at = Column(DateTime, nullable=False)
159
 
160
  annotation = relationship("Annotation")
161
+ validator = relationship("Annotator", foreign_keys=[validator_id]) # Fixed: should reference Annotator
162
+
163
+
164
+ # --------------------------------------------------------------------------- #
165
+ # UserProgress #
166
+ # --------------------------------------------------------------------------- #
167
+ class UserProgress(Base):
168
+ __tablename__ = "user_progress"
169
+
170
+ id = Column(Integer, primary_key=True)
171
+ user_id = Column(Integer, ForeignKey("annotators.id"), nullable=False)
172
+ target_annotator_id = Column(Integer, ForeignKey("annotators.id"), nullable=False)
173
+ last_reviewed_annotation_id = Column(Integer, ForeignKey("annotations.id"), nullable=True)
174
+ last_position = Column(Integer, default=0) # Position in the review list
175
+ updated_at = Column(DateTime, nullable=False)
176
+
177
+ # Relationships
178
+ user = relationship("Annotator", foreign_keys=[user_id])
179
+ target_annotator = relationship("Annotator", foreign_keys=[target_annotator_id])
180
+ last_reviewed_annotation = relationship("Annotation", foreign_keys=[last_reviewed_annotation_id])