<commit_before>module Recommendify::Base
  def self.included(base)
    base.extend(ClassMethods)
  end

  module ClassMethods
    def input_matrix(key, opts={})
      @matrices ||= {}
      @matrices[key] = opts
    end

    def input_matrices=(val)
      @matrices = val
    end

    def input_matrices
      @matrices
    end

    def max_neighbors(n=nil)
      return @max_neighbors unless n
      @max_neighbors = n
    end
  end

  def input_matrices
    @input_matrices ||= Hash[self.class.input_matrices.map{ |key, opts|
      opts.merge!(:key => key, :redis_prefix => redis_prefix)
      [ key, Recommendify::InputMatrix.create(opts) ]
    }]
  end

  def max_neighbors
    self.class.max_neighbors || Recommendify::DEFAULT_MAX_NEIGHBORS
  end

  def redis_prefix
    "recommendify"
  end

  def redis_key(*append)
    ([redis_prefix] + append).flatten.compact.join(":")
  end

  def method_missing(method, *args)
    if input_matrices.has_key?(method)
      input_matrices[method]
    else
      raise NoMethodError.new(method.to_s)
    end
  end

  def respond_to?(method)
    input_matrices.has_key?(method) ? true : super
  end

  def all_items
    Recommendify.redis.sunion input_matrices.map{|k,m| m.redis_key(:all_items)}
  end

  def process_predictions(set_id, matrix_label)
    key = redis_key(:predictions, set_id)
    matrix = input_matrices[matrix_label]
    redis = Recommendify.redis

    item_set = redis.smembers(matrix.redis_key(:sets, set_id))

    item_keys = item_set.map do |item|
      input_matrices.map{ |k,m| m.redis_key(:similarities, item) }
    end.flatten

    item_weights = item_keys.map do |item_key|
      scores = redis.zrange item_key, 0, -1, :with_scores => true
      unless scores.empty?
        1.0/scores.map{|x,y| y}.reduce(:+)
      else
        0
      end
    end

    redis.multi do |multi|
      multi.del key
      multi.zunionstore key, item_keys, :weights => item_weights
      multi.zrem key, item_set
    end

    return predictions_for(set_id)
  end

  def predictions_for(set_id, with_scores = false)
    Recommendify.redis.zrevrange redis_key(:predictions, set_id), 0, -1, :with_scores => with_scores
  end

  def ids_for(item, with_scores = false)
    similarities_for(item, with_scores)
  end

  def similarities_for(item, with_scores = false)
    keys = input_matrices.map{ |k,m| m.redis_key(:similarities, item) }
    neighbors = nil
    unless keys.empty?
      Recommendify.redis.multi do |multi|
        multi.zunionstore 'temp', keys
        neighbors = multi.zrevrange('temp', 0, -1, :with_scores => with_scores)
      end
      return neighbors.value
    else
      return []
    end
  end
<commit_msg>should fix predictions on zero data<commit_after>module Recommendify::Base
  def self.included(base)
    base.extend(ClassMethods)
  end

  module ClassMethods
    def input_matrix(key, opts={})
      @matrices ||= {}
      @matrices[key] = opts
    end

    def input_matrices=(val)
      @matrices = val
    end

    def input_matrices
      @matrices
    end

    def max_neighbors(n=nil)
      return @max_neighbors unless n
      @max_neighbors = n
    end
  end

  def input_matrices
    @input_matrices ||= Hash[self.class.input_matrices.map{ |key, opts|
      opts.merge!(:key => key, :redis_prefix => redis_prefix)
      [ key, Recommendify::InputMatrix.create(opts) ]
    }]
  end

  def max_neighbors
    self.class.max_neighbors || Recommendify::DEFAULT_MAX_NEIGHBORS
  end

  def redis_prefix
    "recommendify"
  end

  def redis_key(*append)
    ([redis_prefix] + append).flatten.compact.join(":")
  end

  def method_missing(method, *args)
    if input_matrices.has_key?(method)
      input_matrices[method]
    else
      raise NoMethodError.new(method.to_s)
    end
  end

  def respond_to?(method)
    input_matrices.has_key?(method) ? true : super
  end

  def all_items
    Recommendify.redis.sunion input_matrices.map{|k,m| m.redis_key(:all_items)}
  end

  def process_predictions(set_id, matrix_label)
    key = redis_key(:predictions, set_id)
    matrix = input_matrices[matrix_label]
    redis = Recommendify.redis

    item_set = redis.smembers(matrix.redis_key(:sets, set_id))

    item_keys = item_set.map do |item|
      input_matrices.map{ |k,m| m.redis_key(:similarities, item) }
    end.flatten

    item_weights = item_keys.map do |item_key|
      scores = redis.zrange item_key, 0, -1, :with_scores => true
      unless scores.empty?
        1.0/scores.map{|x,y| y}.reduce(:+)
      else
        0
      end
    end

    unless item_keys.empty?
      redis.multi do |multi|
        multi.del key
        multi.zunionstore key, item_keys, :weights => item_weights
        multi.zrem key, item_set
      end
      return predictions_for(set_id)
    else
      return []
    end
  end

  def predictions_for(set_id, with_scores = false)
    Recommendify.redis.zrevrange redis_key(:predictions, set_id), 0, -1, :with_scores => with_scores
  end

  def ids_for(item, with_scores = false)
    similarities_for(item, with_scores)
  end

  def similarities_for(item, with_scores = false)
    keys = input_matrices.map{ |k,m| m.redis_key(:similarities, item) }
    neighbors = nil
    unless keys.empty?
      Recommendify.redis.multi do |multi|
        multi.zunionstore 'temp', keys
        neighbors = multi.zrevrange('temp', 0, -1, :with_scores => with_scores)
      end
      return neighbors.value
    else
      return []
    end
  end

{'fraction_non_alphanumeric': 0.10286554004408523, 'fraction_numerical': 0.0033063923585598823, 'mean_word_length': 2.9802631578947367, 'pattern_counts': {'":': 2, '<': 3, '<?xml version=': 0, '>': 15, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '84877', 'n_tokens_mistral': 2000, 'n_tokens_neox': 1894, 'n_words': 396}