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:**
```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