vargha commited on
Commit
faa204e
·
1 Parent(s): 79f51f8

Resume Feature Implementation

Browse files
Files changed (1) hide show
  1. components/review_dashboard_page.py +166 -36
components/review_dashboard_page.py CHANGED
@@ -171,8 +171,13 @@ class ReviewDashboardPage:
171
  validation_status += f" ({validation.description})"
172
 
173
  # For deleted annotations, show special status
174
- if is_deleted and validation_status == "Not Reviewed":
175
- validation_status = "Not Reviewed (Deleted)"
 
 
 
 
 
176
 
177
  return validation_status, is_deleted
178
 
@@ -355,48 +360,93 @@ 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
-
363
- # Check database for validation status to find first unreviewed item
364
  for i, item_data in enumerate(items):
365
- # Check if this annotation has been validated by current user
366
- existing_validation = db.query(Validation).filter_by(
367
- annotation_id=item_data["annotation_id"],
368
- validator_id=user_id
369
- ).first()
370
-
371
- # If no validation exists, this is unreviewed
372
- if not existing_validation:
373
- # Prioritize non-deleted annotations
374
- if not item_data.get("is_deleted", False):
375
- initial_idx = i
376
- found_unreviewed = True
377
- log.info(f"Found first unreviewed non-deleted item at index {i} (annotation_id: {item_data['annotation_id']})")
378
- break
379
-
380
- # If no unreviewed non-deleted items found, look for any unreviewed items (including deleted)
381
- if not found_unreviewed:
382
- for i, item_data in enumerate(items):
383
- existing_validation = db.query(Validation).filter_by(
384
  annotation_id=item_data["annotation_id"],
385
  validator_id=user_id
386
  ).first()
387
 
388
- if not existing_validation:
 
389
  initial_idx = i
390
  found_unreviewed = True
391
- log.info(f"Found first unreviewed item at index {i} (annotation_id: {item_data['annotation_id']}) - may be deleted")
392
  break
 
 
 
 
393
 
394
- # If all items have been reviewed, continue from the end
 
395
  if not found_unreviewed:
