+4.3% happier customers when AI helps

What this meansHow much higher the average happiness score is when AI was involved, against tickets staff handled alone. AI reads each conversation and scores it 1 to 5. Both numbers behind it are in the charts below. Rounding can shift the last decimal. Checked 2026-08-04T12:48:25Z.

Customers score conversations higher when AI is involved

3.49 against 3.34

Tickets AI was involved in average 3.49 out of 5. Staff-only tickets average 3.34. That is 4.3% higher. AI involved pools AI alone (3.5) and AI helped (3.44).

AI alone leads

Out of 5: 3.5 for AI alone, 3.44 when AI helped, 3.34 for staff only.

More conversations read as sorted, too

64.1% of AI-only tickets read as sorted out, against 54.9% staff only and 54.6% when AI helped. 6,703 tickets have a score.

Worth knowing
  • Scores are read by AI from the conversation. Nobody asked the customer.
  • Only scored tickets count: 3,820 AI alone, 1,314 AI helped, 1,006 staff only.
  • Tickets are not shared out at random, so this compares different work, not the same work done two ways.
  • This is against staff-only tickets on Easol, not against before Easol, on a 1 to 5 scale where every group's middle score is 4.
Before Easol On Easol
Tickets with a mood score 6,735 count

Happiness score: AI involved vs staff alone

Staff only 3.34 AI involved 3.49

Average happiness score (1–5)

Before Easol 3.35 Staff only 3.34 AI helped 3.44 AI alone 3.5

Share that read as sorted

Before Easol 57.4% Staff only 54.8% AI helped 54.5% AI alone 63.9%

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.

Happier customers when AI helps ai_satisfaction_uplift_pct

How much higher the average happiness score is when AI was involved, against tickets staff handled alone. AI reads each conversation and scores it 1 to 5

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 ROUND(100.0 * (
  (SELECT AVG(inferred_satisfaction) FROM s WHERE bucket IN ('full_ai', 'partial_ai'))
  / (SELECT AVG(inferred_satisfaction) FROM s WHERE bucket = 'no_ai') - 1
), 1) AS value

Source: app/models/inbox/ticket_sentiment.rb:19 (validates :inferred_satisfaction); written at app/workers/inbox/analyze_sentiment_worker.rb:24

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

Tickets with a mood score tickets_with_sentiment

Tickets where AI has read the conversation and scored it

SELECT COUNT(*) 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

Source: app/models/inbox/ticket.rb:27 (Inbox::Ticket has_one :sentiment, class_name: Inbox::TicketSentiment); app/models/inbox/ticket_sentiment.rb:8-9 (Inbox::TicketSentiment belongs_to :ticket, table inbox_ticket_sentiments)

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

Happiness score: AI involved vs staff alone avg_satisfaction_ai_touched_vs_no_ai

The exact pair behind the headline — average score when AI was involved, against tickets staff handled alone

AI involved pools AI alone and AI helped, so it lands between those two, not on either

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
  CASE WHEN bucket = 'no_ai' THEN 'no_ai' ELSE 'ai_touched' END AS label,
  ROUND(AVG(inferred_satisfaction)::numeric, 2) AS value
FROM s
WHERE bucket IN ('full_ai', 'partial_ai', 'no_ai')
GROUP BY 1 ORDER BY 1

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

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

Average happiness score (1–5) avg_satisfaction_by_bucket

AI reads each conversation and scores how happy the customer seems, 1 to 5. Averaged by who handled the ticket

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.inferred_satisfaction)::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:19 (Inbox::TicketSentiment validates :inferred_satisfaction); written at app/workers/inbox/analyze_sentiment_worker.rb:24

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

Share that read as sorted sentiment_resolved_pct_by_bucket

Of scored tickets, the share where the conversation reads as sorted out, split by who handled them

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.resolution_status = 'resolved') / 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:11-12 (Inbox::TicketSentiment enum :resolution_status); written at app/workers/inbox/analyze_sentiment_worker.rb:25

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