Class: InternalReportsMailer

Inherits:
InternalNotificationMailer show all
Defined in:
app/mailers/internal_reports_mailer.rb

Overview

Scheduled internal reports and digests (calls, leads, KPIs, daily focus).

Extracted from the former InternalMailer god object.

See Also:

  • InternalNotificationMailer
  • doc/tasks/202606131218_INTERNAL_MAILER_DECOMPOSITIONdoc/tasks/202606131218_INTERNAL_MAILER_DECOMPOSITION.md

Constant Summary collapse

DAILY_FOCUS_CC =

Daily focus cc.

%w[epasek@warmlyyours.com jbillen@warmlyyours.com mgodawa@warmlyyours.com].freeze
DAILY_FOCUS_DIGEST_RECIPIENTS =

Consolidated manager digest (DailyFocusManagerReportWorker); fixed list — not all sales_manager/sales_director roles.

%w[
  epasek@warmlyyours.com
  mgodawa@warmlyyours.com
  jbillen@warmlyyours.com
  srosenbaum@warmlyyours.com
].freeze

Instance Method Summary collapse

Methods inherited from InternalNotificationMailer

#default_url_options

Methods included from SendgridSmtpApi::InternalMailerHeaders

#mail

Methods inherited from ApplicationMailer

#null_mail

Instance Method Details

#activities_reportObject



138
139
140
141
142
143
144
145
146
147
148
149
150
# File 'app/mailers/internal_reports_mailer.rb', line 138

def activities_report
  options = {}
  options[:date] = Date.current.to_date
  options[:employee_ids] = ['']
  options[:role_ids] = ['']
  @report_command = ::Report::ActivityPerformance::Command.new(options)
  @results = @report_command.execute

  mail(from: 'Heatwave Team <heatwaveteam@warmlyyours.com>',
       to: 'jbillen@warmlyyours.com;epasek@warmlyyours.com;srosenbaum@warmlyyours.com',
       cc: 'heatwaveteam@warmlyyours.com',
       subject: 'Activities Report')
end

#amazon_repricing_report(report_data) ⇒ Object

Sends a summary report after Amazon pricing automation runs.
Reports on price increases, price decreases, and blocked price raises.

Parameters:

  • report_data (Hash)

    containing:

    • processed_count: total items processed
    • price_increased_count: items that had price raised
    • price_lowered_count: items that had price lowered
    • flagged_count: items flagged for manual review
    • blocked_by_external: array of items blocked by external retailer prices
    • messages: array of status messages


192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
# File 'app/mailers/internal_reports_mailer.rb', line 192

def amazon_repricing_report(report_data)
  @report_data = report_data
  @processed_count = report_data[:processed_count] || 0
  @price_increased_count = report_data[:price_increased_count] || 0
  @price_lowered_count = report_data[:price_lowered_count] || 0
  @flagged_count = report_data[:flagged_count] || 0
  @blocked_by_external = report_data[:blocked_by_external] || []
  @messages = report_data[:messages] || []

  # Skip email if nothing significant happened
  return null_mail if @processed_count.zero? && @blocked_by_external.empty?

  # Only send if there were price changes or blocks
  return null_mail if @price_increased_count.zero? && @price_lowered_count.zero? && @blocked_by_external.empty?

  subject_parts = []
  subject_parts << "#{@price_increased_count} raised" if @price_increased_count.positive?
  subject_parts << "#{@price_lowered_count} lowered" if @price_lowered_count.positive?
  subject_parts << "#{@blocked_by_external.size} blocked" if @blocked_by_external.any?

  subject = "Amazon Repricing Report: #{subject_parts.join(', ')}"

  mail(from: 'Heatwave Team <heatwaveteam@warmlyyours.com>',
       to: 'ediadmin@warmlyyours.com',
       cc: 'heatwaveteam@warmlyyours.com',
       subject: subject)
end

#auto_schema_extraction_report(report_data) ⇒ Object



169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'app/mailers/internal_reports_mailer.rb', line 169

def auto_schema_extraction_report(report_data)
  @report_data = report_data
  @articles_processed = report_data[:articles_processed] || 0
  @errors = report_data[:errors] || 0
  @total_articles = report_data[:total_articles] || 0
  @message = report_data[:message] || 'No message provided'
  @processed_articles = report_data[:processed_articles] || []
  @error_articles = report_data[:error_articles] || []

  mail(from: 'Heatwave Team <heatwaveteam@warmlyyours.com>',
       to: 'Heatwave Team <heatwaveteam@warmlyyours.com>',
       subject: "Auto Schema Extraction Report - #{@articles_processed} articles processed")
end

#call_recording_gaps_reportObject

Scheduled sysadmin report: connected call legs that should have a recording
but didn't match any imported audio. CSV-attached when there are gaps.



281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
# File 'app/mailers/internal_reports_mailer.rb', line 281