396
- initial_idx = len(items) - 1 if items else 0
397
- log.info(f"All loaded items have been reviewed, starting from index {initial_idx}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
398
 
399
- log.info(f"Set initial review index to {initial_idx} out of {len(items)} loaded items")
400
 
401
  # Set initial display
402
  if items:
@@ -476,6 +526,7 @@ class ReviewDashboardPage:
476
  # Check if this is a deleted annotation
477
  is_deleted = current_item.get("is_deleted", False)
478
 
 
479
  if current_item["validation_status"].startswith("Rejected"):
480
  # Extract reason from status like "Rejected (reason)" or just use empty if no parenthesis
481
  start_paren = current_item["validation_status"].find("(")
@@ -483,6 +534,14 @@ class ReviewDashboardPage:
483
  if start_paren != -1 and end_paren != -1:
484
  rejection_reason = current_item["validation_status"][start_paren+1:end_paren]
485
  rejection_visible = True
 
 
 
 
 
 
 
 
486
 
487
  return (
488
  str(current_item["tts_id"]),
@@ -505,6 +564,68 @@ class ReviewDashboardPage:
505
  else:
506
  return f"���� **Phase 2 Review Mode** - No annotations found for review."
507
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
508
  def navigate_and_load_fn(items, current_idx, direction, session):
509
  """Combined navigation and loading function"""
510
  if not items:
@@ -703,6 +824,15 @@ class ReviewDashboardPage:
703
  # Combine with existing items
704
  all_items = items + new_items
705
  log.info(f"Loaded {len(new_items)} more items, total now: {len(all_items)}")
 
 
 
 
 
 
 
 
 
706
  return all_items, total_count
707
 
708
  # Output definitions
@@ -782,9 +912,9 @@ class ReviewDashboardPage:
782
  fn=lambda: gr.update(value="❌ Reject"), # Reset reject button
783
  outputs=[self.btn_reject]
784
  ).then(
785
- fn=lambda items, idx, session: navigate_and_load_fn(items, idx, "next", session),
786
  inputs=[self.items_state, self.idx_state, session_state],
787
- outputs=[self.items_state, self.idx_state, self.review_info]
788
  ).then(
789
  fn=show_current_review_item_fn,
790
  inputs=[self.items_state, self.idx_state, session_state],
@@ -805,9 +935,9 @@ class ReviewDashboardPage:
805
  inputs=[self.items_state, self.idx_state, session_state, self.rejection_mode_active],
806
  outputs=[self.header.progress_display]
807
  ).then(
808
- fn=lambda items, idx, session, rejection_mode: navigate_and_load_fn(items, idx, "next", session) if not rejection_mode else (items, idx, ""),
809
  inputs=[self.items_state, self.idx_state, session_state, self.rejection_mode_active],
810
- outputs=[self.items_state, self.idx_state, self.review_info]
811
  ).then(
812
  fn=lambda items, idx, session, rejection_mode: show_current_review_item_fn(items, idx, session) if not rejection_mode else (
813
  str(items[idx]["tts_id"]) if items and idx < len(items) else "",
@@ -833,9 +963,9 @@ class ReviewDashboardPage:
833
 
834
  # Skip button (just navigate to next)
835
  self.btn_skip.click(
836
- fn=lambda items, idx, session: navigate_and_load_fn(items, idx, "next", session),
837
  inputs=[self.items_state, self.idx_state, session_state],
838
- outputs=[self.items_state, self.idx_state, self.review_info]
839
  ).then(
840
  fn=show_current_review_item_fn,
841
  inputs=[self.items_state, self.idx_state, session_state],
 
171
  validation_status += f" ({validation.description})"
172
 
173
  # For deleted annotations, show special status
174
+ if is_deleted:
175
+ if validation_status == "Not Reviewed":
176
+ validation_status = "Not Reviewed (Deleted)"
177
+ elif validation_status == "Approved":
178
+ validation_status = "Approved (Deleted)"
179
+ elif validation_status.startswith("Rejected"):
180
+ validation_status = f"Rejected (Deleted{validation_status[8:] if validation_status[8:] else ''})"
181
 
182
  return validation_status, is_deleted
183
 
 
360
  "validation_loaded": False # Track if validation status has been loaded
361
  })
362
 
363
+ # --- RESUME LOGIC: Find first unreviewed item ---
364
  initial_idx = 0
365
  if items:
366
+ log.info(f"Starting resume logic search with {len(items)} items")
367
+ # First, try to find the first unreviewed item that is not deleted
368
  found_unreviewed = False
 
 
369
  for i, item_data in enumerate(items):
370
+ if not item_data.get("is_deleted", False):
371
+ # Check if this item has been reviewed by the current validator
372
+ validation = db.query(Validation).filter_by(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
373
  annotation_id=item_data["annotation_id"],
374
  validator_id=user_id
375
  ).first()
376
 
377
+ if not validation:
378
+ # This item hasn't been reviewed yet
379
  initial_idx = i
380
  found_unreviewed = True
381
+ log.info(f" Resume point found in initial batch: index {initial_idx}, TTS ID {item_data['tts_id']}, filename: {item_data['filename']}")
382
  break
383
+ else:
384
+ log.info(f"Item {i} (TTS ID {item_data['tts_id']}) already reviewed by validator {user_id}")
385
+ else:
386
+ log.info(f"Item {i} (TTS ID {item_data['tts_id']}) is deleted, skipping")
387
 
388
+ # If no unreviewed non-deleted items found in initial batch,
389
+ # we need to load more items to find the resume point
390
  if not found_unreviewed:
391
+ log.info("No unreviewed items found in initial batch, loading more items to find resume point")
392
+
393
+ # Load more items to find the resume point
394
+ offset = len(items)
395
+ additional_query = db.query(
396
+ Annotation,
397
+ TTSData.filename,
398
+ TTSData.sentence
399
+ ).join(
400
+ TTSData, Annotation.tts_data_id == TTSData.id
401
+ ).filter(
402
+ Annotation.annotator_id == target_annotator_obj.id
403
+ ).order_by(Annotation.id).offset(offset).limit(20) # Load more to find resume point
404
+
405
+ additional_results = additional_query.all()
406
+ log.info(f"Loaded {len(additional_results)} additional items to search for resume point")
407
+
408
+ # Process additional items
409
+ for annotation, filename, sentence in additional_results:
410
+ is_deleted = not annotation.annotated_sentence or annotation.annotated_sentence.strip() == ""
411
+ annotated_sentence_display = "[DELETED ANNOTATION]" if is_deleted else annotation.annotated_sentence
412
+
413
+ items.append({
414
+ "annotation_id": annotation.id,
415
+ "tts_id": annotation.tts_data_id,
416
+ "filename": filename,
417
+ "sentence": sentence,
418
+ "annotated_sentence": annotated_sentence_display,
419
+ "is_deleted": is_deleted,
420
+ "annotated_at": annotation.annotated_at.isoformat() if annotation.annotated_at else "",
421
+ "validation_status": "Loading...",
422
+ "validation_loaded": False
423
+ })
424
+
425
+ # Now search for the first unreviewed item in the expanded list
426
+ log.info(f"Searching expanded list of {len(items)} items for resume point")
427
+ for i, item_data in enumerate(items):
428
+ if not item_data.get("is_deleted", False):
429
+ validation = db.query(Validation).filter_by(
430
+ annotation_id=item_data["annotation_id"],
431
+ validator_id=user_id
432
+ ).first()
433
+
434
+ if not validation:
435
+ initial_idx = i
436
+ found_unreviewed = True
437
+ log.info(f"✅ Resume point found in expanded list: index {initial_idx}, TTS ID {item_data['tts_id']}, filename: {item_data['filename']}")
438
+ break
439
+ else:
440
+ log.info(f"Item {i} (TTS ID {item_data['tts_id']}) already reviewed by validator {user_id}")
441
+ else:
442
+ log.info(f"Item {i} (TTS ID {item_data['tts_id']}) is deleted, skipping")
443
+
444
+ # If still no unreviewed items found, start from the last item
445
+ if not found_unreviewed:
446
+ initial_idx = len(items) - 1 if items else 0
447
+ log.info(f"⚠️ All items reviewed, starting at last item, index: {initial_idx}")
448
 
449
+ log.info(f"Final resume decision: starting at index {initial_idx} with {len(items)} total items loaded")
450
 
451
  # Set initial display
452
  if items:
 
526
  # Check if this is a deleted annotation
527
  is_deleted = current_item.get("is_deleted", False)
528
 
529
+ # Handle validation status display
530
  if current_item["validation_status"].startswith("Rejected"):
531
  # Extract reason from status like "Rejected (reason)" or just use empty if no parenthesis
532
  start_paren = current_item["validation_status"].find("(")
 
534
  if start_paren != -1 and end_paren != -1:
535
  rejection_reason = current_item["validation_status"][start_paren+1:end_paren]
536
  rejection_visible = True
537
+ elif current_item["validation_status"] == "Not Reviewed (Deleted)":
538
+ # Special handling for deleted annotations that haven't been reviewed
539
+ rejection_visible = False
540
+ rejection_reason = ""
541
+ elif current_item["validation_status"] == "Not Reviewed":
542
+ # Regular unreviewed items
543
+ rejection_visible = False
544
+ rejection_reason = ""
545
 
546
  return (
547
  str(current_item["tts_id"]),
 
564
  else:
565
  return f"���� **Phase 2 Review Mode** - No annotations found for review."
566
 
567
+ def find_next_unreviewed_item(items, current_idx, session):
568
+ """Find the next unreviewed item starting from current_idx"""
569
+ if not items or current_idx >= len(items):
570
+ log.warning(f"find_next_unreviewed_item: Invalid items or current_idx. items: {len(items) if items else 0}, current_idx: {current_idx}")
571
+ return current_idx
572
+
573
+ user_id = session.get("user_id")
574
+ if not user_id:
575
+ log.warning("find_next_unreviewed_item: No user_id in session")
576
+ return current_idx
577
+
578
+ log.info(f"Searching for next unreviewed item starting from index {current_idx} (TTS ID: {items[current_idx]['tts_id'] if current_idx < len(items) else 'N/A'})")
579
+
580
+ # Start searching from the next item
581
+ start_idx = current_idx + 1
582
+ if start_idx >= len(items):
583
+ start_idx = 0 # Wrap around to beginning
584
+ log.info("Wrapping around to beginning of items list")
585
+
586
+ # Search forward from start_idx
587
+ for i in range(len(items)):
588
+ check_idx = (start_idx + i) % len(items)
589
+ item_data = items[check_idx]
590
+
591
+ log.info(f"Checking item {check_idx} (TTS ID: {item_data['tts_id']}, filename: {item_data['filename']})")
592
+
593
+ # Skip deleted annotations
594
+ if item_data.get("is_deleted", False):
595
+ log.info(f"Item {check_idx} is deleted, skipping")
596
+ continue
597
+
598
+ # Check if this item has been reviewed
599
+ if not item_data.get("validation_loaded", False):
600
+ # Load validation status on-demand
601
+ with get_db() as db:
602
+ try:
603
+ validation = db.query(Validation).filter_by(
604
+ annotation_id=item_data["annotation_id"],
605
+ validator_id=user_id
606
+ ).first()
607
+ item_data["validation_loaded"] = True
608
+ if validation:
609
+ item_data["validation_status"] = "Approved" if validation.validated else f"Rejected ({validation.description})" if validation.description else "Rejected"
610
+ log.info(f"Item {check_idx} validation status loaded: {item_data['validation_status']}")
611
+ else:
612
+ item_data["validation_status"] = "Not Reviewed"
613
+ log.info(f"Item {check_idx} has no validation record - unreviewed")
614
+ except Exception as e:
615
+ log.error(f"Error loading validation status for item {check_idx}: {e}")
616
+ item_data["validation_status"] = "Error loading status"
617
+
618
+ # If not reviewed, this is our target
619
+ if item_data["validation_status"] == "Not Reviewed":
620
+ log.info(f"✅ Found next unreviewed item at index {check_idx} (TTS ID: {item_data['tts_id']}, filename: {item_data['filename']})")
621
+ return check_idx
622
+ else:
623
+ log.info(f"Item {check_idx} already reviewed with status: {item_data['validation_status']}")
624
+
625
+ # If no unreviewed items found, return current index
626
+ log.info("⚠️ No unreviewed items found, staying at current position")
627
+ return current_idx
628
+
629
  def navigate_and_load_fn(items, current_idx, direction, session):
630
  """Combined navigation and loading function"""
631
  if not items:
 
824
  # Combine with existing items
825
  all_items = items + new_items
826
  log.info(f"Loaded {len(new_items)} more items, total now: {len(all_items)}")
827
+
828
+ # If this is the first time loading more items, we might need to find the resume point
829
+ # This ensures that even if the initial batch didn't find unreviewed items,
830
+ # we can still resume from the correct position
831
+ if len(items) == 5 and len(all_items) > 5: # Initial batch was 5 items
832
+ log.info("First load_more call, checking if we need to find resume point")
833
+ # The resume logic in load_review_items_fn should have already handled this,
834
+ # but we can add additional safety here if needed
835
+
836
  return all_items, total_count
837
 
838
  # Output definitions
 
912
  fn=lambda: gr.update(value="❌ Reject"), # Reset reject button
913
  outputs=[self.btn_reject]
914
  ).then(
915
+ fn=lambda items, idx, session: find_next_unreviewed_item(items, idx, session),
916
  inputs=[self.items_state, self.idx_state, session_state],
917
+ outputs=[self.idx_state]
918
  ).then(
919
  fn=show_current_review_item_fn,
920
  inputs=[self.items_state, self.idx_state, session_state],
 
935
  inputs=[self.items_state, self.idx_state, session_state, self.rejection_mode_active],
936
  outputs=[self.header.progress_display]
937
  ).then(
938
+ fn=lambda items, idx, session, rejection_mode: find_next_unreviewed_item(items, idx, session) if not rejection_mode else idx,
939
  inputs=[self.items_state, self.idx_state, session_state, self.rejection_mode_active],
940
+ outputs=[self.idx_state]
941
  ).then(
942
  fn=lambda items, idx, session, rejection_mode: show_current_review_item_fn(items, idx, session) if not rejection_mode else (
943
  str(items[idx]["tts_id"]) if items and idx < len(items) else "",
 
963
 
964
  # Skip button (just navigate to next)
965
  self.btn_skip.click(
966
+ fn=lambda items, idx, session: find_next_unreviewed_item(items, idx, session),
967
  inputs=[self.items_state, self.idx_state, session_state],
968
+ outputs=[self.idx_state]
969
  ).then(
970
  fn=show_current_review_item_fn,
971
  inputs=[self.items_state, self.idx_state, session_state],