> ## Documentation Index
> Fetch the complete documentation index at: https://docs.spn.wtf/llms.txt
> Use this file to discover all available pages before exploring further.

# Call Lifecycle

> Lead states, call states, retries, failures, and successful CRM handoff.

## Lead Lifecycle

```text theme={null}
new
  -> dispatched
  -> new       retry scheduled after failed/unanswered/rejected call
  -> failed    retry limit reached or invalid lead
```

`scheduler.py` only selects leads where:

```sql theme={null}
status = 'new'
AND (retry_after IS NULL OR retry_after <= NOW())
```

When a SIP call is successfully triggered, the lead is marked `dispatched`.

## Call Lifecycle

```text theme={null}
calling
  -> called    qualification JSON saved
  -> pushed    Talking Shops task created
  -> failed    rejected, unanswered, dispatch failed, stale, startup failure, or no answers
```

Each dial attempt gets its own `calls` row. This means one lead can have multiple call attempts over time.

## Successful Call Flow

1. Lead starts as `new`.
2. Scheduler validates call window and phone number.
3. Scheduler creates a `calls` row with `status='calling'`.
4. Scheduler calls LiveKit `create_sip_participant`.
5. LiveKit uses the configured outbound SIP trunk to call the lead through Vobiz.
6. Agent joins `ignitech-lead-{lead_id}`.
7. Lead answers and speaks with the agent.
8. Agent extracts structured qualification.
9. `db.save_qualification()` stores JSON and marks the call `called`.
10. `talkingshops_pusher.py` pushes the record to Talking Shops.
11. `db.set_call_pushed()` marks the call `pushed`.

## Failure Reasons

The `calls.failure_reason` field is machine-readable.

| Reason            | Meaning                                                      |
| ----------------- | ------------------------------------------------------------ |
| `rejected`        | Caller rejected or disconnected before usable conversation   |
| `unanswered`      | Call was not answered                                        |
| `dispatch_failed` | LiveKit SIP participant creation failed                      |
| `startup_failed`  | Agent could not start or find the call context               |
| `stale_timeout`   | Call stayed in `calling` for too long                        |
| `no_answers`      | Call connected but no useful qualification data was captured |

## Retry Behavior

Retry scheduling is centralized in `db.py`.

Default:

```env theme={null}
MAX_CALL_RETRIES=3
```

When retryable failure occurs, the lead is set back to:

```text theme={null}
status='new'
retry_after=now + 24 hours
retry_count=retry_count + 1
```

If `retry_count` exceeds `MAX_CALL_RETRIES`, the lead is marked `failed`.

## Phone Validation

Outbound dialing is India-mobile focused. The scheduler accepts:

* `98765XXXXX`
* `098765XXXXX`
* `9198765XXXXX`
* `+9198765XXXXX`

It rejects values that cannot safely become:

```text theme={null}
+91XXXXXXXXXX
```

The local 10-digit mobile number must start with `6`, `7`, `8`, or `9`.

## Whitelist Mode

For testing, you can restrict dialing to selected numbers:

```env theme={null}
CALL_WHITELIST_NUMBERS=98765XXXXX,+9198765XXXXX
```

When this is set, any lead outside the whitelist is marked failed instead of called.
