fix(captain-memory): isolate per-account failures in SilenceDetectorJob + fix typo

This commit is contained in:
Rodribm10 2026-04-19 01:01:28 -03:00
parent 833e76856e
commit fb6673664a
2 changed files with 38 additions and 4 deletions

View File

@ -5,15 +5,26 @@ class Captain::ContactMemories::SilenceDetectorJob < ApplicationJob
def perform
Account.where("custom_attributes->>'captain_contact_memory_extraction_enabled' = 'true'").find_each do |account|
elegible_conversation_ids(account).each do |conv_id|
Captain::ContactMemories::ExtractFromConversationJob.perform_later(conv_id)
end
process_account(account)
rescue StandardError => e
Rails.logger.error("[SilenceDetectorJob] account=#{account.id} failed: #{e.class}: #{e.message}")
ChatwootExceptionTracker.new(e, account: account).capture_exception
end
end
private
def elegible_conversation_ids(account)
def process_account(account)
eligible_conversation_ids(account).each do |conv_id|
Captain::ContactMemories::ExtractFromConversationJob.perform_later(conv_id)
end
end
# Intentionally no `.where('messages.created_at < ?', SILENCE_THRESHOLD.ago)` pre-filter here:
# that would let a conversation with ANY old message + a recent one be incorrectly
# classified as silent. The HAVING MAX(...) clause alone is the correct semantics.
# If this full-join becomes a perf issue at scale, rewrite as NOT EXISTS subquery.
def eligible_conversation_ids(account)
Conversation
.where(account_id: account.id)
.joins(:messages)

View File

@ -49,4 +49,27 @@ RSpec.describe Captain::ContactMemories::SilenceDetectorJob do
expect { described_class.perform_now }
.not_to have_enqueued_job(Captain::ContactMemories::ExtractFromConversationJob)
end
it 'continues processing other accounts when one account raises' do
bad_account = create(:account, custom_attributes: { 'captain_contact_memory_extraction_enabled' => true })
bad_contact = create(:contact, account: bad_account)
create(:conversation, account: bad_account, contact: bad_contact)
good_account = create(:account, custom_attributes: { 'captain_contact_memory_extraction_enabled' => true })
good_contact = create(:contact, account: good_account)
good_conv = create(:conversation, account: good_account, contact: good_contact)
create(:message, conversation: good_conv, account: good_account)
age_all_messages(good_conv, 35.minutes.ago)
original = described_class.instance_method(:eligible_conversation_ids)
allow_any_instance_of(described_class).to receive(:eligible_conversation_ids) do |instance, acc| # rubocop:disable RSpec/AnyInstance
raise StandardError, 'boom' if acc.id == bad_account.id
original.bind_call(instance, acc)
end
allow(ChatwootExceptionTracker).to receive(:new).and_return(instance_double(ChatwootExceptionTracker, capture_exception: true))
expect { described_class.perform_now }
.to have_enqueued_job(Captain::ContactMemories::ExtractFromConversationJob).with(good_conv.id)
end
end