{"record":{"id":"821f8dafbe1ca069","repo":"we-promise/sure","slug":"estimated-values-must-start-with-the-exact-prefix","errorCode":null,"errorMessage":"estimated values must start with the exact prefix #{ESTIMATED_PREFIX.inspect}","messagePattern":"estimated values must start with the exact prefix #(.+?)","errorType":"exception","errorClass":"Provenance::Citation::InvalidError","httpStatus":null,"severity":"error","filePath":"app/models/provenance/citation.rb","lineNumber":47,"sourceCode":"    # citation text and passing as an ungraded — but plausible-looking — source.\n    GRADE_SUFFIX = /\\(grade:\\s*(?<grade>[^)]*)\\)\\s*\\z/\n    ESTIMATED_MARKER = /\\Aestimated\\s*:/i\n\n    class InvalidError < StandardError; end\n\n    attr_reader :raw, :text, :grade\n\n    class << self\n      # Returns a Citation, or raises InvalidError with a message written for the\n      # caller that has to fix it (an agent, usually).\n      def parse!(raw)\n        value = raw.to_s.strip\n\n        raise InvalidError, \"source citation is required\" if value.blank?\n        raise InvalidError, \"source citation must be #{MAX_LENGTH} characters or fewer\" if value.length > MAX_LENGTH\n\n        if value.match?(ESTIMATED_MARKER) && !value.start_with?(ESTIMATED_PREFIX)\n          raise InvalidError, \"estimated values must start with the exact prefix #{ESTIMATED_PREFIX.inspect}\"\n        end\n\n        if (suffix = value.match(GRADE_SUFFIX)) && !GRADES.include?(suffix[:grade])\n          raise InvalidError, \"reliability grade must be one of #{GRADES.join(\", \")} (got #{suffix[:grade].inspect})\"\n        end\n\n        match = value.match(FORMAT)\n        raise InvalidError, \"source citation does not match #{grammar}\" unless match\n\n        text = match[:text].to_s.strip\n        if text.length < MIN_TEXT_LENGTH\n          raise InvalidError, \"source citation must name the document it came from\"\n        end\n\n        estimated = match[:estimated].present?\n        grade = match[:grade]\n\n        if estimated && grade.blank?","sourceCodeStart":29,"sourceCodeEnd":65,"githubUrl":"https://github.com/we-promise/sure/blob/e69894adb92547273377398c15f45c979cd9416a/app/models/provenance/citation.rb#L29-L65","documentation":"Provenance::Citation.parse! raises InvalidError when the string matches the ESTIMATED_MARKER (starts with 'estimated' followed by optional whitespace and a colon, case-insensitive) but does not start with the exact prefix 'estimated: ' (lowercase, single space). This stops variants like 'Estimated:', 'estimated :', or 'ESTIMATED:' so an estimate can never lose its canonical marker on the way in.","triggerScenarios":"Citation beginning 'Estimated: ...', 'estimated : ...', 'ESTIMATED: ...', or 'estimated:\\t...'; agent writing the marker with different casing or spacing.","commonSituations":"LLM capitalizing the prefix; human typing 'Estimated:'; a template that trims or reformats the leading prefix.","solutions":["Use the exact prefix: start the citation with 'estimated: ' (lowercase, one space) — e.g. 'estimated: linear interpolation over anchors (grade: C)'.","Normalize before parsing: strip, downcase just the marker, or value.sub(/\\Aestimated\\s*:/i, 'estimated: ').","Include the grade suffix, since estimates also require a grade.","Test with Provenance::Citation.valid? first and show the grammar in the error UI."],"exampleFix":"# before\nProvenance::Citation.parse!(\"Estimated: interpolated from 2024 anchors (grade: C)\")\n\n# after\nnormalized = raw.to_s.strip.sub(/\\Aestimated\\s*:/i, \"estimated: \")\nProvenance::Citation.parse!(normalized)","handlingStrategy":"validation","validationCode":"v = value.to_s\n!v.match?(/\\Aestimated\\s*:/i) || v.start_with?(\"estimated: \")","typeGuard":"def estimated_prefix_ok?(v)\n  v = v.to_s.strip\n  !v.match?(Provenance::Citation::ESTIMATED_MARKER) || v.start_with?(Provenance::Citation::ESTIMATED_PREFIX)\nend","tryCatchPattern":"begin\n  Provenance::Citation.parse!(source)\nrescue Provenance::Citation::InvalidError => e\n  source = source.sub(/\\Aestimated\\s*:/i, \"estimated: \") # normalize and retry once\n  retry\nend","preventionTips":["Show the exact 'estimated: ' prefix in UI placeholder and agent prompts.","Normalize case/spacing of the marker before parse rather than rejecting the user."],"tags":["provenance","validation","string-format","ruby"],"backgroundTag":"source-citation-invalid","analyzedSha":"e69894adb92547273377398c15f45c979cd9416a","analyzedAt":"2026-08-21T18:22:41.165Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}