Skip to content

Commit 03cb898

Browse files
MLaitarovskyclaude
andcommitted
fix: pre-extract PDF text in API to share with Celery worker
Railway runs the API and Worker in separate containers with isolated filesystems. The worker cannot read files saved by the API. Fix: extract text from the PDF immediately in the upload endpoint and store it in document.raw_text (column already existed). The pipeline's step 1 now checks raw_text first — if populated it uses that and builds a synthetic page_map, skipping the file read entirely. The file-read path is kept as a fallback for local dev where the filesystem is shared. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent b227dc6 commit 03cb898

2 files changed

Lines changed: 42 additions & 2 deletions

File tree

apps/api/app/routers/documents.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Document endpoints — upload, list, detail, delete."""
22

3+
import logging
34
import os
45
import uuid
56

@@ -20,8 +21,10 @@
2021
DocumentUploadResponse,
2122
)
2223
from app.tasks.process_document import process_document
24+
from app.utils.pdf_parser import extract_text_from_pdf
2325

2426
router = APIRouter(prefix="/api/documents", tags=["documents"])
27+
logger = logging.getLogger(__name__)
2528

2629
MAX_FILE_SIZE = 10 * 1024 * 1024 # 10 MB
2730

@@ -73,13 +76,29 @@ async def upload_document(
7376
with open(file_path, "wb") as f:
7477
f.write(contents)
7578

79+
# Pre-extract text from the PDF here in the API process.
80+
# The API and Celery worker run in separate containers on Railway and do
81+
# NOT share a filesystem — the worker cannot open a file saved by the API.
82+
# By extracting text now and storing it in raw_text, the worker can read
83+
# it from the database and skip the file-read step entirely.
84+
raw_text: str | None = None
85+
page_count: int | None = None
86+
try:
87+
extracted_text, page_map = extract_text_from_pdf(file_path)
88+
raw_text = extracted_text
89+
page_count = len(page_map)
90+
except Exception as exc:
91+
logger.warning("Pre-extraction failed, worker will attempt: %s", exc)
92+
7693
# Create the document record
7794
document = Document(
7895
team_id=user.team_id,
7996
uploaded_by=user.id,
8097
filename=safe_name,
8198
file_path=file_path,
8299
file_size_bytes=len(contents),
100+
raw_text=raw_text,
101+
page_count=page_count,
83102
status="uploaded",
84103
)
85104
db.add(document)

apps/api/app/services/extraction_pipeline.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,11 +118,32 @@ def run(self) -> None:
118118

119119
def _step1_extract_text(self) -> None:
120120
self._publish(1, "Extracting text from PDF...", 10)
121-
logger.info("Step 1/5 — Extracting text from %s", self.document.file_path)
122121

122+
if self.document.raw_text:
123+
# Text was pre-extracted by the API on upload. In production the
124+
# API and Worker run in separate containers that don't share a
125+
# filesystem, so we read from the database instead of the file.
126+
logger.info(
127+
"Step 1/5 — Using pre-extracted text (%d chars)",
128+
len(self.document.raw_text),
129+
)
130+
self.full_text = self.document.raw_text
131+
# Build a synthetic page_map so the rest of the pipeline works.
132+
# Page numbers on clauses will all be 1, which is acceptable.
133+
self.page_map = [
134+
{
135+
"page": 1,
136+
"start_char": 0,
137+
"end_char": len(self.full_text),
138+
"text": self.full_text,
139+
}
140+
]
141+
return
142+
143+
# Fallback: extract from file directly (local dev with shared filesystem).
144+
logger.info("Step 1/5 — Extracting text from %s", self.document.file_path)
123145
self.full_text, self.page_map = extract_text_from_pdf(self.document.file_path)
124146

125-
# Update the document with extracted text and page count
126147
self.document.raw_text = self.full_text
127148
self.document.page_count = len(self.page_map)
128149
self.db.commit()

0 commit comments

Comments
 (0)