Skip to main content

Per-campaign dialer sentiment & outcome card

The sentiment card on a dialer campaign’s detail page tells you how answered calls actually went — not just whether they connected. It reads the post-call AI synthesis that post-call scoring writes onto every completed dialer call and rolls it up into an operator view: a positive/neutral/negative split, an average sentiment score, and a per-carrier-outcome breakdown. Open it before every campaign review. A campaign can post a healthy connect rate and still be trending negative — the card is where you see that.

What the card shows

The card renders three read-outs from GET /api/v1/dialer/campaigns/:id/sentiment:
  • The four-way tally — the share of attempts classified positive, neutral, negative, plus a Pending score bucket for calls whose post-call scoring hasn’t landed yet. Each bucket shows its count and, where scored, its mean sentiment score.
  • Average sentiment score — a single mean over all scored attempts on a −1.0 to +1.0 scale, shown to two decimal places. A campaign with no scored attempts yet renders an em dash, never 0.00 — a literal zero would read as “dead-neutral,” which is wrong for an unscored population.
  • Per-outcome breakdown — every carrier outcome the campaign produced (connected, no_answer, busy, voicemail, amd_detected, in-flight pending, and so on), each with its positive/neutral/negative/pending-score counts and mean score. Rows sort by attempt volume, so the outcome most of your traffic produced leads the table.
A campaign that hasn’t originated any attempts shows “No calls yet” rather than a zeroed table.

How the aggregation works — and why hangup bias is avoided

Two things every attempt must clear before it enters the rollup:
  1. The pacing scheduler stamps one call_logs row per originated attempt, keyed off the attempt’s call identifier.
  2. The post-call synthesis service scores that row — sentiment_label (positive / neutral / negative) plus sentiment_score (float from −1 to +1).
The aggregate then joins each attempt’s carrier outcome (from the carrier/compliance tracking surface) to that scored call row and takes, per dialed contact, the newest attempt — so on a retry ladder only the final originated attempt counts, and every scored call counts exactly once. Two consequences of that design matter when you read the card:
  • Unanswered outcomes don’t drag the score down. Without the join, unanswered attempts would all pile into a “negative” bucket purely because the call hung up before anyone spoke. Because the join only counts attempts with a real scored call row, no_answer / busy outcomes sit in their own rows with a pending-score count, and the sentiment split reflects only calls that actually connected.
  • Scoring lag shows up honestly. A call whose synthesis hasn’t landed yet folds into Pending score rather than being silently and wrongly scored as neutral. If Pending score dominates the card ten minutes after a batch ran, the scoring pipeline is behind — don’t read the split as neutral-heavy.

Where the card lives

Open Outbound → Dialer → pick a campaign. The card sits on the campaign detail page alongside the other campaign-analytics panels (live stats, caller-ID coverage). It refreshes on the page’s polling cycle and covers the rolling 30-day window the aggregate reports. A note on scope: the card is a read-only rollup. It never originates a call and never touches routing — like every dialer voice path, origination stays on the wholesale voice route.

Triaging a negative-skew spike

A negative-skew spike means: the negative bucket gained share against neutral/positive across a window, or the average score fell while volume stayed normal. Work the spike in this order:
  1. Check the target segment. Pull the current list’s segment/filters. A list refresh that re-queued previously burned contacts, or a geographic/demographic shift in the audience, reads as negative skew the moment it lands — before any campaign-side change is in play. If the segment changed, re-screen the list and pause.
  2. Check the content. Open the campaign’s script and the recent audio/broadcast asset. A new offer line, a changed opening question, or a recording rework correlates with a skew step-change the day it shipped. If the skew starts at a content change, revert or rewrite that change before touching anything else.
  3. Check RNG pacing. Look at the predictive pacing knobs and the abandon ceiling. A pacing ratio that creeps up puts calls in front of agents with less setup time, agents rush the opener, and recipients push back — that cascade shows up as negative sentiment on connected calls. Also check the abandon-rate trend: rising abandonment often walks in with a negative skew because more calls land with no agent ready.
  4. Correlate with local caller-ID coverage. Open the campaign’s caller-ID coverage read on the same detail page. If the negative skew concentrates specifically where the origin number isn’t local, the issue is recipient trust, not the pitch — raise coverage for the affected region and re-measure.
Read the per-outcome breakdown to see whether the skew lives in connected specifically or across every outcome. A skew only on connected is a script/agent problem; a skew spread over voicemail / amd_detected too usually means list quality or coverage, not content.

Export the read — GET /api/v1/dialer/campaigns/:id/sentiment

The card is backed by a public endpoint you can pull into your BI layer or a nightly QA export. Response shape (trimmed):
Fields worth noting:
  • avg_scorenull when nothing has been scored yet. Parse as nullable.
  • tally — always all four buckets, zero-padded so your consumer doesn’t need to handle missing keys.
  • by_outcome[].unscored — the counts in a row sum: total = positive + neutral + negative + unscored.
  • attempts_scan_cappedtrue when the campaign’s 30-day attempt volume exceeded the server-side scan bound; the rollup then describes the most recent cap of attempts rather than the whole window. Treat that flag as coverage metadata in any report you build off this.
Schedule the export daily for campaigns in flight; a 7-day rolling window is long enough to smooth an agent-bad-day blip and short enough to still catch a pacing drift.

Pair with the manual-dial view

The campaign card covers every originated attempt, but when you chase a negative spike to specific contacts, an agent can re-dial one of them manually through the dialer’s manual-dial endpoint (POST /api/v1/dialer/manual-dial). Manual dial originates a single, agent-supplied number through one human click — deliberately not automated-sequencing — so it never inflates the campaign’s automated-attempt pacing, and the platform stamps that origination so the audit trail can prove a human placed it. The workflow: pull the worst-scoring contacts from by_outcome on the campaign card, hand the short list to the QA agent, and have the agent re-dial one number at a time while re-screening the script. The response comes back through the same post-call scoring, so the follow-up lands in the same four-way split on the card the next time the aggregate refreshes.

See also