def call_recording_gaps_report
  @report = Report::CallRecordingGaps.for_scheduled_run
  if @report.gap_count.positive?
    attachments["call-recording-gaps-#{@report.date.strftime('%Y-%m-%d')}.csv"] =
      { mime_type: 'text/csv', content: @report.to_csv }
  end

  subject = "Call recording gaps — #{@report.date.strftime('%Y-%m-%d')}: " \
            "#{@report.absent_count} missing audio, #{@report.unlinked_count} unlinked " \
            "of #{@report.expected} (#{@report.coverage_rate}% linked)"

  mail(from: 'Heatwave Team <heatwaveteam@warmlyyours.com>',
       to: 'sysadmin@warmlyyours.com',
       subject: subject)
end

#call_statistics_report(date = nil) ⇒ Object



22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'app/mailers/internal_reports_mailer.rb', line 22

def call_statistics_report(date = nil)
  date ||= 1.working.day.ago.to_time

  options = {}
  options[:period1_gteq] = date.beginning_of_day
  options[:period1_lteq] = date.end_of_day
  options[:departments] = ['Accounting', 'Business Development', 'Customer Service', 'Sales', 'Tech Support', 'Warehouse']
  options[:employee_ids] = []
  @result = Report::CallStatistics::Query.report(options)
  subject_dep = 'Inbound/Outbound calls per rep.'
  mail(from: 'Heatwave Team <heatwaveteam@warmlyyours.com>',
       to: 'call_report@warmlyyours.com',
       cc: 'heatwaveteam@warmlyyours.com',
       subject: subject_dep)
end

#campaign_summary(campaign_emails) ⇒ Object



159
160
161
162
163
164
165
166
167
# File 'app/mailers/internal_reports_mailer.rb', line 159

def campaign_summary(campaign_emails)
  @campaign_emails = campaign_emails
  return null_mail if @campaign_emails.blank?

  mail(from: ADMINISTRATOR_EMAIL,
       to: "#{MARKETING_TEAM_EMAIL}, epasek@warmlyyours.com",
       cc: 'heatwaveteam@warmlyyours.com',
       subject: 'Heatwave Campaign Manager Monthly Summary')
end

#daily_account_activity_reportObject



85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'app/mailers/internal_reports_mailer.rb', line 85

def 
  report = AccountDailyActivityReport.for_scheduled_run
  @report_date = report.date
  @report_rolling_hours = report.rolling_hours
  @window_start = report.window_start
  @window_end = report.window_end
  created = report.accounts_created_by_group
  @accounts_created_www = created[:www]
  @accounts_created_crm = created[:crm]
  @accounts_created_other = created[:other]
  @login_events_by_group = report.

  subject = if @report_rolling_hours
              "Account activity — last #{@report_rolling_hours}h (America/Chicago)"
            else
              "Daily account activity — #{@report_date.strftime('%Y-%m-%d')} (America/Chicago)"
            end

  mail(from: 'Heatwave Team <heatwaveteam@warmlyyours.com>',
       to: 'sysadmin@warmlyyours.com',
       subject: subject)
end

#daily_focus_approved(employee:, conversation:) ⇒ Object



260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
# File 'app/mailers/internal_reports_mailer.rb', line 260

def daily_focus_approved(employee:, conversation:)
  @employee = employee
  @conversation = conversation

  raw_content = conversation.assistant_messages
                            .where(role: 'assistant')
                            .order(:id)
                            .last
                            &.content
  @analysis_html = (Assistant::ResponseFormatter.new(raw_content).format if raw_content.present?)

  mail(
    from: 'Heatwave Team <heatwaveteam@warmlyyours.com>',
    to: @employee.email,
    cc: DAILY_FOCUS_CC,
    subject: "Your Daily Focus for #{Date.current.strftime('%A, %B %-d')} is ready"
  )
end

#daily_focus_digest(conversations:, recipient_emails:) ⇒ Object



249
250
251
252
253
254
255
256
257
258
# File 'app/mailers/internal_reports_mailer.rb', line 249

def daily_focus_digest(conversations:, recipient_emails:)
  @conversations = conversations
  @date = Date.current
  @crm_host = CRM_HOSTNAME

  mail(
    to: recipient_emails,
    subject: "Daily Focus Digest — #{@date.strftime('%A, %B %-d, %Y')} (#{conversations.size} reps)"
  )
end

#daily_import_summaryObject

Daily digest of the call-record import + transcription pipeline health.



298
299
300
301
302
303
304
305
306
307
308
309
# File 'app/mailers/internal_reports_mailer.rb', line 298

def daily_import_summary
  @report = Report::DailyImportSummary.for_scheduled_run

  flag = @report.all_clear? ? '' : "#{@report.issues.size} issue(s):"
  subject = "#{flag} Imports #{@report.date.strftime('%Y-%m-%d')}" \
            "#{@report.imported_calls} calls, #{@report.voicemails} voicemails, " \
            "#{@report.transcribed_ok} transcribed, #{@report.transcription_errors} error(s)"

  mail(from: 'Heatwave Team <heatwaveteam@warmlyyours.com>',
       to: 'heatwaveteam@warmlyyours.com',
       subject: subject)
end

#dealer_locator_communication_report(locator_records, activity_task) ⇒ Object



129
130
131
132
133
134
135
136
# File 'app/mailers/internal_reports_mailer.rb', line 129

