Class: InternalReportsMailer
- Inherits:
-
InternalNotificationMailer
- Object
- ActionMailer::Base
- ApplicationMailer
- InternalNotificationMailer
- InternalReportsMailer
- 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.
Constant Summary collapse
- DAILY_FOCUS_CC =
Daily focus cc.
%w[epasek@warmlyyours.com jbillen@warmlyyours.com mgodawa@warmlyyours.com].freeze
- OUTLET_PURCHASE_DIGEST_CC =
Outlet-purchase digest (
OutletPurchaseDigestWorker). Fixed list rather
than the sales_manager/accounting_rep roles that hold the verification
ability — the reviewers were named explicitly, and a role sweep would mail
people who never asked for it. %w[ vadepu@warmlyyours.com epasek@warmlyyours.com mgodawa@warmlyyours.com jbillen@warmlyyours.com cbillen@warmlyyours.com ocamuendo@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
-
#activities_report ⇒ Mail::Message
Today's activities across the team.
-
#amazon_repricing_report(report_data) ⇒ Mail::Message
Sends a summary report after Amazon pricing automation runs.
- #auto_schema_extraction_report(report_data) ⇒ Mail::Message
-
#call_statistics_report(date = nil) ⇒ Mail::Message
Per-department call statistics for a single working day.
-
#campaign_summary(campaign_emails) ⇒ Mail::Message
Monthly campaign-manager summary.
-
#daily_account_activity_report ⇒ Mail::Message
Yesterday's account activity, from AccountDailyActivityReport.
-
#daily_call_record_report ⇒ Mail::Message
Consolidated daily call-record health email — one message covering BOTH the prior day's import/transcription pipeline health (Report::DailyImportSummary) and the recording-gap reconciliation (Report::CallRecordingGaps).
-
#daily_focus_approved(employee:, conversation:) ⇒ Mail::Message
One rep's approved Daily Focus, sent to that rep.
-
#daily_focus_digest(conversations:, recipient_emails:) ⇒ Mail::Message
One digest covering every rep's Daily Focus conversation for today.
-
#dealer_locator_communication_report(locator_records, activity_task) ⇒ Mail::Message
Locator records attached to one activity task, for the rep working it.
-
#kpi_call_report(department, recipient) ⇒ Mail::Message
Weekly KPI call figures for one department, addressed to one recipient.
-
#lead_email_report ⇒ Mail::Message
Leads created three days ago, so the follow-up window has had time to run.
-
#llm_model_sync_report(report_data) ⇒ Mail::Message
Weekly LLM model sync report.
-
#outlet_purchase_digest(since: 1.day.ago) ⇒ Mail::Message
Yesterday's outlet-purchase matches: customers who bought through a retailer instead of ordering direct.
-
#report_mailer(report_command) ⇒ Mail::Message
Generic envelope for a report object that knows its own recipients and subject — the command supplies
email_parameters. -
#retailer_compliance_report(report: nil) ⇒ Mail::Message
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.
Methods inherited from InternalNotificationMailer
Methods included from SendgridSmtpApi::InternalMailerHeaders
Methods inherited from ApplicationMailer
Instance Method Details
#activities_report ⇒ Mail::Message
Today's activities across the team.
179 180 181 182 183 184 185 186 187 188 189 190 191 |
# File 'app/mailers/internal_reports_mailer.rb', line 179 def activities_report = {} [:date] = Date.current.to_date [:employee_ids] = [''] [:role_ids] = [''] @report_command = ::Report::ActivityPerformance::Command.new() @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) ⇒ Mail::Message
Sends a summary report after Amazon pricing automation runs.
Reports on price increases, price decreases, and blocked price raises.
247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 |
# File 'app/mailers/internal_reports_mailer.rb', line 247 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 @price_reverted_count = report_data[:price_reverted_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? && @price_reverted_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 << "#{@price_reverted_count} reverted" if @price_reverted_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) ⇒ Mail::Message
222 223 224 225 226 227 228 229 230 231 232 233 234 |
# File 'app/mailers/internal_reports_mailer.rb', line 222 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_statistics_report(date = nil) ⇒ Mail::Message
Per-department call statistics for a single working day.
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
# File 'app/mailers/internal_reports_mailer.rb', line 40 def call_statistics_report(date = nil) date ||= 1.working.day.ago.to_time = {} [:period1_gteq] = date.beginning_of_day [:period1_lteq] = date.end_of_day [:departments] = ['Accounting', 'Business Development', 'Customer Service', 'Sales', 'Tech Support', 'Warehouse'] [:employee_ids] = [] @result = Report::CallStatistics::Query.report() 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) ⇒ Mail::Message
Monthly campaign-manager summary. Sends nothing when the month had no
campaign emails.
210 211 212 213 214 215 216 217 218 |
# File 'app/mailers/internal_reports_mailer.rb', line 210 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_report ⇒ Mail::Message
Yesterday's account activity, from AccountDailyActivityReport.
114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 |
# File 'app/mailers/internal_reports_mailer.rb', line 114 def daily_account_activity_report 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.login_events_by_group 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_call_record_report ⇒ Mail::Message
Consolidated daily call-record health email — one message covering BOTH the
prior day's import/transcription pipeline health (Report::DailyImportSummary)
and the recording-gap reconciliation (Report::CallRecordingGaps). Replaces
the two former mailers (daily_import_summary + call_recording_gaps_report)
that fired separately at 7am and 8am. The gaps CSV is attached when there are
gaps. The subject flags ⚠ when either half is unhealthy (import issues OR
genuinely missing audio). Sent by DailyCallRecordReportWorker, which refreshes
the call-log↔recording links right before this runs.
358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 |
# File 'app/mailers/internal_reports_mailer.rb', line 358 def daily_call_record_report @import = Report::DailyImportSummary.for_scheduled_run @gaps = Report::CallRecordingGaps.for_scheduled_run if @gaps.gap_count.positive? ["call-recording-gaps-#{@gaps.date.strftime('%Y-%m-%d')}.csv"] = { mime_type: 'text/csv', content: @gaps.to_csv } end all_clear = @import.all_clear? && @gaps.absent_count.zero? subject = "#{all_clear ? '✓' : '⚠'} Call records #{@import.date.strftime('%Y-%m-%d')} — " \ "#{@import.imported_calls} imported, #{@import.transcribed_ok} transcribed; " \ "#{@gaps.absent_count} missing audio, #{@gaps.unlinked_count} unlinked " \ "of #{@gaps.expected} (#{@gaps.coverage_rate}% linked)" mail(from: 'Heatwave Team <heatwaveteam@warmlyyours.com>', to: 'heatwaveteam@warmlyyours.com', subject: subject) end |
#daily_focus_approved(employee:, conversation:) ⇒ Mail::Message
One rep's approved Daily Focus, sent to that rep.
329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 |
# File 'app/mailers/internal_reports_mailer.rb', line 329 def daily_focus_approved(employee:, conversation:) @employee = employee @conversation = conversation raw_content = conversation. .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:) ⇒ Mail::Message
One digest covering every rep's Daily Focus conversation for today.
313 314 315 316 317 318 319 320 321 322 |
# File 'app/mailers/internal_reports_mailer.rb', line 313 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 |
#dealer_locator_communication_report(locator_records, activity_task) ⇒ Mail::Message
Locator records attached to one activity task, for the rep working it.
167 168 169 170 171 172 173 174 |
# File 'app/mailers/internal_reports_mailer.rb', line 167 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) ⇒ Mail::Message
Weekly KPI call figures for one department, addressed to one recipient.
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
# File 'app/mailers/internal_reports_mailer.rb', line 61 def kpi_call_report(department, recipient) end_date = Date.current - 3 start_date = end_date.beginning_of_week = {} [:period1_gteq] = start_date [:period1_lteq] = end_date [:departments] = [department] [:employee_ids] = [] @result = Report::KpiCall::KpiCall.result_report() 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_report ⇒ Mail::Message
Leads created three days ago, so the follow-up window has had time to run.
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 |
# File 'app/mailers/internal_reports_mailer.rb', line 81 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) ⇒ Mail::Message
Weekly LLM model sync report.
Sent after WeeklyLlmModelSyncWorker runs (only when something changed).
Includes a stale-defaults action item when LlmDefaults constants are behind.
288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 |
# File 'app/mailers/internal_reports_mailer.rb', line 288 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 |
#outlet_purchase_digest(since: 1.day.ago) ⇒ Mail::Message
Yesterday's outlet-purchase matches: customers who bought through a retailer
instead of ordering direct.
Sent even when empty is NOT the intent — the worker skips a silent day, so a
digest arriving means there is something to look at.
386 387 388 389 390 391 392 393 394 395 396 397 398 399 |
# File 'app/mailers/internal_reports_mailer.rb', line 386 def outlet_purchase_digest(since: 1.day.ago) @since = since query = CustomerOutletPurchaseQuery.new.created_since(since) @purchases = query.for_display @total_revenue = query.total_revenue @awaiting_verification = CustomerOutletPurchaseQuery.new.awaiting_verification.relation.count @accepted = CustomerOutletPurchaseQuery.new.accepted.relation.count mail(from: 'Orders <orders@warmlyyours.com>', to: 'orders@warmlyyours.com', cc: OUTLET_PURCHASE_DIGEST_CC.join(', '), subject: "#{@purchases.size} new outlet purchase #{'match'.pluralize(@purchases.size)} " \ "(#{ActiveSupport::NumberHelper.number_to_currency(@total_revenue)})") end |
#report_mailer(report_command) ⇒ Mail::Message
Generic envelope for a report object that knows its own recipients and
subject — the command supplies email_parameters.
198 199 200 201 202 203 |
# File 'app/mailers/internal_reports_mailer.rb', line 198 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_report(report: nil) ⇒ Mail::Message
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).
146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 |
# File 'app/mailers/internal_reports_mailer.rb', line 146 def retailer_compliance_report(report: nil) @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 |