Spaces:
Running
Running
progress tracking
Browse files- components/review_dashboard_page.py +54 -69
- data/models.py +1 -20
components/review_dashboard_page.py
CHANGED
@@ -12,11 +12,6 @@ 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 |
-
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,7 +302,7 @@ class ReviewDashboardPage:
|
|
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
|
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,28 +312,33 @@ class ReviewDashboardPage:
|
|
317 |
|
318 |
log.info(f"Found target annotator with ID: {target_annotator_obj.id}")
|
319 |
|
320 |
-
#
|
321 |
-
|
|
|
322 |
|
323 |
-
#
|
324 |
-
|
325 |
-
|
326 |
-
|
327 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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"
|
335 |
|
336 |
# Process items with minimal data - validation status will be loaded on-demand
|
337 |
items = []
|
338 |
-
for annotation in
|
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,8 +346,8 @@ class ReviewDashboardPage:
|
|
346 |
items.append({
|
347 |
"annotation_id": annotation.id,
|
348 |
"tts_id": annotation.tts_data_id,
|
349 |
-
"filename":
|
350 |
-
"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,28 +355,36 @@ class ReviewDashboardPage:
|
|
355 |
"validation_loaded": False # Track if validation status has been loaded
|
356 |
})
|
357 |
|
358 |
-
#
|
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** -
|
365 |
-
|
366 |
-
#
|
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,8 +394,6 @@ class ReviewDashboardPage:
|
|
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,9 +411,15 @@ class ReviewDashboardPage:
|
|
405 |
gr.update(value="β Reject") # Reset reject button
|
406 |
)
|
407 |
else:
|
408 |
-
#
|
409 |
-
|
410 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
411 |
|
412 |
def show_current_review_item_fn(items, idx, session):
|
413 |
if not items or idx >= len(items) or idx < 0:
|
@@ -537,33 +549,6 @@ class ReviewDashboardPage:
|
|
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
|
|
|
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 |
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 |
|
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 |
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 |
"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 |
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 |
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 |
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
|
data/models.py
CHANGED
@@ -158,23 +158,4 @@ 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
|
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])
|
|
|
158 |
validated_at = Column(DateTime, nullable=False)
|
159 |
|
160 |
annotation = relationship("Annotation")
|
161 |
+
validator = relationship("Annotator", foreign_keys=[validator_id]) # Fixed: should reference Annotator
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|