<commit_before>class AnimeResource < BaseResource
  # This regex accepts a numerical range or single number
  # $1 = start, $2 = dot representing closed/open, $3 = end
  NUMBER = /(\d+(?:\.\d+)?)/
  NUMERIC_RANGE = %r{\A#{NUMBER}(?:(?:\.\.(\.)?)#{NUMBER})?\z}
  NUMERIC_QUERY = {
    valid: -> (value, _ctx) { NUMERIC_RANGE.match(value) },
    apply: -> (values, _ctx) {
      # We only accept the first value
      values.map do |value|
        matches = NUMERIC_RANGE.match(value)

        if matches[3] # Range
          Range.new(matches[1].to_f, matches[3].to_f, matches[2] == '.')
        else # Scalar
          matches[1]
        end
      end
    }
  }

  attributes :slug,
             # Images
             :poster_image, :cover_image, :cover_image_top_offset,
             # Titles
             :titles, :canonical_title, :abbreviated_titles,
             # Type, Dates, Synopsis
             :show_type, :start_date, :end_date, :synopsis,
             # Age Ratings
             :age_rating, :age_rating_guide,
             # Ratings
             :average_rating, :rating_frequencies,
             # Episodes
             :episode_count, :episode_length

  has_many :castings
  has_many :genres
  has_many :streaming_links

  filter :slug, apply: -> (records, value, _options) { records.by_slug(value) }

  # ElasticSearch hookup
  index MediaIndex::Anime
  query :season, valid: -> (value, _ctx) { Anime::SEASONS.include?(value) }
  query :year, NUMERIC_QUERY
  query :average_rating, NUMERIC_QUERY
  query :user_count, NUMERIC_QUERY
  query :genres,
    apply: -> (values, _ctx) {
      {match: {genres: {query: value.join(' '), operator: 'and'}}}
    }
  query :streamers,
    valid: -> (value, _ctx) { Streamer.find_by_name(value) }
  query :age_rating,
    valid: -> (values, _ctx) {
    },
    apply: -> (value, _ctx) { value.split(',').join(' ') }
  query :text, mode: :query,
    apply: -> (values, _ctx) {
      {
        function_score: {
          field_value_factor: {
            field: 'user_count',
            modifier: 'log1p'
          },
          query: {
            multi_match: {
              fields: %w[titles.* abbreviated_titles synopsis actors characters],
              query: values.join(','),
              fuzziness: 2,
              max_expansions: 15,
              prefix_length: 2
            }
          }
        }
      }
    }
end
<commit_msg>server: fix searches on genres and age ratings<commit_after>class AnimeResource < BaseResource
  # This regex accepts a numerical range or single number
  # $1 = start, $2 = dot representing closed/open, $3 = end
  NUMBER = /(\d+(?:\.\d+)?)/
  NUMERIC_RANGE = %r{\A#{NUMBER}(?:(?:\.\.(\.)?)#{NUMBER})?\z}
  NUMERIC_QUERY = {
    valid: -> (value, _ctx) { NUMERIC_RANGE.match(value) },
    apply: -> (values, _ctx) {
      # We only accept the first value
      values.map do |value|
        matches = NUMERIC_RANGE.match(value)

        if matches[3] # Range
          Range.new(matches[1].to_f, matches[3].to_f, matches[2] == '.')
        else # Scalar
          matches[1]
        end
      end
    }
  }

  attributes :slug,
             # Images
             :poster_image, :cover_image, :cover_image_top_offset,
             # Titles
             :titles, :canonical_title, :abbreviated_titles,
             # Type, Dates, Synopsis
             :show_type, :start_date, :end_date, :synopsis,
             # Age Ratings
             :age_rating, :age_rating_guide,
             # Ratings
             :average_rating, :rating_frequencies,
             # Episodes
             :episode_count, :episode_length

  has_many :castings
  has_many :genres
  has_many :streaming_links

  filter :slug, apply: -> (records, value, _options) { records.by_slug(value) }

  # ElasticSearch hookup
  index MediaIndex::Anime
  query :season, valid: -> (value, _ctx) { Anime::SEASONS.include?(value) }
  query :year, NUMERIC_QUERY
  query :average_rating, NUMERIC_QUERY
  query :user_count, NUMERIC_QUERY
  query :genres,
    apply: -> (values, _ctx) {
      {match: {genres: {query: values.join(' '), operator: 'and'}}}
    }
  query :streamers,
    valid: -> (value, _ctx) { Streamer.find_by_name(value) }
  query :age_rating,
    valid: -> (value, _ctx) { Anime.age_ratings.keys.include?(value) }
  query :text, mode: :query,
    apply: -> (values, _ctx) {
      {
        function_score: {
          field_value_factor: {
            field: 'user_count',
            modifier: 'log1p'
          },
          query: {
            multi_match: {
              fields: %w[titles.* abbreviated_titles synopsis actors characters],
              query: values.join(','),
              fuzziness: 2,
              max_expansions: 15,
              prefix_length: 2
            }
          }
        }
      }
    }
end

{'fraction_non_alphanumeric': 0.13270833333333334, 'fraction_numerical': 0.005416666666666667, 'mean_word_length': 2.0914359304571795, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 20, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6873244', 'n_tokens_mistral': 1627, 'n_tokens_neox': 1589, 'n_words': 342}