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