Skip to content

Commit 21afa4b

Browse files
Merge pull request #300 from AI4Bharat/Pref_anno
minor fix in unassigned review task
2 parents f60cac4 + 043b354 commit 21afa4b

3 files changed

Lines changed: 118 additions & 20 deletions

File tree

backend/projects/views.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,55 @@ def batch(iterable, n=1):
114114
yield iterable[ndx : min(ndx + n, l)]
115115

116116

117+
def auto_update_reviewer_preferred_annotators(project, new_annotator_ids, requesting_user=None):
118+
"""
119+
After an annotator is added (or un-frozen) in a project, newly added annotators
120+
are added to the preferred annotators list of all annotation reviewers if all were selected,
121+
otherwise not preferred for logged in user as well as reviewers
122+
"""
123+
project_id_str = str(project.id)
124+
new_ids_set = set(new_annotator_ids)
125+
126+
frozen_user_ids = set(project.frozen_users.values_list("id", flat=True))
127+
all_active_ids = set(
128+
project.annotators.exclude(id__in=frozen_user_ids).values_list("id", flat=True)
129+
)
130+
previous_annotator_ids = all_active_ids - new_ids_set
131+
132+
users_to_update = set(project.annotation_reviewers.all())
133+
if requesting_user:
134+
users_to_update.add(requesting_user)
135+
136+
for user in users_to_update:
137+
prefs = user.preferred_task_by_json or {}
138+
if not isinstance(prefs, dict):
139+
prefs = {}
140+
preferred_annotators = prefs.get("preferred_annotators", {})
141+
if not isinstance(preferred_annotators, dict):
142+
preferred_annotators = {}
143+
144+
saved_ids = set(int(x) for x in preferred_annotators.get(project_id_str, []))
145+
previous_ids_int = set(int(x) for x in previous_annotator_ids)
146+
147+
if len(saved_ids) == 0:
148+
continue
149+
150+
saved_ids_clean = saved_ids - set(int(x) for x in new_ids_set)
151+
saved_active_ids = saved_ids_clean.intersection(previous_ids_int)
152+
was_all_selected = (saved_active_ids == previous_ids_int)
153+
154+
if was_all_selected:
155+
final_saved_ids = saved_ids_clean | set(int(x) for x in new_ids_set)
156+
else:
157+
final_saved_ids = saved_ids_clean
158+
159+
if final_saved_ids != saved_ids:
160+
preferred_annotators[project_id_str] = list(final_saved_ids)
161+
prefs["preferred_annotators"] = preferred_annotators
162+
user.preferred_task_by_json = prefs
163+
user.save(update_fields=["preferred_task_by_json"])
164+
165+
117166
def get_review_reports(proj_id, userid, start_date, end_date):
118167
user = User.objects.get(id=userid)
119168
userName = user.username
@@ -1549,6 +1598,8 @@ def remove_frozen_user(self, request, pk=None):
15491598
user = User.objects.get(pk=user_id)
15501599
project.frozen_users.remove(user)
15511600
project.save()
1601+
auto_update_reviewer_preferred_annotators(project, [user.id], requesting_user=request.user)
1602+
15521603
return Response(
15531604
{"message": "Frozen User removed from the project"},
15541605
status=status.HTTP_200_OK,
@@ -3848,6 +3899,8 @@ def add_project_annotators(self, request, pk=None, *args, **kwargs):
38483899
project.annotators.add(annotator)
38493900
project.save()
38503901

3902+
auto_update_reviewer_preferred_annotators(project, [annotator.id], requesting_user=request.user)
3903+
38513904
# Creating Notification
38523905
title = f"{project.title}:{project.id} New annotators have been added to the project"
38533906
notification_type = "add_member"
@@ -3912,6 +3965,30 @@ def add_project_reviewers(self, request, pk, *args, **kwargs):
39123965

39133966
project.annotation_reviewers.add(user)
39143967
project.save()
3968+
project_id_str = str(project.id)
3969+
frozen_user_ids = set(project.frozen_users.values_list("id", flat=True))
3970+
all_active_annotator_ids = set(
3971+
project.annotators.exclude(id__in=frozen_user_ids).values_list("id", flat=True)
3972+
)
3973+
3974+
users_to_update = {user}
3975+
3976+
for u in users_to_update:
3977+
prefs = u.preferred_task_by_json or {}
3978+
if not isinstance(prefs, dict):
3979+
prefs = {}
3980+
preferred_annotators = prefs.get("preferred_annotators", {})
3981+
if not isinstance(preferred_annotators, dict):
3982+
preferred_annotators = {}
3983+
3984+
saved_ids = set(int(x) for x in preferred_annotators.get(project_id_str, []))
3985+
saved_ids.update(all_active_annotator_ids)
3986+
3987+
preferred_annotators[project_id_str] = list(saved_ids)
3988+
prefs["preferred_annotators"] = preferred_annotators
3989+
u.preferred_task_by_json = prefs
3990+
u.save(update_fields=["preferred_task_by_json"])
3991+
39153992
# Creating Notification
39163993
title = f"{project.title}:{project.id} New reviewers have been added to project"
39173994
notification_type = "add_member"

backend/tasks/views.py

Lines changed: 40 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,8 @@ class TaskViewSet(viewsets.ModelViewSet, mixins.ListModelMixin):
112112
)
113113
def unassigned_review_summary(self, request):
114114
"""
115-
Returns a summary of unassigned review tasks grouped by annotator,
116-
including the list of unassigned task IDs.
115+
Returns a summary of unassigned review tasks grouped by annotator.
116+
Shows ALL annotators in the project, even those with 0 unassigned tasks.
117117
"""
118118
project_id = request.query_params.get("project_id")
119119
if not project_id:
@@ -122,40 +122,60 @@ def unassigned_review_summary(self, request):
122122
status=status.HTTP_400_BAD_REQUEST,
123123
)
124124

