Appearance
Call Center (Predictive Dialer)
The Call Center app (slug call_center) is the outbound contact centre: a predictive dialer that re-derives its dial ratio every two seconds from live agent availability and measured call outcomes, an agent desk, and a supervisor console and wallboard around them. It is used by a sales or collections floor to work a list of numbers with as little agent idle time as the abandon-rate cap allows.
How it differs from the two things beside it
Three call-centre surfaces live on this platform and only one of them dials outbound at scale:
| What it is | Direction | |
|---|---|---|
FusionPBX call centre (v_call_center_*, via PBX (Admin)) | mod_callcenter queues — a caller waits, a strategy picks an agent | Inbound only |
AI Call Center (convai) | A voice AI replaces the agent | Outbound, one line at a time per campaign |
| Call Center (this app) | A floor of humans, paced predictively | Outbound, many lines per agent |
FusionPBX has no outbound side at all: no dialling list, no pacing, no retry cadence, no suppression list, no disposition codes. Those are exactly the seven tables this app adds.
What you can do
- Run a campaign in one of four dial modes — preview (the agent clicks Dial), progressive (one line per free agent), power (a fixed ratio), or predictive (the ratio is derived from measurement and held under an abandon cap).
- Watch the pacing, not just the result — the Dialer Console shows the current ratio, the measurements behind it, and the controller's own one-line explanation, over a live series of every decision it has made.
- Take calls at the Agent Desk — go ready, receive bridged calls with the contact record and script in front of you, and close each one with a disposition that decides what happens to the contact next.
- Simulate before dialling anybody — a new campaign starts in simulation mode, where the whole pacing loop runs against generated calls and optional virtual agents. Nothing reaches the trunk until simulation is switched off.
- Build a list — paste names and numbers, or import CRM leads (only those with a usable phone; anything already on the do-not-call list is dropped).
- Suppress a number — from the agent's disposition or directly; the entry also pulls the number out of every campaign that still holds it.
- Watch the floor — the Wallboard is one tenant-wide read of every running campaign, every agent's state, and the day's call outcomes.
The pacing controller
app/apps/portal/services/call_center_pacing.py is a pure module with no I/O, so the model can be read and tested on its own. In outline:
- Warm-up. Under 25 completed calls the measured rates are noise, so the campaign paces progressively whatever mode it is in.
- The offered load. Erlang B gives the largest load (in erlangs) whose blocking probability stays inside the campaign's abandon cap. Blocking is the abandon rate here, because a connect that finds no free agent is dropped after
max_wait_seconds. This is what makes the cap bind at the source rather than only after the fact — and it is why a six-seat floor sustains about 42% occupancy at a 3% cap while a hundred-seat floor sustains 92%. - The lines that produce it. That load is a connect rate; divided by the chance a line reaches a live human (
answer_rate × (1 − machine_rate)) and multiplied by how long a line stays occupied, it is a number of lines. - The governor. A multiplicative controller on the measured abandon rate corrects the model's error — halving per minute over cap, creeping up 10% a minute under it — bounded to 0.35–1.25 so it corrects the model rather than replacing it.
- Ramp limiting on the way up only, and a progressive fallback at twice the cap. Progressive cannot abandon by construction, so the fallback is both safe and self-healing: the clean connects it makes are what bring the measured rate back down.
Every tick writes a CcPacingSample row holding the inputs, the decision and the reason, which is what the console charts and what makes an abandon rate answerable rather than merely reported.
How a call actually reaches an agent
Agents sit on a nailed-up line. Going ready originates their extension once with park_after_bridge=true and parks the answered channel; every customer is then bridged into it with uuid_bridge, which is instant. A predictive dialer cannot ring the agent after the customer answers — that is three to ten seconds of silence, and it is the dead-air complaint the whole technique is known for.
Customers are dialled as loopback/<number>/<domain>, which re-enters the org's own dialplan, so outbound routing, gateway selection, caller ID rules and CDR / billing are exactly what a call dialled from a handset would get. Nothing here writes to FusionPBX's tables.
Data model
- CcCampaign (
cc_campaigns) — the list's dial mode, pacing limits, calling window, retry cadence, caller ID and thedry_runswitch. - CcContact (
cc_contacts) — one row of a dialling list with its own attempt/retry state. - CcAgent (
cc_agents) — an agent's live state (offline/ready/on_call/wrap/break), their nailed-up session and their heartbeat. - CcCall (
cc_calls) — one dial attempt, includingwait_ms(answer → bridge) andabandoned, the two numbers a predictive dialer is judged on. - CcDisposition (
cc_dispositions) — an outcome code and what it does to the contact: close it, retry it on a timer, or suppress the number. - CcDncEntry (
cc_dnc) — suppression, checked before every originate. - CcPacingSample (
cc_pacing_samples) — one row per pacing tick.
Configuration & integrations
- FreeSWITCH event socket — shared with the CRM click-to-dial; configured under App Settings → CRM, with the org's
telephony.sip_domainas the fallback SIP domain (AI Call Center → Telephony Settings). - Agent extensions — read from the agent's own login (
users.extension). An agent without one cannot go ready on a live campaign. - Answering-machine detection —
amd_enableddrivesmod_avmd, which is not present on every FreeSWITCH build (it is absent on the .62 box). It is off by default; the agent's "Voicemail" disposition feeds the same machine-rate input either way, and that input is what the pacing model actually consumes. - A restart drops every running campaign, deliberately. In-flight state is in memory; resurrecting a dialer across a deploy would originate calls nobody asked for from a process that has forgotten who was on the floor. The shutdown hook hangs up what is in the air rather than orphaning it.
API reference
All routes are under /call-center and are tenant-scoped; campaign mutations additionally require the admin role.
| Method | Path | Purpose |
|---|---|---|
| GET/POST | /call-center/campaigns | List / create campaigns |
| GET/PATCH/DELETE | /call-center/campaigns/{id} | Read, update or delete one |
| POST | /call-center/campaigns/{id}/start | Start the dialer loop (optionally with virtual agents, simulation only) |
| POST | /call-center/campaigns/{id}/pause / /stop | Pause or stop it |
| GET | /call-center/campaigns/{id}/pacing | The controller's decision series |
| POST | /call-center/campaigns/{id}/contacts | Add contacts (structured or pasted) |
| POST | /call-center/campaigns/{id}/import-leads | Seed the list from CRM leads |
| GET | /call-center/agent | The agent desk's whole state in one poll |
| POST | /call-center/agent/status | Go ready / on break / log out |
| POST | /call-center/agent/heartbeat | Liveness ping (the pacer's capacity check) |
| POST | /call-center/agent/disposition | Close the current call |
| GET | /call-center/wallboard | Tenant-wide live floor view |
| POST | /call-center/dnc | Suppress a number and pull it from every list |
Flat CRUD is on the generic grid engine at /grid/cc-campaigns, /grid/cc-contacts, /grid/cc-calls (read-only — a call row is evidence), /grid/cc-agents, /grid/cc-dispositions and /grid/cc-dnc. All six are admin_only, which is also what keeps their rows out of a non-admin agent's sidebar while leaving the Agent Desk reachable.