Class: SiteMapDataPoint

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

Overview

== Schema Information

Table name: site_map_data_points
Database name: primary

id :bigint not null, primary key
metric_type :enum not null
period :daterange not null
recorded_at :datetime not null
reference :string
value :decimal(15, 4) not null
created_at :datetime not null
updated_at :datetime not null
site_map_id :bigint not null
source_batch_id :uuid

Indexes

idx_data_points_batch (source_batch_id) WHERE (source_batch_id IS NOT NULL)
idx_data_points_lookup (site_map_id,metric_type,period) USING gist
idx_data_points_metric_period (metric_type,period) USING gist
idx_data_points_reference (site_map_id,metric_type,reference) WHERE (reference IS NOT NULL)
idx_data_points_unique_metric_period (site_map_id, metric_type, period, COALESCE(reference, ''::character varying)) UNIQUE

Foreign Keys

fk_rails_... (site_map_id => site_maps.id) ON DELETE => cascade

SEO time-series metrics per SiteMap. The generic time-series machinery
(scopes, trend/comparison API, period helpers, upsert) lives in
Models::DataPointMetrics; this class holds only the SEO-specific metric
taxonomy and the site_map-named record helpers.

Constant Summary collapse

GSC_METRICS =

Metric type categories for grouping

%w[gsc_clicks gsc_impressions gsc_ctr gsc_avg_position].freeze
GA4_METRICS =

Ga4 metrics.

%w[ga4_page_views ga4_sessions ga4_users ga4_bounce_rate
ga4_engagement_rate ga4_avg_session_duration].freeze
AHREFS_METRICS =

Ahrefs metrics.

%w[ahrefs_traffic ahrefs_keywords_count ahrefs_traffic_value
ahrefs_domain_rating].freeze
ADS_METRICS =

Ads metrics.

%w[ads_clicks ads_impressions ads_cost ads_conversions
ads_conversion_value].freeze
KEYWORD_METRICS =

Keyword metrics.

%w[keyword_position keyword_search_volume keyword_traffic_share].freeze
CLOUDFLARE_METRICS =

Cloudflare edge-analytics metrics (every request at the edge — bots, cache hits, JS-disabled).

%w[cloudflare_requests].freeze
INVERTED_METRICS =

Metrics where lower is better (for trend interpretation)

%w[ga4_bounce_rate gsc_avg_position keyword_position].freeze

Constants included from Models::Schedulable

Models::Schedulable::SIMPLE_FORM_OPTIONS

Instance Attribute Summary collapse

Attributes included from Models::DataPointMetrics

#metric_type, #period, #value

Belongs to collapse

Class Method Summary collapse

Instance 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

Instance Attribute Details

#referenceObject (readonly)

Keyword metrics carry the keyword in reference; require it.

Validations (if => #keyword_metric? ):



61
# File 'app/models/site_map_data_point.rb', line 61

validates :reference, presence: true, if: :keyword_metric?

Class Method Details

.bulk_record!(site_map:, metrics:, period_start:, period_end:, batch_id: nil, reference: nil) ⇒ Object

Bulk record multiple metrics from a sync (upserts to prevent duplicates)

Parameters:

  • site_map (SiteMap)
  • metrics (Hash)

    { metric_type => value }

  • period_start (Date)
  • period_end (Date)
  • batch_id (String) (defaults to: nil)
  • reference (String, nil) (defaults to: nil)

    Context for keyword metrics



94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'app/models/site_map_data_point.rb', line 94

def bulk_record!(site_map:, metrics:, period_start:, period_end:, batch_id: nil, reference: nil)
  batch_id ||= SecureRandom.uuid
  now = Time.current
  rows = metrics.filter_map do |metric_type, value|
    next if value.nil?

    {
      site_map_id: site_map.id,
      metric_type: metric_type.to_s,
      value:,
      period_start:,
      period_end:,
      reference:,
      source_batch_id: batch_id,
      recorded_at: now
    }
  end

  bulk_record_rows!(rows:)
end

.bulk_record_rows!(rows:) ⇒ void

This method returns an undefined value.

Bulk-upsert prebuilt SiteMap metric rows that may cover different periods.
This keeps a many-page sync to one write while preserving each metric's
actual window (for example 30-day and 90-day visit counts).

Parameters:

  • rows (Array<Hash>)

    rows with site_map_id, metric_type, value,
    period_start, period_end, source_batch_id, and recorded_at



122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'app/models/site_map_data_point.rb', line 122

def bulk_record_rows!(rows:)
  normalized_rows = rows.filter_map do |row|
    next if row[:value].nil?

    row.merge(
      metric_type: row.fetch(:metric_type).to_s,
      period: "[#{row.fetch(:period_start)},#{row.fetch(:period_end)}]",
      reference: row[:reference]
    ).except(:period_start, :period_end)
  end

  upsert_data_points!(foreign_key: :site_map_id, rows: normalized_rows)
end

.record!(site_map:, metric_type:, value:, period_start:, period_end:, reference: nil, batch_id: nil) ⇒ SiteMapDataPoint

Convenience method to record a data point

Parameters:

  • site_map (SiteMap)

    The site map this metric belongs to

  • metric_type (Symbol, String)

    The type of metric

  • value (Numeric)

    The metric value

  • period_start (Date)

    Start of the period this metric covers

  • period_end (Date)

    End of the period this metric covers

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

    Optional context (e.g., keyword text)

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

    Optional batch ID for grouping

Returns:



74
75
76
77
78
79
80
81
82
83
84
# File 'app/models/site_map_data_point.rb', line 74

def record!(site_map:, metric_type:, value:, period_start:, period_end:, reference: nil, batch_id: nil)
  create!(
    site_map:,
    metric_type:,
    value:,
    period: period_start..period_end,
    reference:,
    source_batch_id: batch_id,
    recorded_at: Time.current
  )
end

Instance Method Details

#ads_metric?Boolean

Returns:

  • (Boolean)


158
159
160
# File 'app/models/site_map_data_point.rb', line 158

def ads_metric?
  ADS_METRICS.include?(metric_type.to_s)
end

#ahrefs_metric?Boolean

Returns:

  • (Boolean)


153
154
155
# File 'app/models/site_map_data_point.rb', line 153

def ahrefs_metric?
  AHREFS_METRICS.include?(metric_type.to_s)
end

#cloudflare_metric?Boolean

Returns:

  • (Boolean)


163
164
165
# File 'app/models/site_map_data_point.rb', line 163

def cloudflare_metric?
  CLOUDFLARE_METRICS.include?(metric_type.to_s)
end

#ga4_metric?Boolean

Returns:

  • (Boolean)


148
149
150
# File 'app/models/site_map_data_point.rb', line 148

def ga4_metric?
  GA4_METRICS.include?(metric_type.to_s)
end

#gsc_metric?Boolean

Returns:

  • (Boolean)


143
144
145
# File 'app/models/site_map_data_point.rb', line 143

def gsc_metric?
  GSC_METRICS.include?(metric_type.to_s)
end

#keyword_metric?Boolean

Returns:

  • (Boolean)


138
139
140
# File 'app/models/site_map_data_point.rb', line 138

def keyword_metric?
  KEYWORD_METRICS.include?(metric_type.to_s)
end

#site_mapSiteMap

Returns:



39
# File 'app/models/site_map_data_point.rb', line 39

belongs_to :site_map