From ccc033c1a1913f08b071d1c05183536e264e1fed Mon Sep 17 00:00:00 2001 From: RapidPoseidon Date: Fri, 9 Oct 2026 21:16:22 +0000 Subject: [PATCH] feat(selection): add advance_on_view to LabelingSelection Co-Authored-By: Claude Opus 5.5 Co-Authored-By: deli@rapidata.ai --- .../selection/labeling_selection.py | 11 ++++++- tests/rapidata_client/selection/__init__.py | 0 .../selection/test_labeling_selection.py | 32 +++++++++++++++++++ 3 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 tests/rapidata_client/selection/__init__.py create mode 100644 tests/rapidata_client/selection/test_labeling_selection.py diff --git a/src/rapidata/rapidata_client/selection/labeling_selection.py b/src/rapidata/rapidata_client/selection/labeling_selection.py index 3f7671a000..396062909b 100644 --- a/src/rapidata/rapidata_client/selection/labeling_selection.py +++ b/src/rapidata/rapidata_client/selection/labeling_selection.py @@ -17,6 +17,8 @@ class LabelingSelection(RapidataSelection): max_iterations (int | None): An annotator can answer the same task only once if the retrieval_mode is "Shuffled" or "Sequential". max_iterations can increase the amount of responses an annotator can do to the same task (datapoint). + advance_on_view (bool): Move an annotator past a task as soon as it is shown, even without an answer, + so a skipped task is not served to them again. Requires "Shuffled" or "Sequential". Defaults to False. """ def __init__( @@ -24,10 +26,16 @@ def __init__( amount: int, retrieval_mode: RapidataRetrievalMode = RapidataRetrievalMode.Shuffled, max_iterations: int | None = None, + advance_on_view: bool = False, ): + if advance_on_view and retrieval_mode == RapidataRetrievalMode.Random: + raise ValueError( + 'advance_on_view requires the "Shuffled" or "Sequential" retrieval mode' + ) self.amount = amount self.retrieval_mode = retrieval_mode self.max_iterations = max_iterations + self.advance_on_view = advance_on_view def _to_model(self): from rapidata.api_client.models.i_selection import ISelection @@ -41,11 +49,12 @@ def _to_model(self): amount=self.amount, retrievalMode=self.retrieval_mode.value, maxIterations=self.max_iterations, + advanceOnView=self.advance_on_view, ) ) def __str__(self) -> str: - return f"LabelingSelection(amount={self.amount}, retrieval_mode={self.retrieval_mode}, max_iterations={self.max_iterations})" + return f"LabelingSelection(amount={self.amount}, retrieval_mode={self.retrieval_mode}, max_iterations={self.max_iterations}, advance_on_view={self.advance_on_view})" def __repr__(self) -> str: return self.__str__() diff --git a/tests/rapidata_client/selection/__init__.py b/tests/rapidata_client/selection/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/rapidata_client/selection/test_labeling_selection.py b/tests/rapidata_client/selection/test_labeling_selection.py new file mode 100644 index 0000000000..edb6cd26bf --- /dev/null +++ b/tests/rapidata_client/selection/test_labeling_selection.py @@ -0,0 +1,32 @@ +from __future__ import annotations + +import pytest + +from rapidata.rapidata_client.selection.labeling_selection import LabelingSelection +from rapidata.rapidata_client.selection.rapidata_retrieval_modes import ( + RapidataRetrievalMode, +) + + +@pytest.mark.parametrize( + "mode", [RapidataRetrievalMode.Shuffled, RapidataRetrievalMode.Sequential] +) +def test_advance_on_view_is_sent(mode: RapidataRetrievalMode) -> None: + model = LabelingSelection(amount=3, retrieval_mode=mode, advance_on_view=True) + + assert model._to_model().to_dict()["advanceOnView"] is True + + +def test_advance_on_view_defaults_to_false() -> None: + model = LabelingSelection(amount=3) + + assert model._to_model().to_dict()["advanceOnView"] is False + + +def test_advance_on_view_rejects_random() -> None: + with pytest.raises(ValueError, match="Shuffled"): + LabelingSelection( + amount=3, + retrieval_mode=RapidataRetrievalMode.Random, + advance_on_view=True, + )