{"record":{"id":"bac4e06a80ed27ac","repo":"instructure/canvas-lms","slug":"invalid-fields-after-ratings-fields","errorCode":null,"errorMessage":"Invalid fields after ratings: %{fields}","messagePattern":"Invalid fields after ratings: %(.+?)","errorType":"validation","errorClass":"ParseError","httpStatus":null,"severity":"error","filePath":"lib/outcomes/csv_importer.rb","lineNumber":143,"sourceCode":"      @file.rewind\n      count\n    end\n\n    def check_encoding(str)\n      encoded = str&.force_encoding(\"utf-8\")\n      valid = (encoded || \"\").valid_encoding?\n      raise ParseError, I18n.t(\"Not a valid utf-8 string: %{string}\", string: str.inspect) unless valid\n\n      encoded\n    end\n\n    def validate_headers(row, _index)\n      main_columns_end = row.find_index(\"ratings\") || row.length\n      headers = row.slice(0, main_columns_end).map(&:to_sym)\n\n      after_ratings = row[(main_columns_end + 1)..] || []\n      after_ratings = after_ratings.compact_blank.map(&:to_s)\n      raise ParseError, I18n.t(\"Invalid fields after ratings: %{fields}\", fields: after_ratings.inspect) unless after_ratings.empty?\n\n      missing = (REQUIRED_FIELDS - headers).map(&:to_s)\n      raise ParseError, I18n.t(\"Missing required fields: %{fields}\", fields: missing.inspect) unless missing.empty?\n\n      invalid = (headers - OPTIONAL_FIELDS - REQUIRED_FIELDS).map(&:to_s)\n      raise ParseError, I18n.t(\"Invalid fields: %{fields}\", fields: invalid.inspect) unless invalid.empty?\n\n      headers\n    end\n\n    def import_row(headers, row)\n      simple = headers.zip(row).to_h\n      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?","sourceCodeStart":125,"sourceCodeEnd":161,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/lib/outcomes/csv_importer.rb#L125-L161","documentation":"Raised by Outcomes::CsvImporter#validate_headers when the header row contains non-empty, non-nil values in the ratings* columns that are not the literal 'ratings' column position terminator — i.e. content appears immediately after the 'ratings' header that the importer cannot interpret. The importer treats everything from 'ratings' onward as rating pairs and requires nothing extra after the ratings block.","triggerScenarios":"A CSV header like `vendor_guid,title,description,ratings,extra_column` where 'extra_column' follows 'ratings' and its pairs; stray trailing values after the last rating description; commas at the end of the header row creating empty-but-shifted cells the importer interprets as extra fields.","commonSituations":"Hand-edited CSVs adding a custom column after the ratings section; exporting from another tool that appends columns; template misuse where rating pairs are misaligned so a description lands in the wrong slot.","solutions":["Remove any column headers/cells after the 'ratings' column; only rating points/description pairs belong there.","Regenerate the CSV from the official Canvas outcome import template.","Verify the header row exactly: required fields first, then a single 'ratings' column containing point/description pairs."],"exampleFix":"// before\nvendor_guid,title,ratings,notes\n// after\nvendor_guid,title,ratings","handlingStrategy":"validation","validationCode":"headers = CSV.open(path, &:first)\nidx = headers.index('ratings')\nextra = headers[(idx + 1)..].to_a.compact_blank\nraise \"unexpected fields after ratings: #{extra}\" unless extra.empty?","typeGuard":null,"tryCatchPattern":"begin\n  importer.parse_file\nrescue Outcomes::CsvImporter::ParseError => e\n  notify_user(\"Header row problem: #{e.message}\")\nend","preventionTips":["Never add columns after the ratings column","Use the official template as the single source of truth","Diff your header row against the template before each import"],"tags":["csv","headers","import","validation"],"backgroundTag":"schema-validation-failed","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"}