{"record":{"id":"f3bf5fd614eb2c27","repo":"we-promise/sure","slug":"could-not-extract-text-from-pdf-f3bf5f","errorCode":null,"errorMessage":"Could not extract text from PDF","messagePattern":"Could not extract text from PDF","errorType":"exception","errorClass":"Provider::Openai::Error","httpStatus":null,"severity":"error","filePath":"app/models/provider/openai/pdf_processor.rb","lineNumber":101,"sourceCode":"          \"opening_balance\": number or null,\n          \"closing_balance\": number or null,\n          \"currency\": \"USD/EUR/etc or null\",\n          \"account_holder\": \"Name or null\"\n        }\n      }\n    INSTRUCTIONS\n  end\n\n  private\n\n    PdfProcessingResult = Provider::LlmConcept::PdfProcessingResult\n\n    def process_with_text_extraction\n      effective_model = model.presence || Provider::Openai::DEFAULT_MODEL\n\n      # Extract text from PDF using pdf-reader gem\n      pdf_text = extract_text_from_pdf\n      raise Provider::Openai::Error, \"Could not extract text from PDF\" if pdf_text.blank?\n\n      # Truncate if too long (max ~100k chars to stay within token limits)\n      pdf_text = pdf_text.truncate(100_000) if pdf_text.length > 100_000\n\n      params = {\n        model: effective_model,\n        messages: [\n          { role: \"system\", content: instructions },\n          {\n            role: \"user\",\n            content: \"Please analyze the following document text and provide a structured summary:\\n\\n#{pdf_text}\"\n          }\n        ],\n        response_format: { type: \"json_object\" }\n      }\n\n      response = client.chat(parameters: params)\n","sourceCodeStart":83,"sourceCodeEnd":119,"githubUrl":"https://github.com/we-promise/sure/blob/e69894adb92547273377398c15f45c979cd9416a/app/models/provider/openai/pdf_processor.rb#L83-L119","documentation":"Raised by Provider::Openai::PdfProcessor#process_with_text_extraction when extract_text_from_pdf returns nil/empty. The helper constructs a PDF::Reader over the bytes, joins each page's text with page markers, and rescues every exception to nil (logging 'Failed to extract text from PDF'). Blank therefore means one of: pdf_content blank, reader raised (encrypted/corrupt/unsupported PDF), or every page.text came back empty — the classic scanned-PDF signature. Text over ~100k chars is truncated afterward, so length is not a trigger.","triggerScenarios":"Image-only scanned PDF fed to the text path; password-protected PDF throwing inside PDF::Reader; corrupt/truncated upload bytes; malformed xref tables from certain PDF generators; nil pdf_content passed through from an upload handler.","commonSituations":"Users uploading phone-scanned documents; e-statements exported with DRM/encryption flags; PDFs produced by niche tools with quirky internal structure; the vision fallback not selected because the routing logic assumed text would exist.","solutions":["Check the error log for the swallowed exception text to distinguish encrypted (password required) from corrupt (malformed PDF) from empty pages.","Route scanned PDFs to process_with_vision, which rasterizes pages via pdftoppm instead of reading text.","Pre-validate uploads: %PDF- header, non-trivial size, and at least one page with extractable text before choosing the text path.","For encrypted files, collect the password and decrypt first (e.g. with hexapdf/qpdf) before extraction."],"exampleFix":"# before\npdf_text = extract_text_from_pdf\nraise Provider::Openai::Error, \"Could not extract text from PDF\" if pdf_text.blank?\n\n# after\npdf_text = extract_text_from_pdf\nif pdf_text.blank?\n  Rails.logger.info(\"PDF has no text layer; falling back to vision processing\")\n  return process_with_vision\nend","handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"begin\n  processor.process\nrescue Provider::Openai::Error => e\n  raise unless e.message.include?(\"Could not extract text\")\n  processor.process_with_vision # same processor, image path handles scanned pages\nend","preventionTips":["Detect the no-text-layer case up front and choose the vision path directly instead of waiting for the raise.","Read the swallowed 'Failed to extract text from PDF' log to separate encrypted from corrupt files.","Decrypt password-protected PDFs before handing bytes to the text extractor."],"tags":["openai","pdf","pdf-reader","text-extraction","scanned-pdf"],"backgroundTag":"pdf-text-extraction-failed","analyzedSha":"e69894adb92547273377398c15f45c979cd9416a","analyzedAt":"2026-08-21T18:22:41.165Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}