bootsinthepark · everything up to 2026-08-03 · every figure double-checked
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% 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.
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.
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.
One entry per figure: what it means, the SQL that made it, and when it was last re-checked.
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 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
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
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
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
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