125-
cur_user = request.user # current logged-in user
125+
try:
126+
project = Project.objects.get(pk=project_id)
127+
except Project.DoesNotExist:
128+
return Response(
129+
{"error": "Project not found"},
130+
status=status.HTTP_404_NOT_FOUND,
131+
)
132+
133+
cur_user = request.user
126134

127-
tasks = (
135+
frozen_user_ids = project.frozen_users.values_list("id", flat=True)
136+
all_annotators = project.annotators.exclude(id__in=frozen_user_ids)
137+
138+
unassigned_tasks = (
128139
Task.objects.filter(project_id=project_id)
129140
.filter(task_status=ANNOTATED)
130141
.filter(review_user__isnull=True)
131142
.exclude(annotation_users=cur_user.id)
132143
.distinct()
133144
)
134145

135-
data = (
136-
tasks.values(
137-
"annotations__completed_by__id",
138-
"annotations__completed_by__email",
139-
"annotations__completed_by__username"
146+
from tasks.models import Annotation as Annotation_model, ANNOTATOR_ANNOTATION
147+
annotator_task_counts = (
148+
Annotation_model.objects.filter(
149+
task__in=unassigned_tasks,
150+
annotation_type=ANNOTATOR_ANNOTATION,
151+
completed_by__in=all_annotators,
140152
)
153+
.values("completed_by__id")
141154
.annotate(
142-
unassigned_count=Count("id"),
143-
task_ids=ArrayAgg("id", distinct=True),
155+
unassigned_count=Count("task", distinct=True),
156+
task_ids=ArrayAgg("task_id", distinct=True),
144157
)
145-
.order_by("-unassigned_count")
146158
)
147159

148-
result = [
149-
{
150-
"annotator_id": item["annotations__completed_by__id"],
151-
"annotator_username": item["annotations__completed_by__username"],
152-
"annotator_email": item["annotations__completed_by__email"],
160+
count_lookup = {}
161+
for item in annotator_task_counts:
162+
count_lookup[item["completed_by__id"]] = {
153163
"unassigned_count": item["unassigned_count"],
154164
"task_ids": item["task_ids"],
155165
}
156-
for item in data
157-
if item["annotations__completed_by__id"] is not None
158-
]
166+
167+
result = []
168+
for annotator in all_annotators:
169+
counts = count_lookup.get(annotator.id, {"unassigned_count": 0, "task_ids": []})
170+
result.append({
171+
"annotator_id": annotator.id,
172+
"annotator_email": annotator.email,
173+
"annotator_username": annotator.username,
174+
"unassigned_count": counts["unassigned_count"],
175+
"task_ids": counts["task_ids"],
176+
})
177+
178+
result.sort(key=lambda x: x["unassigned_count"], reverse=True)
159179

160180
return Response(result, status=status.HTTP_200_OK)
161181

backend/users/serializers.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ class Meta:
120120
"participation_type",
121121
"prefer_cl_ui",
122122
"is_active",
123+
"preferred_task_by_json",
123124
]
124125
read_only_fields = [
125126
"id",

0 commit comments

Comments
 (0)