Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Loading results with result type #12

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion backend/semant_annotation/db/crud_general.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
26 changes: 25 additions & 1 deletion backend/semant_annotation/db/crud_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand All @@ -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:
Expand All @@ -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()


Expand All @@ -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()
Expand Down Expand Up @@ -114,3 +122,19 @@ 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).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.')

1 change: 1 addition & 0 deletions backend/semant_annotation/db/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')

Expand Down
13 changes: 12 additions & 1 deletion backend/semant_annotation/routes/task_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)


Expand All @@ -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
Expand Down Expand Up @@ -172,4 +184,3 @@ async def upload_image(task_id: UUID, file: UploadFile,

return {"message": f"Successfully uploaded {file.filename}"}


3 changes: 1 addition & 2 deletions backend/semant_annotation/schemas/base_objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ class AnnotationTaskUpdate(BaseModel):
name: str
description: str
active: bool = False
correction: bool = False


class AnnotationTask(AnnotationTaskUpdate):
Expand All @@ -93,7 +94,6 @@ class AnnotationTaskInstanceUpdate(BaseModel):
instance_metadata: str
active: bool


class AnnotationTaskInstance(AnnotationTaskInstanceUpdate):
created_date: datetime
last_change: datetime
Expand Down Expand Up @@ -153,4 +153,3 @@ class TimeTrackingItem(TimeTrackingItemNew):

class Config:
from_attributes = True

100 changes: 100 additions & 0 deletions frontend/src/components/annotations/CorrectionSubtaskResponse.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
<template>
<q-card class="q-pa-md" style="max-width: 350px; width: 100%;">
<q-card-section>
<q-expansion-item
v-model="expanded"
:label="subtask.name"
:expand-separator="true"
:default-open="true"
expand-icon="info">
<!-- show descriptin -->
Description: <span v-html="subtask.description" />
</q-expansion-item>
</q-card-section>
<q-card-section>
<!-- addResponseChange tracks changes (content and timestamp)-->
<q-input v-for="(item, index) in textResponses" :key="index"
dense
v-model="item.text" @update:model-value="update(item, $event)" />
<q-btn label="Add more" @click="textResponses.push({ history: [], timestamp: new Date().toISOString(), text: '' })"
icon="add"
class="full-width" />
</q-card-section>
</q-card>
</template>


<script setup lang="ts">

import { defineComponent, defineProps, ref, onMounted, defineEmits, watch, defineExpose } from 'vue'
import { AnnotationSubtask, TextResponses, TextResponse, AnnotationTaskResult } from 'src/models'

const initialTextReponseCount = 10
const expanded = ref(false)

const textResponses = ref<TextResponses>([])

const emit = defineEmits(['responseUpdate'])

watch(textResponses, (newValue) => {
emit('responseUpdate', newValue)
})

function update (item: TextResponse, event: any) {
item.history.push({ timestamp: new Date().toISOString(), text: event })
item.timestamp = new Date().toISOString()
item.text = event
}

interface Props {
subtask: AnnotationSubtask,
instanceResults: AnnotationTaskResult
}

const props = defineProps<Props>()

defineComponent({
name: 'SubtaskResponse'
})

const subtask = props.subtask
const instanceResults = props.instanceResults

onMounted(async () => {
// initialize results as an empty array of TextResponse
const results: TextResponse[] = []

// load results based on result for task instance for each subtask
const subResults = JSON.parse(instanceResults.result)
for (const [key, value] of Object.entries(subResults)) {

// store only current subtask
if (key === subtask.id) {
if (Array.isArray(value)) {
value.forEach((item) => {
if (typeof item === 'object' && item !== null) {
results.push({
history: Array.isArray(item.history) ? item.history : [],
timestamp: item.timestamp || new Date().toISOString(),
text: item.text || ''
})
}
})
}
break
}
}

// show annotation results for each subtask
textResponses.value = []
for (let i = 0; i < initialTextReponseCount; i++) {
textResponses.value.push({
history: results[i]?.history || [],
timestamp: results[i]?.timestamp || new Date().toISOString(),
text: results[i]?.text || ''
})
}
})


</script>
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
<!-- type can be -->
<q-editor class="q-mt-md" v-model="localTask.description" :toolbar="editorToolbarOptions" :fonts="editorFonts" />
<q-toggle v-model="localTask.active" label="Is active" />
<q-toggle v-model="localTask.correction" label="Is corrected" />

<q-btn unelevated color="primary" size="lg" class="full-width q-mt-md" label="Update task" type="submit"
:disable="disable" />
Expand Down Expand Up @@ -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}.`)
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export class AnnotationTaskUpdate {
name = ''
description = ''
active = false
correction = false
}

export class AnnotationTask extends AnnotationTaskUpdate {
Expand Down Expand Up @@ -119,4 +120,3 @@ export interface TextResponse {
export type TextResponses = TextResponse[]

export type SubtaskResponses = { [id: string] : TextResponses; }

2 changes: 1 addition & 1 deletion frontend/src/pages/AnnotationPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ const textReponseCount = 10
const annotationTask = ref<AnnotationTask | null>(null)
const taskInstance = ref<AnnotationTaskInstance | null>(null)

const subtaskResponsesRefs = ref<SubtaskResponse[]>([])
const subtaskResponsesRefs = ref<SubtaskResponses[]>([])

let startTime = new Date().toISOString()

Expand Down
Loading