Class: Ragdoll::Search

Inherits:
ActiveRecord::Base
  • Object
show all
Defined in:
app/models/ragdoll/search.rb

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.cleanup_old_unused_searches(days: 30) ⇒ Object

Cleanup searches older than specified days with no clicks



136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# File 'app/models/ragdoll/search.rb', line 136

def self.cleanup_old_unused_searches(days: 30)
  cutoff_date = days.days.ago
  unused_searches = where(created_at: ...cutoff_date)
                   .left_joins(:search_results)
                   .where(search_results: { clicked: [nil, false] })
  
  unused_count = unused_searches.count
  
  if unused_count > 0
    unused_searches.destroy_all
    Rails.logger.info "Cleaned up #{unused_count} old unused search records" if defined?(Rails)
  end
  
  unused_count
end

.cleanup_orphaned_searchesObject

Cleanup orphaned searches that have no remaining search results



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

def self.cleanup_orphaned_searches
  orphaned_search_ids = where.not(id: SearchResult.distinct.pluck(:search_id))
  orphaned_count = orphaned_search_ids.count
  
  if orphaned_count > 0
    orphaned_search_ids.destroy_all
    Rails.logger.info "Cleaned up #{orphaned_count} orphaned search records" if defined?(Rails)
  end
  
  orphaned_count
end

.find_similar(query_embedding, limit: 10, threshold: 0.8) ⇒ Object

Find searches with similar query embeddings



30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'app/models/ragdoll/search.rb', line 30

def self.find_similar(query_embedding, limit: 10, threshold: 0.8)
  nearest_neighbors(:query_embedding, query_embedding, distance: "cosine")
    .limit(limit * 2)
    .map do |search|
      similarity = 1.0 - search.neighbor_distance
      next if similarity < threshold
      
      search.define_singleton_method(:similarity_score) { similarity }
      search
    end
    .compact
    .sort_by(&:similarity_score)
    .reverse
    .take(limit)
end

.record_search(query:, query_embedding:, results:, search_type: "semantic", filters: {}, options: {}, execution_time_ms: nil, session_id: nil, user_id: nil) ⇒ Object

Record a search with its results



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
# File 'app/models/ragdoll/search.rb', line 77

def self.record_search(query:, query_embedding:, results:, search_type: "semantic", 
                      filters: {}, options: {}, execution_time_ms: nil, 
                      session_id: nil, user_id: nil)
  search = create!(
    query: query,
    query_embedding: query_embedding,
    search_type: search_type,
    results_count: results.length,
    search_filters: filters,
    search_options: options,
    execution_time_ms: execution_time_ms,
    session_id: session_id,
    user_id: user_id
  )

  # Create search result records
  results.each_with_index do |result, index|
    search.search_results.create!(
      embedding_id: result[:embedding_id],
      similarity_score: result[:similarity],
      result_rank: index + 1
    )
  end

  # Calculate and store similarity statistics
  search.calculate_similarity_stats!
  search
end

.search_analytics(days: 30) ⇒ Object

Search analytics methods



107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'app/models/ragdoll/search.rb', line 107

def self.search_analytics(days: 30)
  start_date = days.days.ago
  searches = where(created_at: start_date..)
  
  {
    total_searches: searches.count,
    unique_queries: searches.distinct.count(:query),
    avg_results_per_search: searches.average(:results_count)&.round(2),
    avg_execution_time: searches.average(:execution_time_ms)&.round(2),
    search_types: searches.group(:search_type).count,
    searches_with_results: searches.where("results_count > 0").count,
    avg_click_through_rate: calculate_avg_ctr(searches)
  }
end

Instance Method Details

#calculate_similarity_stats!Object

Calculate statistics for this search



47
48
49
50
51
52
53
54
55
56
# File 'app/models/ragdoll/search.rb', line 47

def calculate_similarity_stats!
  return unless search_results.any?
  
  scores = search_results.pluck(:similarity_score)
  update!(
    max_similarity_score: scores.max,
    min_similarity_score: scores.min,
    avg_similarity_score: scores.sum.to_f / scores.length
  )
end

#click_through_rateObject

Calculate click-through rate



69
70
71
72
73
74
# File 'app/models/ragdoll/search.rb', line 69

def click_through_rate
  return 0.0 if results_count == 0
  
  clicked_count = search_results.where(clicked: true).count
  clicked_count.to_f / results_count
end

#clicked_resultsObject

Get clicked results



64
65
66
# File 'app/models/ragdoll/search.rb', line 64

def clicked_results
  search_results.where(clicked: true).order(:clicked_at)
end

#ranked_resultsObject

Get search results ordered by rank



59
60
61
# File 'app/models/ragdoll/search.rb', line 59

def ranked_results
  search_results.includes(:embedding).order(:result_rank)
end