+10.2% more happy customers than before Easol

What this meansHow much more often a ticket ends happy (4 or 5 out of 5) when AI was involved, against before Easol. Happiness is read by AI from the conversation, not asked in a survey. Both numbers behind it are in the charts below. Rounding can shift the last decimal. Checked 2026-08-04T12:48:25Z.

More conversations end happy than before Easol

68.9% against 62.5%

68.9% of tickets AI was involved in end at 4 or 5 out of 5. Before Easol, 62.5%. That is 10.3% more. AI involved pools AI alone (69.7%) and AI helped (66.6%), so it lands between them.

AI alone is strongest

69.7% for AI alone, 66.6% when AI helped, 62.5% before Easol. Average scores: 3.5, 3.44 and 3.35 out of 5.

How many tickets sit behind each bar

6,703 tickets have a score: 3,820 AI alone, 1,314 AI helped, 1,006 staff only, 563 before Easol. That makes 5,134 on the AI-involved side against 563 before Easol.

Worth knowing
  • Happiness is read by AI from the conversation, not asked in a survey.
  • Before and after are different months with different tickets, so this is not proof one caused the other.
  • 10.3% is the relative jump. In plain points it is 6.4: 62.5% to 68.9%.
Before Easol On Easol
Tickets with a mood score 6,735 count

Happy tickets: AI involved vs before Easol

Before Easol 62.5% AI involved 68.9%

Share of happy tickets (4–5)

Before Easol 62.5% Staff only 62.8% AI helped 66.6% AI alone 69.7%

Average happiness score (1–5)

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

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.

More happy customers than before Easol csat_uplift_vs_pre_easol_pct

How much more often a ticket ends happy (4 or 5 out of 5) when AI was involved, against before Easol. Happiness is read by AI from the conversation, not asked in a survey

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 COUNT(*) FILTER (WHERE inferred_satisfaction >= 4)::numeric / NULLIF(COUNT(*), 0)
   FROM s WHERE bucket IN ('full_ai', 'partial_ai'))
  / (SELECT COUNT(*) FILTER (WHERE inferred_satisfaction >= 4)::numeric / NULLIF(COUNT(*), 0)
     FROM s WHERE bucket = 'pre_easol') - 1
), 1) AS value

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

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

Happy tickets: AI involved vs before Easol csat_pct_ai_touched_vs_pre_easol

The exact pair behind the headline — happy tickets when AI was involved, against happy tickets before Easol

AI involved pools AI alone and AI helped, so it lands between those two, not on either. Read by AI, not asked in a survey

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 = 'pre_easol' THEN 'pre_easol' ELSE 'ai_touched' END AS label,
  ROUND(100.0 * COUNT(*) FILTER (WHERE inferred_satisfaction >= 4) / COUNT(*), 1) AS value
FROM s
WHERE bucket IN ('full_ai', 'partial_ai', 'pre_easol')
GROUP BY 1 ORDER BY 1

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

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

Share of happy tickets (4–5) csat_pct_by_bucket

Of scored tickets, the share ending at 4 or 5 out of 5, split by who handled them

Read by AI from the conversation, not asked in a survey

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

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

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