53.2% fewer conversations end on a sour note than before Easol

What this meansCut in how often a conversation ends in a worse mood than it started — AI alone now, against before Easol. 8.0% of conversations ended worse than they started before Easol, against 3.7% on AI-only tickets now. Both numbers behind it are in the charts below. Rounding can shift the last decimal. Checked 2026-08-04T12:48:25Z.

Half as many conversations end worse than they started

8.0% before Easol, 3.7% with AI alone

AI reads the first and last message of every conversation and scores the mood on a −2 to +2 scale. Before Easol, 8.0% of conversations ended on a lower score than they opened on — 45 of 563. On AI-only tickets it is 3.7%, 144 of 3,846. That is 53.2% fewer.

Staff-handled tickets sour the most

16.9% of staff-only tickets end worse than they started, and 13.6% when AI only helped. Those are the longer, harder conversations — 3.9 and 3.4 messages against 2.4 for AI alone.

The average mood barely moves either way

Mood moved +0.02 points on AI-only tickets and −0.06 before Easol. Both are close to zero because roughly nine in ten conversations end on the exact score they opened on. The share that sours is the sharper read.

Worth knowing
  • Different tickets in different months, not a controlled test.
  • Mood is read by AI from the conversation, not asked in a survey.
  • AI alone and before Easol run to a similar length — 2.4 and 2.7 messages — so this pair is a fairer comparison than the staff-handled buckets, which run longer and move more in both directions.
  • Sizes are lopsided: 3,846 AI-only tickets against 563 before Easol.
Before Easol On Easol

Sour endings: AI alone vs before Easol

Before Easol 8.0% AI alone 3.7%

Share ending worse than they started

Before Easol 8.0% Staff only 16.9% AI helped 13.6% AI alone 3.7%

Mood change, start to end

Before Easol -0.06 Staff only -0.04 AI helped 0.0 AI alone 0.02

Scored tickets by who handled them

Before Easol 563 Staff only 1,007 AI helped 1,319 AI alone 3,846

Where the numbers come from

One entry per figure: what it means, the SQL that made it, and when it was last re-checked.

Fewer conversations end on a sour note souring_cut_vs_pre_easol_pct

Cut in how often a conversation ends in a worse mood than it started — AI alone now, against before Easol

8.0% of conversations ended worse than they started before Easol, against 3.7% on AI-only tickets now

WITH s AS (
  SELECT
    CASE
      WHEN t.imported THEN 'pre_easol'
      WHEN t.ai_resolved THEN 'full_ai'
      WHEN (t.ai_draft_sent_count > 0 OR t.booking_action_performed_by_ai_count > 0) THEN 'partial_ai'
      WHEN t.sent_message_count > 0 THEN 'no_ai'
      ELSE 'no_reply'
    END AS bucket,
    sen.opening, sen.closing
  FROM reporting.inbox_tickets t
  JOIN inbox_ticket_sentiments sen ON sen.ticket_id = t.id
  WHERE t.company_id = $1 AND t.created_at < $2
),
rates AS (
  SELECT bucket, COUNT(*) FILTER (WHERE closing < opening)::numeric / COUNT(*) AS souring
  FROM s GROUP BY bucket
)
SELECT ROUND(100.0 * (1 -
  (SELECT souring FROM rates WHERE bucket = 'full_ai')
  / (SELECT souring FROM rates WHERE bucket = 'pre_easol')
), 1) AS value

Source: app/models/inbox/ticket_sentiment.rb:17-18 (opening/closing columns); written at app/workers/inbox/analyze_sentiment_worker.rb:22-23

Verified: 2026-08-04T12:48:25Z

Sour endings: AI alone vs before Easol mood_worsened_pct_full_ai_vs_pre_easol

The exact pair behind the headline — share of conversations that end worse than they started, AI alone now against before Easol

