<commit_before># This is a class of static helper methods for generating data for the heatmap
# visualization. See HeatmapHelperTest.
# See selectedOptions in SamplesHeatmapView for client-side defaults, and
# heatmap action in VisualizationsController.
module HeatmapHelper
  DEFAULT_MAX_NUM_TAXONS = 30
  DEFAULT_TAXON_SORT_PARAM = 'highest_nt_rpm'.freeze
  READ_SPECIFICITY = true
  MINIMUM_READ_THRESHOLD = 5
  # Note: this is activated from the heatmap page by selecting "Viruses -
  # Phages". The default categories are all BUT phages, though the UI does not
  # indicate this.
  INCLUDE_PHAGE = false

  MINIMUM_ZSCORE_THRESHOLD = 1.7

  ALL_METRICS = [
    { text: "NT rPM", value: "NT.rpm" },
    { text: "NT Z Score", value: "NT.zscore" },
    { text: "NT r (total reads)", value: "NT.r" },
    { text: "NR rPM", value: "NR.rpm" },
    { text: "NR Z Score", value: "NR.zscore" },
    { text: "NR r (total reads)", value: "NR.r" }
  ].freeze

  # Samples and background are assumed here to be vieweable.
  def self.sample_taxons_dict(params, samples, background_id)
    return {} if samples.empty?

    num_results = params[:taxonsPerSample] ? params[:taxonsPerSample].to_i : DEFAULT_MAX_NUM_TAXONS
    min_reads = params[:minReads] ? params[:minReads].to_i : MINIMUM_READ_THRESHOLD
    removed_taxon_ids = (params[:removedTaxonIds] || []).map do |x|
      begin
        Integer(x)
      rescue ArgumentError
        nil
      end
    end
    removed_taxon_ids = removed_taxon_ids.compact
    categories = params[:categories]
    threshold_filters = if params[:thresholdFilters].is_a?(Array)
                          (params[:thresholdFilters] || []).map { |filter| JSON.parse(filter || "{}") }
                        else
                          JSON.parse(params[:thresholdFilters] || "[]")
                        end
    subcategories = if params[:subcategories] && params[:subcategories].respond_to?(:to_h)
                      params[:subcategories].to_h
                    else
                      JSON.parse(params[:subcategories] || "{}")
                    end
    include_phage = subcategories.fetch("Viruses", []).include?("Phage")
    read_specificity = params[:readSpecificity] ? params[:readSpecificity].to_i == 1 : false

    sort_by = params[:sortBy] &&
              HeatmapHelper::ALL_METRICS.map { |m| m[:value] }.include?(params[:sortBy]) ||
              HeatmapHelper::DEFAULT_TAXON_SORT_PARAM
    species_selected = params[:species] ? params[:species].to_i == 1 : false # Otherwise genus selected

    background_id = background_id && background_id > 0 ? background_id : samples.first.default_background_id

    results_by_pr = fetch_top_taxons(
      samples,
      background_id,
      categories,
      read_specificity,
      include_phage,
      num_results,
      min_reads,
      sort_by,
      threshold_filters
    )

    details = top_taxons_details(
      results_by_pr,
      num_results,
      sort_by,
      species_selected,
      threshold_filters
    )

    taxon_ids = details.pluck('tax_id')
    taxon_ids -= removed_taxon_ids

    # Refetch at genus level using species level
    parent_ids = species_selected ? [] : HeatmapHelper.fetch_parent_ids(taxon_ids, samples)
    results_by_pr = HeatmapHelper.fetch_samples_taxons_counts(samples, taxon_ids, parent_ids, background_id)

    HeatmapHelper.samples_taxons_details(
      results_by_pr,
      samples,
      taxon_ids,
      species_selected,
      threshold_filters<commit_msg>Fix server SQL error for heatmap if all taxons are filtered out. (#2306)<commit_after># This is a class of static helper methods for generating data for the heatmap
# visualization. See HeatmapHelperTest.
# See selectedOptions in SamplesHeatmapView for client-side defaults, and
# heatmap action in VisualizationsController.
module HeatmapHelper
  DEFAULT_MAX_NUM_TAXONS = 30
  DEFAULT_TAXON_SORT_PARAM = 'highest_nt_rpm'.freeze
  READ_SPECIFICITY = true
  MINIMUM_READ_THRESHOLD = 5
  # Note: this is activated from the heatmap page by selecting "Viruses -
  # Phages". The default categories are all BUT phages, though the UI does not
  # indicate this.
  INCLUDE_PHAGE = false

  MINIMUM_ZSCORE_THRESHOLD = 1.7

  ALL_METRICS = [
    { text: "NT rPM", value: "NT.rpm" },
    { text: "NT Z Score", value: "NT.zscore" },
    { text: "NT r (total reads)", value: "NT.r" },
    { text: "NR rPM", value: "NR.rpm" },
    { text: "NR Z Score", value: "NR.zscore" },
    { text: "NR r (total reads)", value: "NR.r" }
  ].freeze

  # Samples and background are assumed here to be vieweable.
  def self.sample_taxons_dict(params, samples, background_id)
    return {} if samples.empty?

    num_results = params[:taxonsPerSample] ? params[:taxonsPerSample].to_i : DEFAULT_MAX_NUM_TAXONS
    min_reads = params[:minReads] ? params[:minReads].to_i : MINIMUM_READ_THRESHOLD
    removed_taxon_ids = (params[:removedTaxonIds] || []).map do |x|
      begin
        Integer(x)
      rescue ArgumentError
        nil
      end
    end
    removed_taxon_ids = removed_taxon_ids.compact
    categories = params[:categories]
    threshold_filters = if params[:thresholdFilters].is_a?(Array)
                          (params[:thresholdFilters] || []).map { |filter| JSON.parse(filter || "{}") }
                        else
                          JSON.parse(params[:thresholdFilters] || "[]")
                        end
    subcategories = if params[:subcategories] && params[:subcategories].respond_to?(:to_h)
                      params[:subcategories].to_h
                    else
                      JSON.parse(params[:subcategories] || "{}")
                    end
    include_phage = subcategories.fetch("Viruses", []).include?("Phage")
    read_specificity = params[:readSpecificity] ? params[:readSpecificity].to_i == 1 : false

    sort_by = params[:sortBy] &&
              HeatmapHelper::ALL_METRICS.map { |m| m[:value] }.include?(params[:sortBy]) ||
              HeatmapHelper::DEFAULT_TAXON_SORT_PARAM
    species_selected = params[:species] ? params[:species].to_i == 1 : false # Otherwise genus selected

    background_id = background_id && background_id > 0 ? background_id : samples.first.default_background_id

    results_by_pr = fetch_top_taxons(
      samples,
      background_id,
      categories,
      read_specificity,
      include_phage,
      num_results,
      min_reads,
      sort_by,
      threshold_filters
    )

    details = top_taxons_details(
      results_by_pr,
      num_results,
      sort_by,
      species_selected,
      threshold_filters
    )

    taxon_ids = details.pluck('tax_id')
    taxon_ids -= removed_taxon_ids

    unless taxon_ids.empty?
      # Refetch at genus level using species level
      parent_ids = species_selected ? [] : HeatmapHelper.fetch_parent_ids(taxon_ids, samples)
      results_by_pr = HeatmapHelper.fetch_samples_taxons_counts(samples, taxon_ids, parent_ids, background_id)
    end

    HeatmapHelper.samples_taxons_details(
      results_by_pr,
      samples,
      taxon_ids,
      species_selected,
      threshold_filters
{'fraction_non_alphanumeric': 0.10069101678183613, 'fraction_numerical': 0.0028204766605556338, 'mean_word_length': 2.9575892857142856, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 5, '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': '3194748', 'n_tokens_mistral': 2247, 'n_tokens_neox': 2164, 'n_words': 492}