Class: SeoBatchJob

Inherits:
ApplicationRecord show all
Defined in:
app/models/seo_batch_job.rb

Overview

Tracks a single batch submission to the Gemini or Anthropic Batch API.

Lifecycle:
collecting -> submitting -> processing -> completed
-> failed

The nightly pipeline creates one SeoBatchJob per run. It progresses through:

  1. SeoBatchCollectorWorker builds prompts for each page (status: collecting)
  2. SeoBatchSubmitWorker submits to Batch API (status: submitting -> processing)
  3. SeoBatchPollWorker monitors until done
  4. SeoBatchResultsWorker saves results (status: completed)

Provider routing:

  • Gemini models -> Gemini Batch API (50% discount)
  • Claude models -> Anthropic Message Batches API (50% discount)

== Schema Information

Table name: seo_batch_jobs
Database name: primary

id :bigint not null, primary key
completed_at :datetime
errored_count :integer default(0)
expired_count :integer default(0)
metadata :jsonb
model :string default("claude-sonnet-4-5"), not null
status :string default("collecting"), not null
submitted_at :datetime
succeeded_count :integer default(0)
total_pages :integer default(0)
created_at :datetime not null
updated_at :datetime not null
ai_batch_id :string

Indexes

index_seo_batch_jobs_on_ai_batch_id (ai_batch_id) UNIQUE
index_seo_batch_jobs_on_status (status)

Constant Summary collapse

STATUSES =
%w[collecting submitting processing completed failed].freeze

Instance Attribute Summary collapse

Has many collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from ApplicationRecord

ransackable_associations, ransackable_attributes, ransackable_scopes, ransortable_attributes, #to_relation

Methods included from Models::EventPublishable

#publish_event

Instance Attribute Details

#modelObject (readonly)



49
# File 'app/models/seo_batch_job.rb', line 49

validates :model, presence: true

#statusObject (readonly)



48
# File 'app/models/seo_batch_job.rb', line 48

validates :status, inclusion: { in: STATUSES }

Class Method Details

.activeActiveRecord::Relation<SeoBatchJob>

A relation of SeoBatchJobs that are active. Active Record Scope

Returns:

See Also:



51
# File 'app/models/seo_batch_job.rb', line 51

scope :active, -> { where(status: %w[collecting submitting processing]) }

.completedActiveRecord::Relation<SeoBatchJob>

A relation of SeoBatchJobs that are completed. Active Record Scope

Returns:

See Also:



52
# File 'app/models/seo_batch_job.rb', line 52

scope :completed, -> { where(status: 'completed') }

Instance Method Details

#anthropic?Boolean

Returns:

  • (Boolean)


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

def anthropic?   = model.to_s.start_with?('claude-')

#collecting?Boolean

Returns:

  • (Boolean)


54
# File 'app/models/seo_batch_job.rb', line 54

def collecting?  = status == 'collecting'

#completed?Boolean

Returns:

  • (Boolean)


57
# File 'app/models/seo_batch_job.rb', line 57

def completed?   = status == 'completed'

#failed?Boolean

Returns:

  • (Boolean)


58
# File 'app/models/seo_batch_job.rb', line 58

def failed?      = status == 'failed'

#gemini?Boolean

Returns:

  • (Boolean)


60
# File 'app/models/seo_batch_job.rb', line 60

def gemini?      = model.to_s.start_with?('gemini-')

#itemsActiveRecord::Relation<SeoBatchItem>

Returns:

See Also:



46
# File 'app/models/seo_batch_job.rb', line 46

has_many :items, class_name: 'SeoBatchItem', dependent: :destroy

#mark_completed!(succeeded: 0, errored: 0, expired: 0) ⇒ Object



81
82
83
84
85
86
87
88
89
# File 'app/models/seo_batch_job.rb', line 81

def mark_completed!(succeeded: 0, errored: 0, expired: 0)
  update!(
    status: 'completed',
    succeeded_count: succeeded,
    errored_count: errored,
    expired_count: expired,
    completed_at: Time.current
  )
end

#mark_failed!(error_message) ⇒ Object



91
92
93
94
95
96
97
# File 'app/models/seo_batch_job.rb', line 91

def mark_failed!(error_message)
  update!(
    status: 'failed',
    completed_at: Time.current,
    metadata: .merge('error' => error_message)
  )
end

#mark_submitted!(provider_batch_id:, provider: nil, extra_metadata: {}) ⇒ Object



67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'app/models/seo_batch_job.rb', line 67

def (provider_batch_id:, provider: nil, extra_metadata: {})
  merged = 
    .merge('provider' => provider || inferred_provider)
    .merge()

  update!(
    status: 'processing',
    ai_batch_id: provider_batch_id,
    submitted_at: Time.current,
    total_pages: items.count,
    metadata: merged
  )
end

#processing?Boolean

Returns:

  • (Boolean)


56
# File 'app/models/seo_batch_job.rb', line 56

def processing?  = status == 'processing'

#provider_batch_idObject



63
64
65
# File 'app/models/seo_batch_job.rb', line 63

def provider_batch_id
  ai_batch_id
end

#submitting?Boolean

Returns:

  • (Boolean)


55
# File 'app/models/seo_batch_job.rb', line 55

def submitting?  = status == 'submitting'