Debug a Project Assignment Codebase
Scale AISoftware Engineer · OnsiteLast updated
Tests your ability to manage state, enforce filtering rules, and maintain clean public API contracts in an asynchronous or stateful task assignment system.
Task Assignment Engine
Scale AI routes large volumes of data labeling tasks to qualified annotators based on project requirements, annotator skill profiles, and concurrency limits. You are tasked with implementing (or refactoring) the core ProjectAssignmentEngine class.
Requirements
Implement the ProjectAssignmentEngine class with the following methods:
-
register_annotator(annotator_id: str, skills: list[str], max_concurrent_tasks: int) -> None- Adds a new annotator or updates an existing annotator's skill list and capacity. Active tasks assigned to an existing annotator must remain active.
-
add_project(project_id: str, required_skills: list[str], priority: int) -> None- Registers a project. Higher
priorityvalues take precedence (priority=10is higher priority thanpriority=1).
- Registers a project. Higher
-
add_tasks(project_id: str, task_ids: list[str]) -> None- Appends unassigned task IDs to the given project. Tasks within the same project are assigned in FIFO order.
-
assign_next() -> list[tuple[str, str, str]]- Finds available capacity across annotators and assigns pending tasks.
- Only projects with pending tasks are considered, processed in descending order of project
priority. For ties in priority, use project insertion order. - A task can only be assigned to an annotator who possesses all of the project's
required_skillsand currently has active tasks strictly less thanmax_concurrent_tasks. - If multiple annotators are eligible for a task, assign to the one with the fewest active tasks. If there is still a tie, assign to the annotator whose ID is lexicographically smallest.
- Returns a list of
(task_id, project_id, annotator_id)tuples representing all new assignments made during this invocation.
-
complete_task(task_id: str) -> bool- Marks an active task as completed and frees up one concurrency slot for the assigned annotator.
- Returns
Trueif the task was currently active and successfully completed; returnsFalseiftask_idis unknown or not currently active.
Constraints
- Number of total tasks
<= 10^5. - Number of annotators
<= 500. - Number of projects
<= 1,000. - Length of skill sets
<= 20per annotator/project. - All IDs are non-empty alphanumeric strings.
Example
engine = ProjectAssignmentEngine()
engine.register_annotator("ann_1", ["nlp", "audio"], max_concurrent_tasks=2)
engine.register_annotator("ann_2", ["nlp"], max_concurrent_tasks=1)
engine.add_project("proj_a", required_skills=["nlp"], priority=5)
engine.add_tasks("proj_a", ["t1", "t2"])
engine.add_project("proj_b", required_skills=["audio"], priority=10)
engine.add_tasks("proj_b", ["t3"])
# proj_b has higher priority (10 > 5), but requires 'audio', which only ann_1 has.
# proj_a requires 'nlp', which both ann_1 and ann_2 have.
assignments = engine.assign_next()
# Expected assignments:
# 1. t3 (proj_b) -> ann_1 (ann_1 load becomes 1)
# 2. t1 (proj_a) -> ann_2 (ann_2 load is 0, ann_1 load is 1; ann_2 has fewer active tasks)
# 3. t2 (proj_a) -> ann_1 (ann_2 is at capacity 1/1, ann_1 has capacity 1/2 left)
# Result: [("t3", "proj_b", "ann_1"), ("t1", "proj_a", "ann_2"), ("t2", "proj_a", "ann_1")]
engine.complete_task("t1") # Returns True, ann_2 now has 0 active tasks
engine.complete_task("non_existent") # Returns False
Hints
Hint 1
Maintain a set of skills for each annotator and project so subset checks (`project_skills.issubset(annotator_skills)`) run in `O(min(|P|, |A|))` time.
Hint 2
Be careful when updating an existing annotator via `register_annotator`: retain their currently assigned active tasks and ensure their new capacity limit does not cause an inconsistent negative free capacity.
Hint 3
During `assign_next()`, update active task counts immediately as each assignment is made so subsequent decisions within the same call use up-to-date loads.
Follow-up questions
- How would you optimize `assign_next()` if the number of annotators grows from 500 to 50,000?
- How would you handle task timeouts or worker disconnects where a task remains in an active state indefinitely?
- If annotators could specify task preferences or target hourly throughput, how would you modify the assignment selection heuristic?
Solution walkthrough is for members
Get the full walkthrough with reference code and complexity analysis, plus Kitty Coach at 300 messages a day. Hints, the code runner and community answers stay free.