Spaces:
Running
Running
Resume Feature Implementation
Browse files
components/review_dashboard_page.py
CHANGED
@@ -278,6 +278,42 @@ class ReviewDashboardPage:
|
|
278 |
log.error(f"Error calculating review progress for user {user_id}: {e}")
|
279 |
return f"⚠️ **Error calculating progress**"
|
280 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
281 |
def load_review_items_fn(session):
|
282 |
user_id = session.get("user_id")
|
283 |
username = session.get("username")
|
@@ -302,7 +338,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
|
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 +348,60 @@ class ReviewDashboardPage:
|
|
312 |
|
313 |
log.info(f"Found target annotator with ID: {target_annotator_obj.id}")
|
314 |
|
315 |
-
#
|
316 |
-
|
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
|
333 |
total_count = db.query(Annotation).filter(
|
334 |
Annotation.annotator_id == target_annotator_obj.id
|
335 |
).count()
|
336 |
|
337 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
338 |
|
339 |
# Process items with minimal data - validation status will be loaded on-demand
|
340 |
items = []
|
341 |
-
for annotation, filename, sentence in
|
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
|
@@ -355,29 +418,20 @@ class ReviewDashboardPage:
|
|
355 |
"validation_loaded": False # Track if validation status has been loaded
|
356 |
})
|
357 |
|
358 |
-
#
|
359 |
initial_idx = 0
|
360 |
if items:
|
361 |
-
|
362 |
-
|
363 |
-
|
364 |
-
|
365 |
-
|
366 |
-
|
367 |
-
|
368 |
-
|
369 |
-
|
370 |
-
|
371 |
-
|
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:
|
|
|
278 |
log.error(f"Error calculating review progress for user {user_id}: {e}")
|
279 |
return f"⚠️ **Error calculating progress**"
|
280 |
|
281 |
+
def find_first_unreviewed_annotation_index(db, target_annotator_obj, reviewer_user_id):
|
282 |
+
"""
|
283 |
+
Find the index (0-based) of the first annotation that hasn't been reviewed by the current reviewer.
|
284 |
+
Returns the global index within all annotations for the target annotator.
|
285 |
+
"""
|
286 |
+
try:
|
287 |
+
# Query to find the first annotation that doesn't have a validation record from this reviewer
|
288 |
+
# We use LEFT JOIN to include annotations without validations
|
289 |
+
first_unreviewed = db.query(Annotation).outerjoin(
|
290 |
+
Validation,
|
291 |
+
(Annotation.id == Validation.annotation_id) &
|
292 |
+
(Validation.validator_id == reviewer_user_id)
|
293 |
+
).filter(
|
294 |
+
Annotation.annotator_id == target_annotator_obj.id,
|
295 |
+
Validation.id.is_(None) # No validation record exists
|
296 |
+
).order_by(Annotation.id).first()
|
297 |
+
|
298 |
+
if first_unreviewed:
|
299 |
+
# Find the index of this annotation among all annotations for the target annotator
|
300 |
+
all_annotations = db.query(Annotation).filter(
|
301 |
+
Annotation.annotator_id == target_annotator_obj.id
|
302 |
+
).order_by(Annotation.id).all()
|
303 |
+
|
304 |
+
for idx, annotation in enumerate(all_annotations):
|
305 |
+
if annotation.id == first_unreviewed.id:
|
306 |
+
log.info(f"Found first unreviewed annotation at index {idx} (ID: {first_unreviewed.id})")
|
307 |
+
return idx
|
308 |
+
|
309 |
+
# If no unreviewed annotation found, return -1
|
310 |
+
log.info("No unreviewed annotations found")
|
311 |
+
return -1
|
312 |
+
|
313 |
+
except Exception as e:
|
314 |
+
log.error(f"Error finding first unreviewed annotation: {e}")
|
315 |
+
return -1
|
316 |
+
|
317 |
def load_review_items_fn(session):
|
318 |
user_id = session.get("user_id")
|
319 |
username = session.get("username")
|
|
|
338 |
log.warning(f"No target annotator found for reviewer {username}")
|
339 |
return [], 0, "", "", "", "", "", "", "", "", gr.update(value=None, autoplay=False), gr.update(visible=False, value=""), False, gr.update(value="❌ Reject")
|
340 |
|
341 |
+
# Load annotations from target annotator with RESUME LOGIC
|
342 |
with get_db() as db:
|
343 |
# Get target annotator's ID
|
344 |
target_annotator_obj = db.query(Annotator).filter_by(name=target_annotator).first()
|
|
|
348 |
|
349 |
log.info(f"Found target annotator with ID: {target_annotator_obj.id}")
|
350 |
|
351 |
+
# --- RESUME LOGIC: Find first unreviewed annotation ---
|
352 |
+
resume_index = find_first_unreviewed_annotation_index(db, target_annotator_obj, user_id)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
353 |
|
354 |
+
# Get total count for progress info
|
355 |
total_count = db.query(Annotation).filter(
|
356 |
Annotation.annotator_id == target_annotator_obj.id
|
357 |
).count()
|
358 |
|
359 |
+
# Calculate how many items to load initially to include the resume index
|
360 |
+
INITIAL_BATCH_SIZE = 5 # Minimum batch size
|
361 |
+
if resume_index >= 0:
|
362 |
+
# Load enough items to include the resume index, but at least INITIAL_BATCH_SIZE
|
363 |
+
items_to_load = max(INITIAL_BATCH_SIZE, resume_index + 1)
|
364 |
+
# Cap it to avoid loading too many items at once
|
365 |
+
items_to_load = min(items_to_load, 20)
|
366 |
+
else:
|
367 |
+
# No unreviewed items found, load standard batch from the end
|
368 |
+
items_to_load = INITIAL_BATCH_SIZE
|
369 |
+
|
370 |
+
# Load items based on resume logic
|
371 |
+
if resume_index >= 0:
|
372 |
+
# Load from the beginning to include the resume index
|
373 |
+
query = db.query(
|
374 |
+
Annotation,
|
375 |
+
TTSData.filename,
|
376 |
+
TTSData.sentence
|
377 |
+
).join(
|
378 |
+
TTSData, Annotation.tts_data_id == TTSData.id
|
379 |
+
).filter(
|
380 |
+
Annotation.annotator_id == target_annotator_obj.id
|
381 |
+
).order_by(Annotation.id).limit(items_to_load)
|
382 |
+
else:
|
383 |
+
# All items reviewed, load from the end
|
384 |
+
query = db.query(
|
385 |
+
Annotation,
|
386 |
+
TTSData.filename,
|
387 |
+
TTSData.sentence
|
388 |
+
).join(
|
389 |
+
TTSData, Annotation.tts_data_id == TTSData.id
|
390 |
+
).filter(
|
391 |
+
Annotation.annotator_id == target_annotator_obj.id
|
392 |
+
).order_by(Annotation.id.desc()).limit(items_to_load)
|
393 |
+
|
394 |
+
results = query.all()
|
395 |
+
|
396 |
+
# If we loaded from the end (all reviewed), reverse the results to maintain chronological order
|
397 |
+
if resume_index < 0:
|
398 |
+
results = list(reversed(results))
|
399 |
+
|
400 |
+
log.info(f"Loaded {len(results)} annotations out of {total_count} total for target annotator ID {target_annotator_obj.id}")
|
401 |
|
402 |
# Process items with minimal data - validation status will be loaded on-demand
|
403 |
items = []
|
404 |
+
for annotation, filename, sentence in results:
|
405 |
# Check if annotation is deleted (minimal processing)
|
406 |
is_deleted = not annotation.annotated_sentence or annotation.annotated_sentence.strip() == ""
|
407 |
annotated_sentence_display = "[DELETED ANNOTATION]" if is_deleted else annotation.annotated_sentence
|
|
|
418 |
"validation_loaded": False # Track if validation status has been loaded
|
419 |
})
|
420 |
|
421 |
+
# --- Calculate initial index based on resume logic ---
|
422 |
initial_idx = 0
|
423 |
if items:
|
424 |
+
if resume_index >= 0 and resume_index < len(items):
|
425 |
+
# Resume from the first unreviewed item
|
426 |
+
initial_idx = resume_index
|
427 |
+
log.info(f"User '{username}' resuming at first unreviewed item, index: {initial_idx} (annotation ID: {items[initial_idx]['annotation_id']})")
|
428 |
+
else:
|
429 |
+
# All items reviewed or no unreviewed items in current batch, start from the last item
|
430 |
+
initial_idx = len(items) - 1 if items else 0
|
431 |
+
if items:
|
432 |
+
log.info(f"User '{username}' has all loaded items reviewed, starting at last item index: {initial_idx} (annotation ID: {items[initial_idx]['annotation_id']})")
|
433 |
+
else:
|
434 |
+
log.info(f"User '{username}' has no items assigned, starting at index 0.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
435 |
|
436 |
# Set initial display
|
437 |
if items:
|