feat: update seeder to match new reporting event
This commit is contained in:
@@ -13,6 +13,7 @@ class Seeders::Reports::ConversationCreator
|
||||
@teams = resources[:teams]
|
||||
@labels = resources[:labels]
|
||||
@agents = resources[:agents]
|
||||
@captain_assistant = resources[:captain_assistant]
|
||||
@priorities = [nil, 'urgent', 'high', 'medium', 'low']
|
||||
end
|
||||
|
||||
@@ -27,8 +28,17 @@ class Seeders::Reports::ConversationCreator
|
||||
conversation = build_conversation
|
||||
conversation.save!
|
||||
|
||||
# Trigger conversation_created event
|
||||
trigger_conversation_created_event(conversation)
|
||||
|
||||
add_labels_to_conversation(conversation)
|
||||
create_messages_for_conversation(conversation)
|
||||
|
||||
# Check if this is a bot inbox or human-only inbox
|
||||
if inbox_has_captain?(conversation.inbox)
|
||||
create_bot_conversation_flow(conversation, created_at)
|
||||
else
|
||||
create_human_conversation_flow(conversation)
|
||||
end
|
||||
|
||||
# Determine if should resolve but don't update yet
|
||||
should_resolve = rand > 0.3
|
||||
@@ -100,15 +110,88 @@ class Seeders::Reports::ConversationCreator
|
||||
conversation.update_labels(labels_to_add.map(&:title))
|
||||
end
|
||||
|
||||
def create_messages_for_conversation(conversation)
|
||||
def create_messages_for_conversation(conversation, include_bot: false)
|
||||
message_creator = Seeders::Reports::MessageCreator.new(
|
||||
account: @account,
|
||||
agents: @agents,
|
||||
conversation: conversation
|
||||
conversation: conversation,
|
||||
captain_assistant: include_bot ? @captain_assistant : nil
|
||||
)
|
||||
message_creator.create_messages
|
||||
end
|
||||
|
||||
def trigger_conversation_created_event(conversation)
|
||||
event_data = { conversation: conversation }
|
||||
|
||||
ReportingEventListener.instance.conversation_created(
|
||||
Events::Base.new('conversation_created', Time.current, event_data)
|
||||
)
|
||||
end
|
||||
|
||||
def inbox_has_captain?(inbox)
|
||||
return false unless @captain_assistant && inbox.respond_to?(:captain_assistant)
|
||||
|
||||
inbox.captain_assistant.present?
|
||||
end
|
||||
|
||||
def create_bot_conversation_flow(conversation, _created_at)
|
||||
message_creator = Seeders::Reports::MessageCreator.new(
|
||||
account: @account,
|
||||
agents: @agents,
|
||||
conversation: conversation,
|
||||
captain_assistant: @captain_assistant
|
||||
)
|
||||
|
||||
# Create initial customer message
|
||||
message_creator.create_incoming_message
|
||||
|
||||
# Add delay for bot response
|
||||
travel(rand((5.seconds)..(30.seconds)))
|
||||
|
||||
# Create bot response
|
||||
message_creator.create_bot_message
|
||||
|
||||
# Randomly decide outcome: 30% bot resolves, 50% handoff to human, 20% abandoned
|
||||
outcome = rand(100)
|
||||
|
||||
if outcome < 30
|
||||
# Bot resolves - no more messages needed, will be resolved later
|
||||
nil
|
||||
elsif outcome < 80
|
||||
# Bot hands off to human - use travel to move forward from current time
|
||||
travel(rand((1.minute)..(10.minutes)))
|
||||
trigger_bot_handoff_event(conversation)
|
||||
# Continue with human agent messages
|
||||
create_human_messages_after_handoff(message_creator)
|
||||
end
|
||||
# else: 20% abandoned - no further action
|
||||
end
|
||||
|
||||
def create_human_conversation_flow(conversation)
|
||||
create_messages_for_conversation(conversation)
|
||||
end
|
||||
|
||||
def create_human_messages_after_handoff(message_creator)
|
||||
# Create a few more human agent messages after handoff
|
||||
message_count = rand(2..4)
|
||||
message_count.times do |i|
|
||||
travel(rand((1.minute)..(30.minutes)))
|
||||
if i.even?
|
||||
message_creator.create_incoming_message
|
||||
else
|
||||
message_creator.create_human_outgoing_message
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def trigger_bot_handoff_event(conversation)
|
||||
event_data = { conversation: conversation }
|
||||
|
||||
ReportingEventListener.instance.conversation_bot_handoff(
|
||||
Events::Base.new('conversation_bot_handoff', Time.current, event_data)
|
||||
)
|
||||
end
|
||||
|
||||
def trigger_conversation_resolved_event(conversation)
|
||||
event_data = { conversation: conversation }
|
||||
|
||||
|
||||
@@ -8,10 +8,11 @@ class Seeders::Reports::MessageCreator
|
||||
|
||||
MESSAGES_PER_CONVERSATION = 5
|
||||
|
||||
def initialize(account:, agents:, conversation:)
|
||||
def initialize(account:, agents:, conversation:, captain_assistant: nil)
|
||||
@account = account
|
||||
@agents = agents
|
||||
@conversation = conversation
|
||||
@captain_assistant = captain_assistant
|
||||
end
|
||||
|
||||
def create_messages
|
||||
@@ -37,6 +38,44 @@ class Seeders::Reports::MessageCreator
|
||||
false # No longer first reply after any agent message
|
||||
end
|
||||
|
||||
def create_incoming_message
|
||||
@conversation.messages.create!(
|
||||
account: @account,
|
||||
inbox: @conversation.inbox,
|
||||
message_type: :incoming,
|
||||
content: generate_customer_message_content,
|
||||
sender: @conversation.contact
|
||||
)
|
||||
end
|
||||
|
||||
def create_bot_message
|
||||
return unless @captain_assistant
|
||||
|
||||
@conversation.messages.create!(
|
||||
account: @account,
|
||||
inbox: @conversation.inbox,
|
||||
message_type: :outgoing,
|
||||
content: generate_bot_response_content,
|
||||
sender: @captain_assistant
|
||||
)
|
||||
end
|
||||
|
||||
def create_human_outgoing_message
|
||||
sender = @conversation.assignee || @agents.sample
|
||||
|
||||
message = @conversation.messages.create!(
|
||||
account: @account,
|
||||
inbox: @conversation.inbox,
|
||||
message_type: :outgoing,
|
||||
content: generate_message_content,
|
||||
sender: sender
|
||||
)
|
||||
|
||||
# Trigger reply events for human messages after handoff
|
||||
trigger_reply_event(message)
|
||||
message
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def add_realistic_delay(_message_index, is_incoming)
|
||||
@@ -64,16 +103,6 @@ class Seeders::Reports::MessageCreator
|
||||
end
|
||||
end
|
||||
|
||||
def create_incoming_message
|
||||
@conversation.messages.create!(
|
||||
account: @account,
|
||||
inbox: @conversation.inbox,
|
||||
message_type: :incoming,
|
||||
content: generate_message_content,
|
||||
sender: @conversation.contact
|
||||
)
|
||||
end
|
||||
|
||||
def create_outgoing_message
|
||||
sender = @conversation.assignee || @agents.sample
|
||||
|
||||
@@ -90,6 +119,39 @@ class Seeders::Reports::MessageCreator
|
||||
Faker::Lorem.paragraph(sentence_count: rand(1..5))
|
||||
end
|
||||
|
||||
def generate_customer_message_content
|
||||
customer_messages = [
|
||||
'Hi, I need help with my order.',
|
||||
'Can you assist me with a billing issue?',
|
||||
'I have a question about my account.',
|
||||
"I'm having trouble logging in.",
|
||||
'How do I change my password?',
|
||||
'When will my order arrive?',
|
||||
"I'd like to request a refund.",
|
||||
'Can you help me update my shipping address?',
|
||||
'I received the wrong item.',
|
||||
'Is there a way to track my package?',
|
||||
Faker::Lorem.paragraph(sentence_count: rand(1..3))
|
||||
]
|
||||
customer_messages.sample
|
||||
end
|
||||
|
||||
def generate_bot_response_content
|
||||
bot_responses = [
|
||||
"Hello! I'm here to help. Let me look into that for you.",
|
||||
'Thank you for reaching out. I can assist you with this.',
|
||||
'I understand your concern. Let me check our records.',
|
||||
'Sure, I can help with that! Could you provide more details?',
|
||||
"I've found some information that might help.",
|
||||
'Let me connect you with a specialist who can better assist you.',
|
||||
'Based on your question, here are some options available to you.',
|
||||
'I can see your account details. Let me review this for you.',
|
||||
"Thank you for your patience. I'm processing your request now.",
|
||||
'Is there anything else I can help you with today?'
|
||||
]
|
||||
bot_responses.sample
|
||||
end
|
||||
|
||||
def handle_agent_reply_events(message, is_first_reply)
|
||||
if is_first_reply
|
||||
trigger_first_reply_event(message)
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
# to generate historical data with realistic timestamps.
|
||||
#
|
||||
# Usage:
|
||||
# ACCOUNT_ID=1 ENABLE_ACCOUNT_SEEDING=true bundle exec rake db:seed:reports_data
|
||||
# ENABLE_ACCOUNT_SEEDING=true bundle exec rake db:seed:reports_data
|
||||
#
|
||||
# This will create:
|
||||
# - 1000 conversations with realistic message exchanges
|
||||
@@ -25,29 +25,36 @@ require 'faker'
|
||||
require_relative 'conversation_creator'
|
||||
require_relative 'message_creator'
|
||||
|
||||
# rubocop:disable Rails/Output
|
||||
# rubocop:disable Rails/Output, Metrics/ClassLength
|
||||
class Seeders::Reports::ReportDataSeeder
|
||||
include ActiveSupport::Testing::TimeHelpers
|
||||
|
||||
TOTAL_CONVERSATIONS = 1000
|
||||
TOTAL_CONTACTS = 100
|
||||
TOTAL_AGENTS = 20
|
||||
TOTAL_TEAMS = 5
|
||||
TOTAL_LABELS = 30
|
||||
TOTAL_INBOXES = 3
|
||||
MESSAGES_PER_CONVERSATION = 5
|
||||
START_DATE = 3.months.ago # rubocop:disable Rails/RelativeDateConstant
|
||||
END_DATE = Time.current
|
||||
|
||||
def initialize(account:)
|
||||
def initialize(account:, config: {})
|
||||
raise 'Account Seeding is not allowed.' unless ENV.fetch('ENABLE_ACCOUNT_SEEDING', !Rails.env.production?)
|
||||
|
||||
@account = account
|
||||
initialize_config(config)
|
||||
@teams = []
|
||||
@agents = []
|
||||
@labels = []
|
||||
@inboxes = []
|
||||
@contacts = []
|
||||
@captain_assistant = nil
|
||||
end
|
||||
|
||||
def initialize_config(config)
|
||||
defaults = { total_conversations: 1000, total_contacts: 100, total_agents: 20,
|
||||
total_teams: 5, total_labels: 30, days_range: 90 }
|
||||
config = defaults.merge(config)
|
||||
|
||||
@total_conversations = config[:total_conversations]
|
||||
@total_contacts = config[:total_contacts]
|
||||
@total_agents = config[:total_agents]
|
||||
@total_teams = config[:total_teams]
|
||||
@total_labels = config[:total_labels]
|
||||
@days_range = config[:days_range]
|
||||
end
|
||||
|
||||
def perform!
|
||||
@@ -56,6 +63,7 @@ class Seeders::Reports::ReportDataSeeder
|
||||
# Clear existing data
|
||||
clear_existing_data
|
||||
|
||||
create_captain_assistant
|
||||
create_teams
|
||||
create_agents
|
||||
create_labels
|
||||
@@ -78,26 +86,38 @@ class Seeders::Reports::ReportDataSeeder
|
||||
@account.contacts.destroy_all
|
||||
@account.agents.destroy_all
|
||||
@account.reporting_events.destroy_all
|
||||
Captain::Assistant.where(account: @account).destroy_all if defined?(Captain::Assistant)
|
||||
end
|
||||
|
||||
def create_captain_assistant
|
||||
return unless defined?(Captain::Assistant)
|
||||
|
||||
@captain_assistant = Captain::Assistant.create!(
|
||||
account: @account,
|
||||
name: 'Support Bot',
|
||||
description: 'AI assistant for customer support'
|
||||
)
|
||||
puts 'Created Captain assistant: Support Bot'
|
||||
end
|
||||
|
||||
def create_teams
|
||||
TOTAL_TEAMS.times do |i|
|
||||
@total_teams.times do |i|
|
||||
team = @account.teams.create!(
|
||||
name: "#{Faker::Company.industry} Team #{i + 1}"
|
||||
)
|
||||
@teams << team
|
||||
print "\rCreating teams: #{i + 1}/#{TOTAL_TEAMS}"
|
||||
print "\rCreating teams: #{i + 1}/#{@total_teams}"
|
||||
end
|
||||
|
||||
print "\n"
|
||||
end
|
||||
|
||||
def create_agents
|
||||
TOTAL_AGENTS.times do |i|
|
||||
@total_agents.times do |i|
|
||||
user = create_single_agent(i)
|
||||
assign_agent_to_teams(user)
|
||||
@agents << user
|
||||
print "\rCreating agents: #{i + 1}/#{TOTAL_AGENTS}"
|
||||
print "\rCreating agents: #{i + 1}/#{@total_agents}"
|
||||
end
|
||||
|
||||
print "\n"
|
||||
@@ -134,22 +154,33 @@ class Seeders::Reports::ReportDataSeeder
|
||||
end
|
||||
|
||||
def create_labels
|
||||
TOTAL_LABELS.times do |i|
|
||||
@total_labels.times do |i|
|
||||
label = @account.labels.create!(
|
||||
title: "Label-#{i + 1}-#{Faker::Lorem.word}",
|
||||
description: Faker::Company.catch_phrase,
|
||||
color: Faker::Color.hex_color
|
||||
)
|
||||
@labels << label
|
||||
print "\rCreating labels: #{i + 1}/#{TOTAL_LABELS}"
|
||||
print "\rCreating labels: #{i + 1}/#{@total_labels}"
|
||||
end
|
||||
|
||||
print "\n"
|
||||
end
|
||||
|
||||
def create_inboxes
|
||||
TOTAL_INBOXES.times do |_i|
|
||||
inbox = create_single_inbox
|
||||
# Create inboxes: 2/3 with Captain (bot inboxes) and 1/3 without (human-only)
|
||||
bot_inbox_count = (TOTAL_INBOXES * 2 / 3.0).ceil
|
||||
human_inbox_count = TOTAL_INBOXES - bot_inbox_count
|
||||
|
||||
bot_inbox_count.times do |i|
|
||||
inbox = create_web_inbox_with_captain("Support Bot Inbox #{i + 1}")
|
||||
assign_agents_to_inbox(inbox)
|
||||
@inboxes << inbox
|
||||
print "\rCreating inboxes: #{@inboxes.size}/#{TOTAL_INBOXES}"
|
||||
end
|
||||
|
||||
human_inbox_count.times do |i|
|
||||
inbox = create_web_inbox("Human Support Inbox #{i + 1}")
|
||||
assign_agents_to_inbox(inbox)
|
||||
@inboxes << inbox
|
||||
print "\rCreating inboxes: #{@inboxes.size}/#{TOTAL_INBOXES}"
|
||||
@@ -158,14 +189,27 @@ class Seeders::Reports::ReportDataSeeder
|
||||
print "\n"
|
||||
end
|
||||
|
||||
def create_single_inbox
|
||||
def create_web_inbox_with_captain(name)
|
||||
inbox = create_web_inbox(name)
|
||||
|
||||
if @captain_assistant && defined?(CaptainInbox)
|
||||
CaptainInbox.create!(
|
||||
captain_assistant: @captain_assistant,
|
||||
inbox: inbox
|
||||
)
|
||||
end
|
||||
|
||||
inbox
|
||||
end
|
||||
|
||||
def create_web_inbox(name)
|
||||
channel = Channel::WebWidget.create!(
|
||||
website_url: "https://#{Faker::Internet.domain_name}",
|
||||
account_id: @account.id
|
||||
)
|
||||
|
||||
@account.inboxes.create!(
|
||||
name: "#{Faker::Company.name} Website",
|
||||
name: name,
|
||||
channel: channel
|
||||
)
|
||||
end
|
||||
@@ -176,9 +220,10 @@ class Seeders::Reports::ReportDataSeeder
|
||||
@agents
|
||||
else
|
||||
# Subsequent inboxes get random selection with some overlap
|
||||
min_agents = [@agents.size / TOTAL_INBOXES, 10].max
|
||||
max_agents = [(@agents.size * 0.8).to_i, 50].min
|
||||
@agents.sample(rand(min_agents..max_agents))
|
||||
min_agents = [(@agents.size / TOTAL_INBOXES), 1].max
|
||||
max_agents = [(@agents.size * 0.8).to_i, @agents.size].min
|
||||
sample_count = rand([min_agents, max_agents].min..[min_agents, max_agents].max)
|
||||
@agents.sample(sample_count)
|
||||
end
|
||||
|
||||
agents_to_assign.each do |agent|
|
||||
@@ -187,7 +232,7 @@ class Seeders::Reports::ReportDataSeeder
|
||||
end
|
||||
|
||||
def create_contacts
|
||||
TOTAL_CONTACTS.times do |i|
|
||||
@total_contacts.times do |i|
|
||||
contact = @account.contacts.create!(
|
||||
name: Faker::Name.name,
|
||||
email: Faker::Internet.email,
|
||||
@@ -202,7 +247,7 @@ class Seeders::Reports::ReportDataSeeder
|
||||
)
|
||||
@contacts << contact
|
||||
|
||||
print "\rCreating contacts: #{i + 1}/#{TOTAL_CONTACTS}"
|
||||
print "\rCreating contacts: #{i + 1}/#{@total_contacts}"
|
||||
end
|
||||
|
||||
print "\n"
|
||||
@@ -216,19 +261,20 @@ class Seeders::Reports::ReportDataSeeder
|
||||
inboxes: @inboxes,
|
||||
teams: @teams,
|
||||
labels: @labels,
|
||||
agents: @agents
|
||||
agents: @agents,
|
||||
captain_assistant: @captain_assistant
|
||||
}
|
||||
)
|
||||
|
||||
TOTAL_CONVERSATIONS.times do |i|
|
||||
created_at = Faker::Time.between(from: START_DATE, to: END_DATE)
|
||||
@total_conversations.times do |i|
|
||||
created_at = Faker::Time.between(from: @days_range.days.ago, to: Time.current)
|
||||
conversation_creator.create_conversation(created_at: created_at)
|
||||
|
||||
completion_percentage = ((i + 1).to_f / TOTAL_CONVERSATIONS * 100).round
|
||||
print "\rCreating conversations: #{i + 1}/#{TOTAL_CONVERSATIONS} (#{completion_percentage}%)"
|
||||
completion_percentage = ((i + 1).to_f / @total_conversations * 100).round
|
||||
print "\rCreating conversations: #{i + 1}/#{@total_conversations} (#{completion_percentage}%)"
|
||||
end
|
||||
|
||||
print "\n"
|
||||
end
|
||||
end
|
||||
# rubocop:enable Rails/Output
|
||||
# rubocop:enable Rails/Output, Metrics/ClassLength
|
||||
|
||||
@@ -1,24 +1,53 @@
|
||||
# rubocop:disable Rails/Output, Metrics/BlockLength
|
||||
namespace :db do
|
||||
namespace :seed do
|
||||
desc 'Seed test data for reports with conversations, contacts, agents, teams, and realistic reporting events'
|
||||
task reports_data: :environment do
|
||||
if ENV['ACCOUNT_ID'].blank?
|
||||
puts 'Please provide an ACCOUNT_ID environment variable'
|
||||
puts 'Usage: ACCOUNT_ID=1 ENABLE_ACCOUNT_SEEDING=true bundle exec rake db:seed:reports_data'
|
||||
exit 1
|
||||
end
|
||||
abort 'This task can only be run in development environment.' unless Rails.env.development?
|
||||
abort 'ENABLE_ACCOUNT_SEEDING must be set to true.' unless ENV['ENABLE_ACCOUNT_SEEDING'] == 'true'
|
||||
|
||||
ENV['ENABLE_ACCOUNT_SEEDING'] = 'true' if ENV['ENABLE_ACCOUNT_SEEDING'].blank?
|
||||
account = prompt_for_account
|
||||
config = prompt_for_config
|
||||
abort 'Seeding cancelled.' unless confirm_seeding(account)
|
||||
|
||||
account_id = ENV.fetch('ACCOUNT_ID', nil)
|
||||
account = Account.find(account_id)
|
||||
|
||||
puts "Starting reports data seeding for account: #{account.name} (ID: #{account.id})"
|
||||
|
||||
seeder = Seeders::Reports::ReportDataSeeder.new(account: account)
|
||||
seeder = Seeders::Reports::ReportDataSeeder.new(account: account, config: config)
|
||||
seeder.perform!
|
||||
|
||||
puts "Finished seeding reports data for account: #{account.name}"
|
||||
end
|
||||
|
||||
def prompt(message)
|
||||
print "#{message}: "
|
||||
$stdin.gets.chomp
|
||||
end
|
||||
|
||||
def prompt_with_default(message, default)
|
||||
input = prompt("#{message} (default: #{default})")
|
||||
input.blank? ? default : input.to_i
|
||||
end
|
||||
|
||||
def prompt_for_account
|
||||
input = prompt('Enter Account ID')
|
||||
account = Account.find_by(id: input)
|
||||
abort "Account with ID #{input} not found." if account.nil?
|
||||
account
|
||||
end
|
||||
|
||||
def prompt_for_config
|
||||
{
|
||||
total_conversations: prompt_with_default('Total conversations', 1000),
|
||||
total_contacts: prompt_with_default('Total contacts', 100),
|
||||
total_agents: prompt_with_default('Total agents', 20),
|
||||
total_teams: prompt_with_default('Total teams', 5),
|
||||
total_labels: prompt_with_default('Total labels', 30),
|
||||
days_range: prompt_with_default('Days of data to generate', 90)
|
||||
}
|
||||
end
|
||||
|
||||
def confirm_seeding(account)
|
||||
puts "\nWARNING: This will DELETE all existing data for account '#{account.name}' (ID: #{account.id})"
|
||||
prompt('Are you sure you want to proceed? (yes/no)').downcase == 'yes'
|
||||
end
|
||||
end
|
||||
end
|
||||
# rubocop:enable Rails/Output, Metrics/BlockLength
|
||||
|
||||
Reference in New Issue
Block a user