{"record":{"id":"fc53d12d4fd2a15a","repo":"instructure/canvas-lms","slug":"points-for-rating-tier-index-not-present","errorCode":null,"errorMessage":"Points for rating tier %{index} not present","messagePattern":"Points for rating tier %(.+?) not present","errorType":"validation","errorClass":"InvalidDataError","httpStatus":null,"severity":"error","filePath":"lib/outcomes/csv_importer.rb","lineNumber":174,"sourceCode":"      ratings = row[headers.length..]\n\n      object = simple.to_h\n      object[:ratings] = parse_ratings(ratings)\n      object[:learning_outcome_group_id] = @import[:learning_outcome_group_id]\n      if object[:mastery_points].present?\n        object[:mastery_points] = strict_parse_float(object[:mastery_points], I18n.t(\"mastery points\"))\n      end\n      if object[:friendly_description].present? && object[:friendly_description].length > 255\n        raise InvalidDataError, I18n.t(\"Friendly description is too long (maximum is 255 characters)\")\n      end\n\n      import_object(object)\n    end\n\n    def parse_ratings(ratings)\n      prior = nil\n      drop_trailing_nils(ratings).each_slice(2).to_a.map.with_index(1) do |(points, description), index|\n        raise InvalidDataError, I18n.t(\"Points for rating tier %{index} not present\", index:) if points.nil? || points.blank?\n\n        points = strict_parse_float(points, I18n.t(\"rating tier %{index} threshold\", index:))\n\n        if prior.present? && prior < points\n          raise InvalidDataError, I18n.t(\n            \"Points for tier %{index} must be less than points for prior tier (%{points} is greater than %{prior})\",\n            index:,\n            prior:,\n            points:\n          )\n        end\n\n        prior = points\n        { points:, description: }\n      end\n    end\n\n    def normalize_i18n(string)","sourceCodeStart":156,"sourceCodeEnd":192,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/lib/outcomes/csv_importer.rb#L156-L192","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Fill in a numeric value in every ratings points cell (points and description strictly alternate).","Fix the number of cells so pairs align: points, description, points, description...","Regenerate the ratings section from the template, one points cell per description."],"exampleFix":"// before\nratings: 3,,Meets,5,Exemplary\n// after\nratings: 3,Meets,5,Exemplary","handlingStrategy":"validation","validationCode":"cells = row['ratings'].to_a.drop_while { |c| c.nil? }.reverse.drop_while(&:nil?).reverse\npairs = cells.each_slice(2).to_a\nraise 'rating points missing' if pairs.any? { |pts, _| pts.nil? || pts.to_s.strip.empty? }","typeGuard":null,"tryCatchPattern":"begin\n  importer.run\nrescue Outcomes::Import::InvalidDataError => e\n  errors << \"Row #{i}: #{e.message}\"\nend","preventionTips":["Fill every rating points cell — no blanks inside the ratings section","Keep points/description pairs strictly alternating","Count commas: an odd number of ratings cells usually means a missing points value"],"tags":["csv","ratings","validation","import"],"backgroundTag":"empty-required-field","analyzedSha":"1c9f0bb8013ed69c4f2efe11fd483025469b7e6c","analyzedAt":"2026-09-15T20:33:18.891Z","contentChangedAt":"2026-09-15T20:33:18.891Z","schemaVersion":2},"datasetVersion":"2026-09-23T02:17:17.105Z"}