swiftops-backend / docs /agent /implementation-notes /SELF_ASSIGNMENT_FEATURE.md
kamau1's picture
feat(project): add complete project setup workflow with service methods and API endpoints for regions, roles, subcontractors, and finalization including validation and authorization
4835b24

Self-Assignment Feature

Overview

The self-assignment feature empowers field agents to pick their own work from an available ticket pool, reducing dispatcher workload and giving agents autonomy over their daily schedule.

Key Benefits

For Agents

  • Autonomy: Choose tickets based on location, complexity, or schedule
  • Better Route Planning: Agents know their areas and can optimize travel
  • Faster Pickup: No waiting for dispatcher assignment
  • Workload Control: Visual capacity management (max 4 active tickets)

For Dispatchers

  • Reduced Workload: Agents handle routine assignments themselves
  • Focus on Priorities: Dispatch only VIP/escalated tickets manually
  • Better Resource Utilization: Agents pick work they're best suited for

For Business

  • Faster Ticket Resolution: Reduced assignment delays
  • Improved Efficiency: Better geographic clustering
  • Audit Trail: Complete tracking of who picked what and when

How It Works

1. Available Ticket Pool

Agents see tickets that are:

  • βœ… Status = OPEN (not assigned, in_progress, completed, or cancelled)
  • βœ… In Agent's Region (based on project_team.project_region_id)
  • βœ… From Agent's Projects (agent must be on project team)
  • βœ… Ordered by Priority (high priority, urgent, oldest first)
GET /api/v1/assignments/tickets/available-for-me

Response:

{
  "agent_id": "agent-uuid",
  "agent_region_id": "region-uuid",
  "current_active_count": 2,
  "max_capacity": 4,
  "remaining_capacity": 2,
  "available_tickets": [
    {
      "id": "ticket-uuid",
      "ticket_name": "John Doe - Fiber Installation",
      "ticket_type": "installation",
      "service_type": "fiber",
      "priority": "high",
      "status": "open",
      "work_description": "Install 50Mbps fiber connection",
      "scheduled_date": "2024-03-20",
      "due_date": "2024-03-20T17:00:00Z",
      "sla_violated": false,
      "project_region_id": "region-uuid"
    }
  ],
  "total_available": 15
}

2. Self-Assign Ticket

Agent picks ticket from pool:

POST /api/v1/assignments/tickets/{ticket_id}/self-assign

Request:

{
  "execution_order": 1,
  "planned_start_time": "2024-03-20T09:00:00Z",
  "notes": "I know this area well, can complete quickly"
}

Validations:

  • βœ… Ticket is OPEN (not already assigned)
  • βœ… Agent is on project team
  • βœ… Agent's region matches ticket region
  • βœ… Agent has capacity (< 4 active tickets)
  • βœ… No duplicate active assignment

Effects:

  • Creates assignment with action='self_assigned' and is_self_assigned=True
  • assigned_by_user_id = user_id (self)
  • Ticket status changes: open β†’ assigned

Regional Filtering

Agent Regional Assignment

In project_team table:

  • project_region_id = NULL: Agent can work all regions in that project
  • project_region_id = <region_id>: Agent limited to specific region

Ticket Pool Filtering

System shows tickets based on agent's regional assignments:

# If agent can work all regions in Project A
Agent can see: All tickets from Project A (regardless of ticket.project_region_id)

# If agent is limited to Region X in Project A
Agent can see: Only tickets from Project A where ticket.project_region_id = Region X

Capacity Management

Rules

  • Max Active Tickets: 4 per agent
  • Active Definition: ended_at IS NULL
  • Daily Limit: None (agent can complete 10+ tickets per day)
  • Prevents Hoarding: Can't pick more than 4 at once

Lifecycle Example

Morning:

Agent has 0 active β†’ Picks 3 tickets β†’ Now has 3 active

Mid-Day:

Agent completes 2 tickets β†’ Now has 1 active
Agent picks 2 more tickets β†’ Now has 3 active

Afternoon:

