Class: PublicationVisionWorker

Inherits:
Object
  • Object
show all
Includes:
Sidekiq::Worker, Workers::StatusBroadcastable
Defined in:
app/workers/publication_vision_worker.rb

Overview

Background worker for analyzing PDF publications using Claude's native PDF vision capabilities.
Passes the full PDF directly to Claude, which natively reads all pages including
vector graphics, diagrams, photos, and embedded text — eliminating the need to
extract images before sending to a vision model.

Examples:

Queue publication for vision analysis

PublicationVisionWorker.perform_async(13472)

Queue with force regeneration

PublicationVisionWorker.perform_async(13472, force: true)

Constant Summary collapse

VISION_MODEL =
AiModelConstants.id(:vision_analysis)
MAX_TOKENS_PDF_ANALYSIS =

Max tokens for full-document visual analysis

2_000
SYSTEM_PROMPT =

System prompt for analyzing full PDF documents

<<~PROMPT
  You are an expert analyst for technical product documentation PDFs.
  Analyze ALL pages of this document and describe the visual content you find.

  For each page containing meaningful visual content, describe:
  1. DIAGRAMS: Wiring diagrams, installation layouts, floor plans, electrical schematics
  2. ILLUSTRATIONS: Step-by-step instructions, cross-sections, product cutaways
  3. PHOTOS: Product photos, installation examples, finished installations
  4. TABLES/CHARTS: Specification tables, sizing charts, temperature curves
  5. LABELS: Warning labels, certification marks, control panel layouts

  Be specific about what you can actually read or see:
  - Product names and model numbers if visible
  - Technical specifications shown
  - Installation steps or procedures depicted
  - Safety warnings or important notes

  Write in plain text, no markdown. Be concise but capture key technical details.
  Skip pages that contain only text.
  Do not hallucinate content that is not visible.
PROMPT

Instance Attribute Summary

Attributes included from Workers::StatusBroadcastable

#broadcast_status_updates

Instance Method Summary collapse

Methods included from Workers::StatusBroadcastable::Overrides

#at, #store, #total

Instance Method Details

#perform(item_id, options = {}) ⇒ Object

Parameters:

  • item_id (Integer)

    The Item (publication) record ID

  • options (Hash) (defaults to: {})

    Options

Options Hash (options):

  • :force (Boolean)

    Force regeneration even if already analyzed

  • :redirect_to (String)

    Path to redirect to after completion



58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
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
110
111
112
113
114
115
116
117
118
# File 'app/workers/publication_vision_worker.rb', line 58

def perform(item_id, options = {})
  options = options.with_indifferent_access
  force = options[:force].to_b
  redirect_to_path = options[:redirect_to]

  store redirect_to: redirect_to_path if redirect_to_path.present?

  total 4
  at 1, 'Loading publication...'

  item = Item.find_by(id: item_id)
  unless item
    store error_message: "Item #{item_id} not found"
    return log_info("Item #{item_id} not found, skipping")
  end

  unless item.is_publication?
    store error_message: "Item #{item_id} is not a publication"
    return log_info("Item #{item_id} is not a publication, skipping")
  end

  if item.pdf_images_analyzed_at.present? && !force
    store info_message: "Already analyzed at #{item.pdf_images_analyzed_at}"
    return log_info("Publication #{item_id} already analyzed at #{item.pdf_images_analyzed_at}")
  end

  unless item.literature&.attachment && File.exist?(item.literature.attachment.path)
    store error_message: 'PDF file not found'
    return log_info("No PDF attachment found for publication #{item_id}")
  end

  at 2, 'Analyzing PDF with Claude Vision...'
  log_info "Analyzing publication #{item_id}: #{item.sku}"

  description = analyze_pdf(item)

  at 3, 'Saving results...'

  item.update!(
    pdf_image_descriptions: description.presence,
    pdf_images_analyzed_at: Time.current
  )

  if description.present?
    EmbeddingWorker.perform_async('Item', item_id, force: true)
    at 4, 'Complete!'
    store info_message: 'Vision analysis complete'
    log_info "Publication #{item_id} vision analysis complete: #{description.length} chars"
  else
    at 4, 'Complete!'
    store info_message: 'No visual content found in PDF'
    log_info "No visual content found in publication #{item_id}"
  end
rescue RubyLLM::Error => e
  log_error "RubyLLM error for Publication #{item_id}: #{e.message}"
  raise
rescue StandardError => e
  log_error "Error analyzing Publication #{item_id}: #{e.message}"
  ErrorReporting.error(e)
  raise
end