diff --git a/pdm.lock b/pdm.lock index 808eab3..d379588 100644 --- a/pdm.lock +++ b/pdm.lock @@ -524,7 +524,7 @@ files = [ [[package]] name = "hypothesis" -version = "6.80.1" +version = "6.84.3" requires_python = ">=3.8" summary = "A library for property-based testing" dependencies = [ @@ -533,8 +533,8 @@ dependencies = [ "sortedcontainers<3.0.0,>=2.1.0", ] files = [ - {file = "hypothesis-6.80.1-py3-none-any.whl", hash = "sha256:a9b889f60e5804e3bba708dee471e3c4b190c6387d9cb74c7472b4a7d32377fb"}, - {file = "hypothesis-6.80.1.tar.gz", hash = "sha256:dbc8ab5881224bf85b242fa08488faf8693b2d2b92ef2af9b21f43e0124365a1"}, + {file = "hypothesis-6.84.3-py3-none-any.whl", hash = "sha256:4dd7de7a341a80c10d3e6beca12c084aab84c48ea270c1b9b8cee7e4aa5d7be2"}, + {file = "hypothesis-6.84.3.tar.gz", hash = "sha256:b4117f4138e81986cf62ad4e1410a021adeaa52e4b0326419da626cd7d3b6250"}, ] [[package]] diff --git a/src/soundevent/data/__init__.py b/src/soundevent/data/__init__.py index 5fed47b..9f17822 100644 --- a/src/soundevent/data/__init__.py +++ b/src/soundevent/data/__init__.py @@ -31,7 +31,11 @@ """ from soundevent.data.annotation_projects import AnnotationProject -from soundevent.data.annotation_tasks import AnnotationTask +from soundevent.data.annotation_tasks import ( + AnnotationTask, + StatusBadge, + TaskState, +) from soundevent.data.annotations import Annotation from soundevent.data.clips import Clip from soundevent.data.dataset import Dataset @@ -90,7 +94,9 @@ "Recording", "Sequence", "SoundEvent", + "StatusBadge", "Tag", + "TaskState", "Time", "TimeInterval", "TimeStamp", diff --git a/src/soundevent/data/annotation_tasks.py b/src/soundevent/data/annotation_tasks.py index 7bf3992..0ac2a8d 100644 --- a/src/soundevent/data/annotation_tasks.py +++ b/src/soundevent/data/annotation_tasks.py @@ -48,6 +48,7 @@ """ import datetime +from enum import Enum from typing import List, Optional from uuid import UUID, uuid4 @@ -59,6 +60,37 @@ from soundevent.data.tags import Tag +class TaskState(Enum): + """Task state.""" + + assigned = "assigned" + """Task has been assigned to an annotator.""" + + completed = "completed" + """Task has been completed by an annotator.""" + + verified = "verified" + """Task has been verified by a reviewer.""" + + rejected = "rejected" + """Task has been rejected by a reviewer.""" + + +class StatusBadge(BaseModel): + """Annotation Status Badge.""" + + state: TaskState + """State of the annotation task.""" + + user: Optional[str] = None + """User who is responsible for this status badge.""" + + created_at: datetime.datetime = Field( + default_factory=datetime.datetime.now + ) + """Date and time when the status badge was created.""" + + class AnnotationTask(BaseModel): """Annotation task.""" @@ -71,20 +103,14 @@ class AnnotationTask(BaseModel): annotations: List[Annotation] = Field(default_factory=list) """List of annotations in the created during the annotation task.""" - completed_by: Optional[str] = None - """The user who completed the annotation task.""" - - completed_on: Optional[datetime.datetime] = None - """The date and time when the annotation task was completed.""" - notes: List[Note] = Field(default_factory=list) """Notes associated with the annotation task.""" tags: List[Tag] = Field(default_factory=list) """User provided tags to the annotated clip.""" - completed: bool = False - """Whether the annotation task has been completed.""" + status_badges: List[StatusBadge] = Field(default_factory=list) + """Status badges for the annotation task.""" def __hash__(self): """Compute the hash value for the annotation task.""" diff --git a/src/soundevent/data/geometries.py b/src/soundevent/data/geometries.py index fcd3637..2300956 100644 --- a/src/soundevent/data/geometries.py +++ b/src/soundevent/data/geometries.py @@ -126,13 +126,13 @@ class BaseGeometry(BaseModel, ABC): type: GeometryType = Field( description="the type of geometry used to locate the sound event.", frozen=True, - include=True, + exclude=False, ) coordinates: Union[float, List] = Field( description="the coordinates of the geometry.", frozen=True, - include=True, + exclude=False, ) _geom: ShapelyBaseGeometry = PrivateAttr() diff --git a/src/soundevent/io/formats/aoef.py b/src/soundevent/io/formats/aoef.py index e53f0be..12f19a5 100644 --- a/src/soundevent/io/formats/aoef.py +++ b/src/soundevent/io/formats/aoef.py @@ -519,11 +519,7 @@ class AnnotationTaskObject(BaseModel): annotations: Optional[List[int]] = None - completed_by: Optional[str] = None - - completed_on: Optional[datetime.datetime] = None - - completed: bool + status_badges: Optional[List[data.StatusBadge]] = None notes: Optional[List[data.Note]] = None @@ -570,11 +566,9 @@ def from_annotation_task( audio_dir=audio_dir, ).id, annotations=annotation_ids if annotation_ids else None, - completed_by=task.completed_by, - completed_on=task.completed_on, - completed=task.completed, notes=task.notes if task.notes else None, tags=tag_ids if tag_ids else None, + status_badges=task.status_badges if task.status_badges else None, ) return tasks[task] @@ -595,9 +589,7 @@ def to_annotation_task( annotations[annotation_id] for annotation_id in (self.annotations or []) ], - completed_by=self.completed_by, - completed_on=self.completed_on, - completed=self.completed, + status_badges=self.status_badges if self.status_badges else [], notes=self.notes if self.notes else [], tags=[tags[tag_id] for tag_id in (self.tags or [])], ) diff --git a/tests/test_io/test_annotation_projects.py b/tests/test_io/test_annotation_projects.py index 904bdd3..fd2495b 100644 --- a/tests/test_io/test_annotation_projects.py +++ b/tests/test_io/test_annotation_projects.py @@ -173,7 +173,16 @@ def test_can_recover_task_status( # Arrange annotation_project = data.AnnotationProject( name="test_project", - tasks=[data.AnnotationTask(clip=clip, completed=True)], + tasks=[ + data.AnnotationTask( + clip=clip, + status_badges=[ + data.StatusBadge( + state=data.TaskState.completed, + ) + ], + ) + ], ) path = tmp_path / "test_project.json" @@ -183,7 +192,9 @@ def test_can_recover_task_status( # Assert assert recovered == annotation_project - assert recovered.tasks[0].completed + assert ( + recovered.tasks[0].status_badges[0].state == data.TaskState.completed + ) def test_can_recover_user_that_completed_task(tmp_path: Path, clip: data.Clip): @@ -194,8 +205,12 @@ def test_can_recover_user_that_completed_task(tmp_path: Path, clip: data.Clip): tasks=[ data.AnnotationTask( clip=clip, - completed=True, - completed_by="test_user", + status_badges=[ + data.StatusBadge( + state=data.TaskState.completed, + user="test_user", + ) + ], ) ], ) @@ -207,7 +222,9 @@ def test_can_recover_user_that_completed_task(tmp_path: Path, clip: data.Clip): # Assert assert recovered == annotation_project - assert recovered.tasks[0].completed_by == "test_user" + badge = recovered.tasks[0].status_badges[0] + assert badge.state == data.TaskState.completed + assert badge.user == "test_user" def test_can_recover_task_notes(tmp_path: Path, clip: data.Clip): @@ -241,7 +258,15 @@ def test_can_recover_task_completion_date(tmp_path: Path, clip: data.Clip): annotation_project = data.AnnotationProject( name="test_project", tasks=[ - data.AnnotationTask(clip=clip, completed=True, completed_on=date) + data.AnnotationTask( + clip=clip, + status_badges=[ + data.StatusBadge( + state=data.TaskState.completed, + created_at=date, + ) + ], + ) ], ) path = tmp_path / "test_project.json" @@ -252,7 +277,9 @@ def test_can_recover_task_completion_date(tmp_path: Path, clip: data.Clip): # Assert assert recovered == annotation_project - assert recovered.tasks[0].completed_on == date + badge = recovered.tasks[0].status_badges[0] + assert badge.state == data.TaskState.completed + assert badge.created_at == date def test_can_recover_task_simple_annotation(