Agent drops 1 ticket (customer unavailable) β†’ Now has 2 active
Agent picks 2 more tickets β†’ Now has 4 active (AT CAPACITY)

End of Day:

Agent completes 3 tickets β†’ Now has 1 active
Total completed: 8 tickets in one day βœ…

Assignment Types

Manual Assignment (Dispatcher/PM)

Use Cases:

  • VIP customers requiring best technician
  • Escalated tickets needing immediate attention
  • Team assignments (multiple agents)
  • Scheduled appointments

Action: 'assigned'
is_self_assigned: False

Self-Assignment (Agent)

Use Cases:

  • Routine installations
  • Standard support tickets
  • Agent-driven scheduling
  • Regional work optimization

Action: 'self_assigned'
is_self_assigned: True


Multiple Assignment Attempts

Same Ticket, Multiple Days

Agents can be assigned to same ticket multiple times (different attempts):

Monday:

Agent picks Ticket #123 β†’ Arrives β†’ Customer unavailable β†’ Drops ticket
Assignment #1: ended_at = Monday 2PM

Tuesday:

Ticket #123 reopened β†’ Agent picks again β†’ Completes successfully
Assignment #2: ended_at = Tuesday 11AM

Benefits:

  • Each attempt has separate expense tracking
  • Complete audit trail preserved
  • Historical data for performance metrics

Validation:

  • ❌ Can't have 2+ active assignments for same ticket
  • βœ… Can have multiple historical assignments for same ticket

API Endpoints

Self-Assignment

Method Endpoint Description Auth
GET /api/v1/assignments/tickets/available-for-me Get available tickets for agent Agent
POST /api/v1/assignments/tickets/{ticket_id}/self-assign Self-assign ticket Agent

Manual Assignment (Existing)

Method Endpoint Description Auth
POST /api/v1/assignments/tickets/{ticket_id}/assign Assign to agent Dispatcher/PM
POST /api/v1/assignments/tickets/{ticket_id}/assign-team Assign to team Dispatcher/PM

Agent Actions (Existing)

Method Endpoint Description
POST /assignments/{id}/accept Accept assignment
POST /assignments/{id}/reject Reject assignment
POST /assignments/{id}/start-journey Begin travel
POST /assignments/{id}/arrived Mark arrival
POST /assignments/{id}/complete Complete work
POST /assignments/{id}/drop Drop ticket

Error Handling

409 Conflict

{
  "detail": "Ticket is not available for self-assignment (status: assigned)"
}

Reason: Someone else picked the ticket first (race condition)

409 Conflict

{
  "detail": "Agent has reached capacity (4 active tickets)"
}

Reason: Agent already has 4 active tickets

403 Forbidden

{
  "detail": "This ticket is not in your assigned region"
}

Reason: Ticket's region doesn't match agent's regional assignment

400 Bad Request

{
  "detail": "Agent is not on this project's team"
}

Reason: Agent not added to project team


Database Schema

ticket_assignments Table

CREATE TABLE ticket_assignments (
    id UUID PRIMARY KEY,
    ticket_id UUID REFERENCES tickets(id),
    user_id UUID REFERENCES users(id),
    assigned_by_user_id UUID REFERENCES users(id), -- Self: user_id = assigned_by_user_id
    
    -- Assignment type
    action TEXT, -- 'assigned' | 'self_assigned' | 'rejected' | 'dropped' | etc.
    is_self_assigned BOOLEAN DEFAULT FALSE, -- TRUE for self-picked tickets
    
    -- Execution planning
    execution_order INTEGER,
    planned_start_time TIMESTAMPTZ,
    
    -- Timeline
    assigned_at TIMESTAMPTZ NOT NULL,
    responded_at TIMESTAMPTZ,
    journey_started_at TIMESTAMPTZ,
    arrived_at TIMESTAMPTZ,
    ended_at TIMESTAMPTZ, -- NULL = active, NOT NULL = closed
    
    -- Metadata
    notes TEXT,
    reason TEXT
);

project_team Table