WITH s AS (
  SELECT
    CASE
      WHEN t.imported THEN 'pre_easol'
      WHEN t.ai_resolved THEN 'full_ai'
      WHEN (t.ai_draft_sent_count > 0 OR t.booking_action_performed_by_ai_count > 0) THEN 'partial_ai'
      WHEN t.sent_message_count > 0 THEN 'no_ai'
      ELSE 'no_reply'
    END AS bucket,
    sen.opening, sen.closing
  FROM reporting.inbox_tickets t
  JOIN inbox_ticket_sentiments sen ON sen.ticket_id = t.id
  WHERE t.company_id = $1 AND t.created_at < $2
)
SELECT bucket AS label,
  ROUND(100.0 * COUNT(*) FILTER (WHERE closing < opening) / COUNT(*), 1) AS value
FROM s WHERE bucket IN ('full_ai', 'pre_easol')
GROUP BY 1 ORDER BY 1

Source: app/models/inbox/ticket_sentiment.rb:17-18 (Inbox::TicketSentiment opening/closing columns); derived, no dedicated scope

Verified: 2026-08-04T12:48:25Z

Share ending worse than they started mood_worsened_pct_by_bucket

Of scored tickets, the share where the closing mood is below the opening mood, split by who handled them

Staff-only and AI-helped tickets run longer, so their mood moves more in both directions. AI alone and before Easol sit at similar thread lengths (2.4 and 2.7 messages), which is the pair the headline compares

SELECT
  CASE
    WHEN t.imported THEN 'pre_easol'
    WHEN t.ai_resolved THEN 'full_ai'
    WHEN (t.ai_draft_sent_count > 0 OR t.booking_action_performed_by_ai_count > 0) THEN 'partial_ai'
    WHEN t.sent_message_count > 0 THEN 'no_ai'
    ELSE 'no_reply'
  END AS label,
  ROUND(100.0 * COUNT(*) FILTER (WHERE s.closing < s.opening) / COUNT(*), 1) AS value
FROM reporting.inbox_tickets t
JOIN inbox_ticket_sentiments s ON s.ticket_id = t.id
WHERE t.company_id = $1 AND t.created_at < $2
GROUP BY 1 ORDER BY 1

Source: app/models/inbox/ticket_sentiment.rb:17-18 (Inbox::TicketSentiment opening/closing columns); derived, no dedicated scope

Verified: 2026-08-04T12:48:25Z

Mood change, start to end avg_sentiment_delta_by_bucket

How far the mood moved between the first and last message, in points on the −2 to +2 scale. Above zero means the customer left happier than they arrived

Most conversations end on the same score they started, so this average sits close to zero. The share that moves up or down is the clearer read

SELECT
  CASE
    WHEN t.imported THEN 'pre_easol'
    WHEN t.ai_resolved THEN 'full_ai'
    WHEN (t.ai_draft_sent_count > 0 OR t.booking_action_performed_by_ai_count > 0) THEN 'partial_ai'
    WHEN t.sent_message_count > 0 THEN 'no_ai'
    ELSE 'no_reply'
  END AS label,
  ROUND(AVG(s.closing - s.opening)::numeric, 2) AS value
FROM reporting.inbox_tickets t
JOIN inbox_ticket_sentiments s ON s.ticket_id = t.id
WHERE t.company_id = $1 AND t.created_at < $2
GROUP BY 1 ORDER BY 1

Source: app/models/inbox/ticket_sentiment.rb:17-18 (Inbox::TicketSentiment opening/closing columns); derived, no dedicated scope

Verified: 2026-08-04T12:48:25Z

Scored tickets by who handled them sentiment_scored_tickets_by_bucket

How many tickets each group has a mood score for. Every happiness figure is a share of these

WITH s AS (
  SELECT
    CASE
      WHEN t.imported THEN 'pre_easol'
      WHEN t.ai_resolved THEN 'full_ai'
      WHEN (t.ai_draft_sent_count > 0 OR t.booking_action_performed_by_ai_count > 0) THEN 'partial_ai'
      WHEN t.sent_message_count > 0 THEN 'no_ai'
      ELSE 'no_reply'
    END AS bucket,
    sen.inferred_satisfaction
  FROM reporting.inbox_tickets t
  JOIN inbox_ticket_sentiments sen ON sen.ticket_id = t.id
  WHERE t.company_id = $1 AND t.created_at < $2
)
SELECT bucket AS label, COUNT(*) AS value
FROM s
GROUP BY 1 ORDER BY 1

Source: app/models/reporting/inbox/ticket.rb

Verified: 2026-08-04T12:48:25Z