Spaces:
Sleeping
Sleeping
| # 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 = <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 | |