def dealer_locator_communication_report(locator_records, activity_task)
  @locator_records = locator_records
  @activity_task = activity_task
  mail(from: 'Heatwave Team <heatwaveteam@warmlyyours.com>',
       to: 'epasek@warmlyyours.com',
       cc: 'heatwaveteam@warmlyyours.com',
       subject: "Locator Record: #{activity_task}")
end

#kpi_call_report(department, recipient) ⇒ Object



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'app/mailers/internal_reports_mailer.rb', line 38

def kpi_call_report(department, recipient)
  end_date = Date.current - 3
  start_date = end_date.beginning_of_week

  options = {}
  options[:period1_gteq] = start_date
  options[:period1_lteq] = end_date
  options[:departments] = [department]
  options[:employee_ids] = []
  @result = Report::KpiCall::KpiCall.result_report(options)
  subject_dep = "KPI Call Report #{department}"
  mail(from: 'Heatwave Team <heatwaveteam@warmlyyours.com>',
       to: recipient,
       cc: 'jbillen@warmlyyours.com;epasek@warmlyyours.com;mgodawa@warmlyyours.com',
       subject: subject_dep)
end

#lead_email_reportObject



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'app/mailers/internal_reports_mailer.rb', line 55

def lead_email_report
  date = Date.current - 3
  sql = <<-SQL.squish
    select
    timezone('America/Chicago', a.created_at)::date,
    c.id as customer_id,
    'CN' || c.id::varchar as customer_number,
    c.full_name as customer_name,
    c.state as customer_state,
    s.full_name as visit_source_name,
    at.task_type as lead_activity_type,
    v.id as visit_id,
    a.id as activity_id
    from activities a
    inner join visits v on v.id = a.visit_id
    left join sources s on s.id = v.source_id
    inner join activity_types at on at.id = a.activity_type_id
    inner join parties c on c.id = a.customer_id
    where at.task_type LIKE 'LEAD%'
    and timezone('America/Chicago', a.created_at)::date = '#{date}'
    order by a.created_at desc
  SQL

  @result = ActiveRecord::Base.lease_connection.execute(sql).to_a.map(&:symbolize_keys)

  mail(from: 'Heatwave Team <heatwaveteam@warmlyyours.com>',
       to: 'jbillen@warmlyyours.com;',
       subject: 'Leads Created Yesterday')
end

#llm_model_sync_report(report_data) ⇒ Object

Weekly LLM model sync report.
Sent after WeeklyLlmModelSyncWorker runs (only when something changed).
Includes a stale-defaults action item when LlmDefaults constants are behind.

Parameters:

  • report_data (Hash)

    :models_total [Integer] total models in llm_models after sync
    :models_added [Integer] net new models added this run
    :synced_at [Time] when the sync completed
    :stale_defaults [Hash] output of LlmDefaults.stale_defaults (empty = all good)



229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
# File 'app/mailers/internal_reports_mailer.rb', line 229

def llm_model_sync_report(report_data)
  @models_total       = report_data[:models_total]
  @models_added       = report_data[:models_added]
  @new_models         = report_data[:new_models] || []
  @synced_at          = report_data[:synced_at]
  @stale_defaults     = report_data[:stale_defaults] || {}
  @unavailable_models = report_data[:unavailable_models] || {}

  issue_count = @stale_defaults.size + @unavailable_models.size
  subject = if issue_count > 0
              "⚠️ LLM Model Sync — #{issue_count} issue(s) need attention"
            else
              "✅ LLM Model Sync — #{@models_total} models, all constants current"
            end

  mail(from: 'Heatwave Team <heatwaveteam@warmlyyours.com>',
       to:   'Heatwave Team <heatwaveteam@warmlyyours.com>',
       subject: subject)
end

#report_mailer(report_command) ⇒ Object



152
153
154
155
156
157
# File 'app/mailers/internal_reports_mailer.rb', line 152

def report_mailer(report_command)
  report_command.execute
  @report_command = report_command
  @report_partial = report_command.email_partial
  mail(report_command.email_parameters)
end

#retailer_compliance_reportObject

Daily per-retailer compliance digest (MAP violations, listing issues, scrape
failures, out-of-stock, promotions, etc.) for the externally price-checked
catalogs plus the Amazon Seller catalogs. Scheduled ~6 AM CT, after
retailer_probe_worker and listing_issue_sync_worker (see
config/sidekiq_production_schedule.yml).



113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'app/mailers/internal_reports_mailer.rb', line 113

def retailer_compliance_report
  @report = Retailer::DailyComplianceReport.for_scheduled_run
  @generated_at = @report.generated_at
  @rows = @report.rows
  @rows_by_region = @report.rows_by_region
  @totals = @report.totals
  @flagged_rows = @report.flagged_rows

  subject = "Retailer compliance — #{@totals[:map_violations]} MAP violations, " \
            "#{@totals[:listing_issues]} listing issues (#{@generated_at.strftime('%Y-%m-%d')})"

  mail(from: 'Heatwave Team <heatwaveteam@warmlyyours.com>',
       to: 'cbillen@warmlyyours.com',
       subject: subject)
end