Class: EmailTemplateDataPoint

Inherits:
ApplicationRecord show all
Includes:
Models::DataPointMetrics
Defined in:
app/models/email_template_data_point.rb

Overview

Daily time-series of email engagement per EmailTemplate, written by
EmailEngagementRollupWorker.

Exists because webhook_events retains only ~3 months. Counts (sends,
delivered, opened, clicked) can always be recomputed from the durable
terminal state on communication_recipients, but anything derived from event
TIMING cannot — once the events age out it is gone. median_hours_to_open
and opened_within_24h are the metrics that showed Saturday quote-expiration
notices sat ~50h before being read against 6-8h on weekdays; that analysis
was only possible inside the retention window.

The generic time-series machinery (scopes, trend/comparison API, period
helpers, idempotent upsert) lives in Models::DataPointMetrics — same shape
as SiteMapDataPoint and CatalogDataPoint.

Constant Summary collapse

DURABLE_METRICS =

Recomputable from communication_recipients at any time.

%w[sends delivered opened clicked bounced dropped
unsubscribed spammed machine_opened].freeze
PERISHABLE_METRICS =

Derived from webhook_events — unrecoverable once those age out.

%w[total_opens total_clicks median_hours_to_open
opened_within_24h].freeze
INVERTED_METRICS =

Lower is better: a slow open is a worse outcome, so trend arrows must
read a falling median as an improvement.

%w[bounced dropped unsubscribed spammed
median_hours_to_open].freeze

Constants included from Models::Schedulable

Models::Schedulable::SIMPLE_FORM_OPTIONS

Instance Attribute Summary

Attributes included from Models::DataPointMetrics

#metric_type, #period, #value

Belongs to collapse

Class Method Summary collapse

Methods included from Models::DataPointMetrics

by_period_start, by_recorded, containing_date, for_metric, for_reference, #inverted_metric?, inverted_metrics, latest, latest_values, overlapping, period_comparison, #period_days, #period_end, #period_start, recent, trend, trend_direction, upsert_data_points!

Methods inherited from ApplicationRecord

ransackable_associations, ransackable_attributes, ransackable_scopes, ransortable_attributes, #to_relation

Methods included from Models::Schedulable

config

Methods included from Models::AfterCommittable

#after_commit

Methods included from Models::EventPublishable

#publish_event

Class Method Details

.bulk_record!(email_template:, period_start:, period_end:, metrics:, reference: nil, source_batch_id: nil) ⇒ void

This method returns an undefined value.

Upserts a day's metrics for one template.

Parameters:

  • email_template (EmailTemplate)
  • period_start (Date)

    first day covered

  • period_end (Date)

    last day covered (inclusive)

  • metrics (Hash{Symbol,String=>Numeric})

    metric_type => value

  • reference (String, nil) (defaults to: nil)

    optional slice (e.g. send day-of-week)

  • source_batch_id (String, nil) (defaults to: nil)


46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'app/models/email_template_data_point.rb', line 46

def self.bulk_record!(email_template:, period_start:, period_end:, metrics:, reference: nil, source_batch_id: nil)
  # Inclusive daterange literal — the column is `daterange`, so a Ruby Range
  # would stringify into something Postgres rejects.
  period = "[#{period_start},#{period_end}]"

  rows = metrics.filter_map do |metric_type, value|
    next if value.nil?

    { email_template_id: email_template.id, metric_type: metric_type.to_s,
      value:, period:, reference:, source_batch_id:, recorded_at: Time.current }
  end

  upsert_data_points!(foreign_key: :email_template_id, rows:)
end

Instance Method Details

#email_templateEmailTemplate

The template these metrics describe.

Returns:



22
# File 'app/models/email_template_data_point.rb', line 22

belongs_to :email_template