CREATE TABLE project_team (
    id UUID PRIMARY KEY,
    project_id UUID REFERENCES projects(id),
    user_id UUID REFERENCES users(id),
    
    -- Regional assignment
    project_region_id UUID REFERENCES project_regions(id), -- NULL = all regions
    
    role TEXT,
    assigned_at TIMESTAMPTZ,
    removed_at TIMESTAMPTZ
);

Testing Scenarios

Scenario 1: Agent Picks Available Ticket

1. Agent calls GET /tickets/available-for-me
2. System returns 15 open tickets in agent's region
3. Agent picks ticket #1
4. POST /tickets/{ticket-1}/self-assign
5. Assignment created with is_self_assigned=True
6. Ticket status: open β†’ assigned

Scenario 2: Race Condition (Two Agents Pick Same Ticket)

1. Agent A and Agent B both see ticket #123 in pool
2. Agent A picks ticket #123 at 9:00:01
3. Agent B tries to pick ticket #123 at 9:00:02
4. System returns 409: "Ticket is not available" (already assigned)
5. Agent B picks different ticket from pool

Scenario 3: At Capacity

1. Agent has 4 active tickets
2. Agent tries to pick 5th ticket
3. System returns 409: "Agent has reached capacity (4 active tickets)"
4. Agent must complete/drop existing ticket first

Scenario 4: Regional Restriction

1. Agent assigned to Region A only
2. Ticket #456 is in Region B
3. Agent tries to pick ticket #456
4. System returns 403: "This ticket is not in your assigned region"
5. Agent can only pick tickets from Region A

Scenario 5: Customer Unavailable - Re-pick

Day 1:
1. Agent picks ticket #789
2. Agent arrives at site
3. Customer unavailable
4. Agent drops ticket (ended_at = Day 1)

Day 2:
1. Ticket #789 reopened (status = open)
2. Agent picks ticket #789 again (NEW assignment)
3. Agent completes work
4. Two assignments in history for same ticket

Configuration

Capacity Settings

In ticket_assignment_service.py:

MAX_AGENT_CAPACITY = 4  # Maximum active tickets per agent

Rationale:

  • Minimum 3 tickets per day expected
  • +1 for ambitious agents
  • Prevents ticket hoarding
  • Maintains work quality

Future Enhancements

Potential Features

  1. Smart Recommendations: AI suggests best tickets based on agent skills/location
  2. Auto-Assignment: System auto-assigns based on proximity and workload
  3. Team Self-Assignment: Agents pick team tickets together
  4. Scheduled Picking: Agents reserve tickets for specific time slots
  5. Priority Booking: High-performing agents get first pick
  6. Geographic Clustering: Show map view of available tickets

Migration Path

Phase 1: Current State (Manual Only)

  • All assignments done by dispatcher
  • No agent autonomy

Phase 2: Hybrid (Manual + Self)

  • Agents can self-assign routine tickets
  • Dispatchers assign VIP/escalated tickets
  • Monitor adoption and performance

Phase 3: Future (AI-Assisted)

  • System recommends optimal tickets
  • Smart auto-assignment for urgent cases
  • Dispatchers handle exceptions only

Monitoring & Metrics

Key Metrics to Track

  • Self-Assignment Rate: % of tickets self-assigned vs dispatcher-assigned
  • Pickup Time: Time from ticket creation to assignment
  • Completion Rate: % of self-assigned tickets completed vs dropped
  • Regional Distribution: Even workload across regions
  • Agent Satisfaction: Survey feedback on autonomy

Dashboard Views

  • Dispatcher: See which agents are self-assigning effectively
  • Agent: See capacity and available work
  • Manager: Compare manual vs self-assignment performance

Summary

βœ… Implemented Features:

  • Available ticket pool filtering by region
  • Self-assignment with validation
  • Capacity management (max 4 active)
  • Multiple assignment attempts per ticket
  • Complete audit trail

βœ… Benefits:

  • Reduced dispatcher workload
  • Agent autonomy and efficiency
  • Faster ticket pickup
  • Better route optimization

βœ… Safeguards:

  • Regional restrictions enforced
  • Capacity limits prevent hoarding
  • Race condition handling
  • Complete validation and error handling