Created
July 6, 2016 23:26
-
-
Save yknx4/d0ab08205a1a1b442bf28d5f63b62e8b to your computer and use it in GitHub Desktop.
troublesome code.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class ProfileBookEvaluator | |
OTHER_MATCH_COUNT = 10 | |
def evaluate( profile_id ) | |
profile = profile_id | |
# This check is to not to brake existing functionality, but it is better to pass a profile_id than the full object | |
if profile.is_a? Profile | |
# Ensure we have the most up-to-date information on this object | |
# Since users can potentially change their filters / address info | |
# In between the serialization of the object and the time it's run | |
profile.reload | |
else | |
profile = Profile.find(profile_id) | |
end | |
@profile = profile | |
@user = profile.user | |
@microsite = profile.microsite | |
# if this months profile has been locked by admin, skip the evaluation | |
return [] if profile.blank? || profile.is_book_match_approved? | |
# get rid of any existing matches, | |
# since we will recreate them if applicable | |
profile.profile_matches.destroy_all | |
# Start with all the books. | |
# Eliminate any that aren't tagged with the child's age (if child's DOB is provided) | |
not_to_send_book_ids = already_sent_books + saved_books + paused_books + logged_books | |
books = if profile_age_rounded.blank? | |
approved_books.where('books.id NOT IN (?)', not_to_send_book_ids.flatten) | |
else | |
approved_books.appropriate_for_age(profile_age_rounded).where('books.id NOT IN (?)', not_to_send_book_ids.flatten) | |
end | |
unless profile.customized_filters.blank? | |
# Eliminate any that are not in at least one of the selected languages (if languages are selected) | |
if profile.customized_filters['languages'].blank? | |
english = Language.find_by(name: 'English') | |
books = books.with_languages([english.id]) if english.present? | |
else | |
books = books.with_languages(profile.customized_filters['languages']) | |
end | |
# Eliminate any that don't match any of the selected Reading Levels (if reading levels are provided) | |
unless profile.customized_filters["reading_levels"].blank? | |
books = books.with_reading_levels(profile.customized_filters['reading_levels']) | |
end | |
end | |
# Calculate a relevance score for the remaining books, | |
# => where 'Background' filters are worth twice as much as 'Interest' and 'Genre' filters | |
if books.any? | |
related = {} | |
# Sometimes pluck is not available or refuses to work due to some bad sql generated, better safe than sorry.... | |
begin | |
books_in_custom_collection = BookFormat.where(in_custom_collection: true, | |
book_id: books.pluck('books.id')) | |
.pluck(:book_id) | |
rescue | |
books_in_custom_collection = BookFormat.where(in_custom_collection: true, | |
book_id: books.collect(&:id)) | |
.pluck(:book_id) | |
end | |
reading_weight_total = 0 | |
background_weight_total = 0 | |
interest_weight_total = 0 | |
genre_weight_total = 0 | |
books.all.map{ |book| related[book.id] = { | |
:book_id => book.id, | |
:profile_id => profile.id, | |
:matched_filters => {}, | |
:in_custom_collection => books_in_custom_collection.include?(book.id), | |
:score => 0, | |
:interval => 0.0, | |
:has_tip => true } #We can assure all books have tip because we only did query for books with tip | |
} | |
unless profile.customized_filters.blank? | |
# Assign scores for selected reading levels | |
if profile.customized_filters.key? 'reading_levels' | |
profile.customized_filters['reading_levels'].each do |r| | |
reading_level = ReadingLevel.find_by_id(r) | |
next if reading_level.nil? | |
weight = reading_level.evaluation_weight.blank? ? 0 : reading_level.evaluation_weight | |
reading_weight_total += weight | |
eligible_formats = books | |
eligible_formats.joins(:reading_levels).where(:reading_levels => { :id => r }).uniq.each do |match| | |
related[match.id][:score] += weight | |
related[match.id][:matched_filters][:reading_levels] ||= [] | |
related[match.id][:matched_filters][:reading_levels] << r | |
end | |
end | |
end | |
# Assign scores for selected backgrounds | |
if profile.customized_filters.key? 'backgrounds' | |
profile.customized_filters['backgrounds'].each do |b| | |
background_type = BackgroundType.find_by_id(b) | |
next if background_type.nil? | |
weight = background_type.evaluation_weight.blank? ? 0 : background_type.evaluation_weight | |
background_weight_total += weight | |
eligible_formats = books | |
eligible_formats.joins(:backgrounds).where(:backgrounds => { :background_type_id => b }).uniq.each do |match| | |
related[match.id][:score] += weight | |
related[match.id][:matched_filters][:backgrounds] ||= [] | |
related[match.id][:matched_filters][:backgrounds] << b | |
end | |
end | |
end | |
# Assign scores for selected interests | |
if profile.customized_filters.key? 'interests' | |
profile.customized_filters['interests'].each do |i| | |
interest = Interest.find_by_id(i) | |
next if interest.nil? | |
weight = interest.evaluation_weight.blank? ? 0 : interest.evaluation_weight | |
interest_weight_total += weight | |
eligible_formats = books | |
eligible_formats.joins(:interests).where(:interests => { :id => i }).uniq.each do |match| | |
related[match.id][:score] += weight | |
related[match.id][:matched_filters][:interests] ||= [] | |
related[match.id][:matched_filters][:interests] << i | |
end | |
end | |
end | |
# Assign scores for selected interests | |
if profile.customized_filters.key? 'genres' | |
profile.customized_filters['genres'].each do |g| | |
genre = Genre.find_by_id(g) | |
next if genre.nil? | |
weight = genre.evaluation_weight.blank? ? 0 : genre.evaluation_weight | |
genre_weight_total += weight | |
eligible_formats = books | |
eligible_formats.joins(:genres).where(:genres => { :id => g }).uniq.each do |match| | |
related[match.id][:score] += weight | |
related[match.id][:matched_filters][:genres] ||= [] | |
related[match.id][:matched_filters][:genres] << g | |
end | |
end | |
end | |
end | |
# Calculate intervals | |
max_rs = max_relevance_score(profile, reading_weight_total, background_weight_total, interest_weight_total, genre_weight_total) | |
related.each do |k, _| | |
if max_rs == 0 | |
related[k][:interval] = 0 | |
related[k][:max_relevance_score] = 0 | |
else | |
related[k][:interval] = 100 * related[k][:score] / max_rs | |
related[k][:max_relevance_score] = max_rs | |
end | |
end | |
# Sort | |
related = related.values.sort{ |x, y| | |
# prefer the highest interval | |
if x[:interval] == y[:interval] | |
if (x[:has_tip] == y[:has_tip]) | |
0 | |
else | |
x[:has_tip] ? -1 : 1 | |
end | |
else | |
y[:interval] <=> x[:interval] | |
end | |
} | |
# Create return array | |
matches = [] | |
profile_match_limit = related.size < OTHER_MATCH_COUNT ? related.size : OTHER_MATCH_COUNT | |
ProfileMatch.transaction do | |
related.slice(0, profile_match_limit).each do |item| | |
matches << ProfileMatch.create(item) | |
end | |
end | |
create_pending_profile_match(profile) | |
matches | |
else | |
[] | |
end | |
end | |
def self.evaluate_now(profile) | |
self.new.evaluate(profile.id) | |
end | |
def create_pending_profile_match(profile) | |
# get the best book profile match | |
best_match = ProfileMatch.best_book_match(profile) | |
unless best_match.nil? | |
PendingProfileMatch.where('profile_id = (?)', profile.id).destroy_all | |
pending_match_params = { | |
:profile_match_id => best_match.id, | |
:profile_id => profile.id, | |
:book_id => best_match.book.id, | |
:matchable_title => best_match.book.title, | |
:user_id => profile.user.id, | |
:user_name => profile.user.name, | |
:score => best_match.score, | |
:max_relevance_score => best_match.max_relevance_score, | |
:interval => best_match.interval, | |
:has_tip => best_match.has_tip | |
} | |
pending_match = PendingProfileMatch.new(pending_match_params) | |
pending_match.save | |
pending_match | |
end | |
end | |
def max_relevance_score(profile, reading_total, background_total, interest_total, genre_total) | |
max_score = 0 | |
unless profile.customized_filters.blank? | |
max_score += reading_total unless reading_total.nil? | |
max_score += background_total unless background_total.nil? | |
max_score += interest_total unless interest_total.nil? | |
max_score += genre_total unless genre_total.nil? | |
end | |
max_score | |
end | |
def approved_books | |
@approved_books ||= Rails.cache.fetch "/microsite/#{@microsite.id}/books_approved", expires_in: 1.hour do | |
@microsite.books | |
.approved | |
end | |
end | |
def books_with_learning_tip | |
@books_with_learning_tip ||= Rails.cache.fetch "/microsite/#{@microsite.id}/books_approved_with_learning_tip", expires_in: 1.hour do | |
@microsite.books | |
.approved | |
.joins(:book_learning_tip) | |
end | |
end | |
def already_sent_books | |
@already_sent_books ||= sent_book_ids + pending_matches_book_ids | |
end | |
def recommendations_for_user | |
Recommendation.joins(:profile).where('profiles.user_id = ?', @user.id) unless @user.blank? | |
end | |
def sent_book_ids | |
@sent_book_ids ||= (recommendations_for_user.try(:pluck, :book_id) || []) | |
end | |
def pending_matches_for_user | |
PendingProfileMatch.joins(:profile).where('profiles.user_id = ?', @user.id) | |
end | |
def pending_matches_book_ids | |
@pending_matches_book_ids ||= pending_matches_for_user.try(:pluck, :book_id) | |
end | |
def saved_books | |
@saved_books ||= wish_lists_book_ids | |
end | |
def wish_lists_for_user | |
WishList.joins(:profile).where('profiles.user_id = ?', @user.id) | |
end | |
def wish_lists_book_ids | |
@wish_lists_book_ids ||= wish_lists_for_user.pluck(:itemizable_id) | |
end | |
def logged_books | |
@logged_books ||= @profile.logged_books.all.pluck(:book_id).compact | |
end | |
def paused_books | |
@paused_books ||= Microsites::PausedBooks.new(@profile.microsite).call | |
end | |
def profile_age_rounded | |
@profile_age_rounded ||= @profile.age_rounded | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment