instructure/canvas-lms · error · InvalidDataError

Points for rating tier

Error message

Points for rating tier %{index} not present

What it means

Raised by Outcomes::CsvImporter#parse_ratings when a rating tier has a missing/blank points value. Ratings are read as consecutive points,description pairs; after dropping trailing nils, any pair whose points cell is nil or blank triggers this InvalidDataError naming the 1-based tier index. This guarantees every rating tier has a numeric threshold.

Solutions

  1. Fill in a numeric value in every ratings points cell (points and description strictly alternate).
  2. Fix the number of cells so pairs align: points, description, points, description...
  3. Regenerate the ratings section from the template, one points cell per description.

Example fix

// before
ratings: 3,,Meets,5,Exemplary
// after
ratings: 3,Meets,5,Exemplary
Defensive patterns

Strategy: validation

Validate before calling

cells = row['ratings'].to_a.drop_while { |c| c.nil? }.reverse.drop_while(&:nil?).reverse
pairs = cells.each_slice(2).to_a
raise 'rating points missing' if pairs.any? { |pts, _| pts.nil? || pts.to_s.strip.empty? }

Try / catch

begin
  importer.run
rescue Outcomes::Import::InvalidDataError => e
  errors << "Row #{i}: #{e.message}"
end

Prevention

When it happens

Trigger: Ratings cells like `4,,Exemplary,` — an empty points cell between descriptions; an odd number of ratings cells so slicing pairs puts a description where points should be; trailing commas creating misalignment.

Common situations: Hand-built CSV where someone left the points cell empty assuming a default; extra/missing commas shifting the pair alignment; templates where users filled descriptions but not points.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15). Data as JSON: /api/errors/fc53d12d4fd2a15a. Report an issue: GitHub.

Appendix: source

Thrown at lib/outcomes/csv_importer.rb:174

      ratings = row[headers.length..]

      object = simple.to_h
      object[:ratings] = parse_ratings(ratings)
      object[:learning_outcome_group_id] = @import[:learning_outcome_group_id]
      if object[:mastery_points].present?
        object[:mastery_points] = strict_parse_float(object[:mastery_points], I18n.t("mastery points"))
      end
      if object[:friendly_description].present? && object[:friendly_description].length > 255
        raise InvalidDataError, I18n.t("Friendly description is too long (maximum is 255 characters)")
      end

      import_object(object)
    end

    def parse_ratings(ratings)
      prior = nil
      drop_trailing_nils(ratings).each_slice(2).to_a.map.with_index(1) do |(points, description), index|
        raise InvalidDataError, I18n.t("Points for rating tier %{index} not present", index:) if points.nil? || points.blank?

        points = strict_parse_float(points, I18n.t("rating tier %{index} threshold", index:))

        if prior.present? && prior < points
          raise InvalidDataError, I18n.t(
            "Points for tier %{index} must be less than points for prior tier (%{points} is greater than %{prior})",
            index:,
            prior:,
            points:
          )
        end

        prior = points
        { points:, description: }
      end
    end

    def normalize_i18n(string)

View on GitHub (pinned to 1c9f0bb801)