Class: ProblematicDeliveryDigestWorker

Inherits:
Object
  • Object
show all
Includes:
Sidekiq::Job
Defined in:
app/workers/problematic_delivery_digest_worker.rb

Overview

Daily roll-up of every currently-flagged problematic delivery, mailed to
orders@ via ProblematicDeliveryDigestMailer.

Split of responsibilities with ProblematicDeliverySweep: the sweep runs
hourly and owns DETECTION — it decides what is problematic and records each
first sighting as an Events::ProblematicDeliveryDetected. This worker owns
NOTIFICATION and runs once a day, reading the sweep's current verdict rather
than keeping its own.

Why the split: the sweep used to email on each detection, and that put a
per-incident message in a sales rep's inbox. Sales asked off it on
2026-08-06 — "we lack the time to manage delayed shipments; this is an
operational and logistical matter rather than a sales responsibility" — and
a per-incident email can't answer the reader's only question, which is what
changed since yesterday. Reading ProblematicDeliverySweep.flagged fresh
each morning also means anything delivered or dismissed overnight simply
isn't in the message, with no state of our own to expire.

Scheduled daily via sidekiq-scheduler — see
config/sidekiq_production_schedule.yml and
config/sidekiq_staging_schedule.yml.

Constant Summary collapse

NEW_SINCE =

A row counts as new when its detection event landed inside this window —
one digest interval, so "new" means "you have not seen this in a digest
before". Slightly wider than 24h so a late-running job doesn't silently
drop a row's NEW marker.

25.hours

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.rowsArray<Hash>

Every flagged delivery, critical first and newest-scan first within a
severity, decorated with what the digest needs to render.

Returns:

  • (Array<Hash>)

    keys +:shipment+, +:reason+, +:severity+, +:new+,
    +:last_event+



57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'app/workers/problematic_delivery_digest_worker.rb', line 57

def rows
  recent = recently_detected_shipment_ids

  ProblematicDeliverySweep.flagged.map do |shipment, reason|
    events = ProblematicDeliverySweep.scans_for(shipment)
    {
      shipment: shipment,
      reason: reason,
      severity: ProblematicDeliverySweep.severity(shipment, reason),
      new: recent.include?(shipment.id),
      last_event: events.max_by(&:occurred_at)
    }
  end.sort_by { |row| [row[:severity] == :critical ? 0 : 1, -row[:last_event]&.occurred_at.to_i] }
end

Instance Method Details

#performObject

Runs the job.

Returns:

  • (Object)

    the result



38
39
40
41
42
43
44
45
46
47
48
49
# File 'app/workers/problematic_delivery_digest_worker.rb', line 38

def perform
  rows = self.class.rows
  if rows.empty?
    Rails.logger.info '[ProblematicDeliveryDigestWorker] nothing flagged — no digest sent'
    return
  end

  Rails.logger.info "[ProblematicDeliveryDigestWorker] #{rows.size} flagged " \
                    "(#{rows.count { |r| r[:severity] == :critical }} critical, " \
                    "#{rows.count { |r| r[:new] }} new)"
  ProblematicDeliveryDigestMailer.digest(rows).deliver_now
end