# 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:** ```json { "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:** ```json { "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 = `**: Agent limited to **specific region** ### Ticket Pool Filtering System shows tickets based on agent's regional assignments: ```python # 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 ```json { "detail": "Ticket is not available for self-assignment (status: assigned)" } ``` **Reason:** Someone else picked the ticket first (race condition) ### 409 Conflict ```json { "detail": "Agent has reached capacity (4 active tickets)" } ``` **Reason:** Agent already has 4 active tickets ### 403 Forbidden ```json { "detail": "This ticket is not in your assigned region" } ``` **Reason:** Ticket's region doesn't match agent's regional assignment ### 400 Bad Request ```json { "detail": "Agent is not on this project's team" } ``` **Reason:** Agent not added to project team --- ## Database Schema ### ticket_assignments Table ```sql 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 ```sql 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`: ```python 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