From fe81df6ff678d784dd67ee64fa4f146d7a2bebce Mon Sep 17 00:00:00 2001 From: DianaDrzikova Date: Mon, 8 Jul 2024 21:35:06 +0200 Subject: [PATCH 1/2] Page for correction, updated model for correction column, loading results with their type --- backend/semant_annotation/db/crud_task.py | 18 ++++++++++++++++++ backend/semant_annotation/db/model.py | 1 + .../semant_annotation/routes/task_routes.py | 13 ++++++++++++- .../semant_annotation/schemas/base_objects.py | 3 +-- .../annotations/EditAnnotationTaskDialog.vue | 4 +++- frontend/src/models.ts | 2 +- frontend/src/pages/TaskPage.vue | 11 +++++++++-- frontend/src/router/routes.ts | 9 +++++++++ 8 files changed, 54 insertions(+), 7 deletions(-) diff --git a/backend/semant_annotation/db/crud_task.py b/backend/semant_annotation/db/crud_task.py index a2350f1..f1999c6 100644 --- a/backend/semant_annotation/db/crud_task.py +++ b/backend/semant_annotation/db/crud_task.py @@ -114,3 +114,21 @@ async def get_task_instance_result_times(db: AsyncSession, task_id: UUID = None, logging.error(str(e)) raise DBError(f'Failed fetching task instance result from database.') + +async def get_task_instance_annot_random(db: AsyncSession, annotation_task_instance_id: UUID) -> base_objects.AnnotationTaskResult: + time_delta = timedelta(minutes=10) + try: + async with db.begin(): + stmt = select(model.AnnotationTaskResult).where(model.AnnotationTaskResult.annotation_task_instance_id == annotation_task_instance_id) + + stmt = stmt.limit(1).with_for_update() + + result = await db.execute(stmt) + + db_task_instance_result = result.scalar_one_or_none() + + return base_objects.AnnotationTaskResult.model_validate(db_task_instance_result) + + except exc.SQLAlchemyError as e: + logging.error(str(e)) + raise DBError(f'Failed fetching task instance from database.') \ No newline at end of file diff --git a/backend/semant_annotation/db/model.py b/backend/semant_annotation/db/model.py index a27740c..5cd9fca 100644 --- a/backend/semant_annotation/db/model.py +++ b/backend/semant_annotation/db/model.py @@ -62,6 +62,7 @@ class AnnotationTask(Base): created_date: Mapped[datetime.datetime] = mapped_column(default=datetime.datetime.utcnow, index=True, nullable=False) last_change: Mapped[datetime.datetime] = mapped_column(default=datetime.datetime.utcnow, index=True, nullable=False) active: Mapped[bool] = mapped_column(default=False, nullable=False) + correction: Mapped[bool] = mapped_column(default=False, nullable=False) subtasks: Mapped[List['AnnotationSubtask']] = relationship( viewonly=True, lazy='joined') diff --git a/backend/semant_annotation/routes/task_routes.py b/backend/semant_annotation/routes/task_routes.py index 6cd79b9..ab1c2b2 100644 --- a/backend/semant_annotation/routes/task_routes.py +++ b/backend/semant_annotation/routes/task_routes.py @@ -56,6 +56,7 @@ async def delete_task(task_id: UUID, @task_route.post("/subtask", tags=["Task"]) async def new_subtask(subtask: base_objects.AnnotationSubtaskUpdate, user_token: TokenData = Depends(get_current_admin), db: AsyncSession = Depends(get_async_session)): + await crud_general.new(db, subtask, model.AnnotationSubtask) @@ -81,6 +82,17 @@ async def update_task_instance(task_instance: base_objects.AnnotationTaskInstanc async def get_task_instance(task_id: UUID, result_count_new: int, result_count_correction: int, user_token: TokenData = Depends(get_current_user), db: AsyncSession = Depends(get_async_session)): task = await crud_task.get_task_instance_random(db, task_id, result_count_new, result_count_correction) + + if task is None: + raise HTTPException(status_code=404, detail="No task instance available.") + return task + +@task_route.get("/task_instance_annot_random/{task_instance_id}", response_model=base_objects.AnnotationTaskResult, tags=["Task"]) +async def get_task_instance(task_instance_id: UUID, + user_token: TokenData = Depends(get_current_user), db: AsyncSession = Depends(get_async_session)): + + task = await crud_task.get_task_instance_annot_random(db, task_instance_id) + if task is None: raise HTTPException(status_code=404, detail="No task instance available.") return task @@ -172,4 +184,3 @@ async def upload_image(task_id: UUID, file: UploadFile, return {"message": f"Successfully uploaded {file.filename}"} - diff --git a/backend/semant_annotation/schemas/base_objects.py b/backend/semant_annotation/schemas/base_objects.py index a7e0ac8..c90353f 100644 --- a/backend/semant_annotation/schemas/base_objects.py +++ b/backend/semant_annotation/schemas/base_objects.py @@ -74,6 +74,7 @@ class AnnotationTaskUpdate(BaseModel): name: str description: str active: bool = False + correction: bool = False class AnnotationTask(AnnotationTaskUpdate): @@ -93,7 +94,6 @@ class AnnotationTaskInstanceUpdate(BaseModel): instance_metadata: str active: bool - class AnnotationTaskInstance(AnnotationTaskInstanceUpdate): created_date: datetime last_change: datetime @@ -153,4 +153,3 @@ class TimeTrackingItem(TimeTrackingItemNew): class Config: from_attributes = True - diff --git a/frontend/src/components/annotations/EditAnnotationTaskDialog.vue b/frontend/src/components/annotations/EditAnnotationTaskDialog.vue index b37d464..a3f5759 100644 --- a/frontend/src/components/annotations/EditAnnotationTaskDialog.vue +++ b/frontend/src/components/annotations/EditAnnotationTaskDialog.vue @@ -12,6 +12,7 @@ + @@ -113,7 +114,8 @@ async function onSubmit () { id: localTask.value.id, name: localTask.value.name, description: localTask.value.description, - active: localTask.value.active + active: localTask.value.active, + correction: localTask.value.correction } await api.put('/task/task', taskUpdate) successNotification(`Updated annotation task: ${localTask.value}.`) diff --git a/frontend/src/models.ts b/frontend/src/models.ts index 58b068a..1ea9726 100644 --- a/frontend/src/models.ts +++ b/frontend/src/models.ts @@ -44,6 +44,7 @@ export class AnnotationTaskUpdate { name = '' description = '' active = false + correction = false } export class AnnotationTask extends AnnotationTaskUpdate { @@ -119,4 +120,3 @@ export interface TextResponse { export type TextResponses = TextResponse[] export type SubtaskResponses = { [id: string] : TextResponses; } - diff --git a/frontend/src/pages/TaskPage.vue b/frontend/src/pages/TaskPage.vue index 78b0394..7f73f63 100644 --- a/frontend/src/pages/TaskPage.vue +++ b/frontend/src/pages/TaskPage.vue @@ -9,11 +9,13 @@
- +
{{ task.name }} - (INACTIVE) + (INACTIVE, CORRECTION) + (INACTIVE) + (CORRECTION)
@@ -33,6 +35,7 @@ + @@ -113,6 +116,10 @@ function annotate(task: AnnotationTask) { router.push('/annotation_tasks/' + task.id) } +function correct(task: AnnotationTask) { + router.push('/correction_tasks/' + task.id) +} + async function deleteTask(task: AnnotationTask) { deleteDialog.value = false const dismiss = actionNotification('Deleting task ' + task.name) diff --git a/frontend/src/router/routes.ts b/frontend/src/router/routes.ts index b0cfa2c..95b78b2 100644 --- a/frontend/src/router/routes.ts +++ b/frontend/src/router/routes.ts @@ -26,6 +26,15 @@ const routes: RouteRecordRaw[] = [ ], meta: { requiresAuth: true }, }, + { + name: 'correct_task', + path: '/correction_tasks/:task_id', + component: () => import('src/layouts/MainLayout.vue'), + children: [ + { path: '', component: () => import('src/pages/CorrectionAnnotationPage.vue') }, + ], + meta: { requiresAuth: true }, + }, { name: 'annotation_results', path: '/annotation_results/', From fa36e35439ff5ae165df3d312ab7269d3803f27c Mon Sep 17 00:00:00 2001 From: DianaDrzikova Date: Fri, 26 Jul 2024 16:08:24 +0200 Subject: [PATCH 2/2] Added correction option for annotators. DB sync with correction task. --- backend/semant_annotation/db/crud_general.py | 1 - backend/semant_annotation/db/crud_task.py | 20 +- .../semant_annotation/routes/task_routes.py | 2 +- .../annotations/CorrectionSubtaskResponse.vue | 100 +++++++++ frontend/src/pages/AnnotationPage.vue | 2 +- .../src/pages/CorrectionAnnotationPage.vue | 197 ++++++++++++++++++ frontend/src/pages/TaskPage.vue | 3 +- 7 files changed, 314 insertions(+), 11 deletions(-) create mode 100644 frontend/src/components/annotations/CorrectionSubtaskResponse.vue create mode 100644 frontend/src/pages/CorrectionAnnotationPage.vue diff --git a/backend/semant_annotation/db/crud_general.py b/backend/semant_annotation/db/crud_general.py index 5784c56..5a4c090 100644 --- a/backend/semant_annotation/db/crud_general.py +++ b/backend/semant_annotation/db/crud_general.py @@ -52,7 +52,6 @@ async def update_obj(db: AsyncSession, obj: model.Base, table: model.Base): data = obj.model_dump(exclude={'id'}) # add last_change with current timestamp data['last_change'] = datetime.datetime.utcnow() - stm = (update(table).where(table.id == obj.id).values(data)) await db.execute(stm) except exc.SQLAlchemyError as e: diff --git a/backend/semant_annotation/db/crud_task.py b/backend/semant_annotation/db/crud_task.py index f1999c6..337ad7a 100644 --- a/backend/semant_annotation/db/crud_task.py +++ b/backend/semant_annotation/db/crud_task.py @@ -25,7 +25,12 @@ async def store_task_instance_result(db, task_instance_result: base_objects.Anno # stmt = stmt.values({'result_count_correction': model.AnnotationTaskInstance.result_count_correction + 1}) # await db.execute(stmt) stmt = update(model.AnnotationTaskInstance).where(model.AnnotationTaskInstance.id == task_instance_result.annotation_task_instance_id) - stmt = stmt.values({'result_count_new': model.AnnotationTaskInstance.result_count_new + 1}) + + if(task_instance_result.result_type == base_objects.AnnotationResultType.NEW): + stmt = stmt.values({'result_count_new': model.AnnotationTaskInstance.result_count_new + 1}) + else: + stmt = stmt.values({'result_count_correction': model.AnnotationTaskInstance.result_count_correction + 1}) + await db.execute(stmt) db_task_instance_result = model.AnnotationTaskResult(**task_instance_result.model_dump()) @@ -37,6 +42,7 @@ async def store_task_instance_result(db, task_instance_result: base_objects.Anno def base_select_random_instance(task_id: UUID, result_count_new: int, result_count_correction: int, time_delta: timedelta, random_number: int, greater_or_equal: bool) -> select: stmt = select(model.AnnotationTaskInstance).filter(model.AnnotationTaskInstance.annotation_task_id == task_id) + if result_count_correction >= 0: stmt = stmt.filter(model.AnnotationTaskInstance.result_count_correction == result_count_correction) if result_count_new >= 0: @@ -47,6 +53,7 @@ def base_select_random_instance(task_id: UUID, result_count_new: int, result_cou stmt = stmt.filter(model.AnnotationTaskInstance.random_number >= random_number) else: stmt = stmt.filter(model.AnnotationTaskInstance.random_number < random_number) + return stmt.limit(1).with_for_update() @@ -62,6 +69,7 @@ async def get_task_instance_random(db: AsyncSession, task_id: UUID, result_count stmt = base_select_random_instance(task_id, result_count_new, result_count_correction, time_delta, random_number, False) result = await db.execute(stmt) db_task_instance = result.scalar_one_or_none() + if not db_task_instance: return None db_task_instance.last_send_send_to_user = datetime.now() @@ -119,16 +127,14 @@ async def get_task_instance_annot_random(db: AsyncSession, annotation_task_insta time_delta = timedelta(minutes=10) try: async with db.begin(): - stmt = select(model.AnnotationTaskResult).where(model.AnnotationTaskResult.annotation_task_instance_id == annotation_task_instance_id) - - stmt = stmt.limit(1).with_for_update() - + stmt = select(model.AnnotationTaskResult).where(model.AnnotationTaskResult.annotation_task_instance_id == annotation_task_instance_id).order_by(model.AnnotationTaskResult.last_change.desc()).limit(1) + stmt = stmt.limit(1)#.with_for_update() result = await db.execute(stmt) - db_task_instance_result = result.scalar_one_or_none() return base_objects.AnnotationTaskResult.model_validate(db_task_instance_result) except exc.SQLAlchemyError as e: logging.error(str(e)) - raise DBError(f'Failed fetching task instance from database.') \ No newline at end of file + raise DBError(f'Failed fetching task instance from database.') + \ No newline at end of file diff --git a/backend/semant_annotation/routes/task_routes.py b/backend/semant_annotation/routes/task_routes.py index ab1c2b2..053901d 100644 --- a/backend/semant_annotation/routes/task_routes.py +++ b/backend/semant_annotation/routes/task_routes.py @@ -82,7 +82,7 @@ async def update_task_instance(task_instance: base_objects.AnnotationTaskInstanc async def get_task_instance(task_id: UUID, result_count_new: int, result_count_correction: int, user_token: TokenData = Depends(get_current_user), db: AsyncSession = Depends(get_async_session)): task = await crud_task.get_task_instance_random(db, task_id, result_count_new, result_count_correction) - + if task is None: raise HTTPException(status_code=404, detail="No task instance available.") return task diff --git a/frontend/src/components/annotations/CorrectionSubtaskResponse.vue b/frontend/src/components/annotations/CorrectionSubtaskResponse.vue new file mode 100644 index 0000000..74e9730 --- /dev/null +++ b/frontend/src/components/annotations/CorrectionSubtaskResponse.vue @@ -0,0 +1,100 @@ + + + + diff --git a/frontend/src/pages/AnnotationPage.vue b/frontend/src/pages/AnnotationPage.vue index adb7bb6..2421b69 100644 --- a/frontend/src/pages/AnnotationPage.vue +++ b/frontend/src/pages/AnnotationPage.vue @@ -78,7 +78,7 @@ const textReponseCount = 10 const annotationTask = ref(null) const taskInstance = ref(null) -const subtaskResponsesRefs = ref([]) +const subtaskResponsesRefs = ref([]) let startTime = new Date().toISOString() diff --git a/frontend/src/pages/CorrectionAnnotationPage.vue b/frontend/src/pages/CorrectionAnnotationPage.vue new file mode 100644 index 0000000..7be9162 --- /dev/null +++ b/frontend/src/pages/CorrectionAnnotationPage.vue @@ -0,0 +1,197 @@ + + + + diff --git a/frontend/src/pages/TaskPage.vue b/frontend/src/pages/TaskPage.vue index 7f73f63..8bcba74 100644 --- a/frontend/src/pages/TaskPage.vue +++ b/frontend/src/pages/TaskPage.vue @@ -35,7 +35,8 @@ - +