pdf-quiz-generator/backend/app/tasks/pdf_tasks.py
Daniel 2cbbfe00c3 Tag filtering, multi-category, bug fixes, image validation, docs
- Fix tag filtering (sa_text import shadowing caused UnboundLocalError)
- Add TagBrowser component with per-section search
- Multi-category selection (OR within categories, AND with tags)
- AI image validation: has_figure field in extraction prompt
- Skip known branding images by MD5 hash + dimension filters
- Fix quiz timer auto-submit (wrong useEffect dependency)
- Fix QuizResponse schema: section_id nullable
- Fix Question.quiz_id → source_quiz_id attribute name
- Fix SQL injection in quizzes.py vector search
- Add PDF processing progress steps via Redis
- Add delete user from admin panel
- Admin page: no spinner flash on data refresh
- Upload progress: axios 1.x e.progress, remove manual Content-Type
- Duplicate model error: 409 with clear message
- Backend startup: retry DDL migration on lock timeout
- Replace all silent except:pass with warning logs
- Comprehensive multi-page documentation (docs/)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 22:48:26 +02:00

82 lines
2.9 KiB
Python

import logging
import json
import time
from app.tasks import celery_app
from app.database import SessionLocal
from app.models.pdf_document import PDFDocument
from app.services import pdf_service, vector_service
from app.config import settings
logger = logging.getLogger(__name__)
def _redis():
import redis
return redis.from_url(settings.REDIS_URL, decode_responses=True)
def _push_step(r, doc_id: int, step: str, message: str):
r.rpush(f"pdf:steps:{doc_id}", json.dumps({"step": step, "message": message, "ts": time.time()}))
r.expire(f"pdf:steps:{doc_id}", 3600)
@celery_app.task(name="process_pdf")
def process_pdf(document_id: int, file_path: str):
"""Background task: extract PDF text and store in ChromaDB."""
db = SessionLocal()
r = _redis()
try:
doc = db.query(PDFDocument).filter(PDFDocument.id == document_id).first()
if not doc:
logger.error(f"Document {document_id} not found")
return
_push_step(r, document_id, "start", "Starting PDF processing…")
# Get page count
total_pages = pdf_service.get_page_count(file_path)
doc.total_pages = total_pages
db.commit()
_push_step(r, document_id, "pages", f"Found {total_pages} pages")
# Extract text from all pages
_push_step(r, document_id, "text", "Extracting text from pages…")
pages = pdf_service.extract_text_by_page(file_path)
if not pages:
doc.status = "error"
doc.error_message = "No text could be extracted from the PDF"
db.commit()
_push_step(r, document_id, "error", "No text could be extracted")
return
_push_step(r, document_id, "text", f"Extracted text from {len(pages)} pages")
# Store in ChromaDB
total_chunks = 0
for page_num, text in pages.items():
chunks = vector_service.chunk_text(text)
total_chunks += len(chunks)
_push_step(r, document_id, "vector", f"Vectorizing {total_chunks} chunks across {len(pages)} pages…")
vector_service.store_pages(document_id, pages)
_push_step(r, document_id, "vector", "Vectorization complete")
# Mark as ready
doc.status = "ready"
db.commit()
_push_step(r, document_id, "done", f"Ready — {total_pages} pages, {len(pages)} with text")
logger.info(f"Document {document_id} processed: {total_pages} pages, {len(pages)} with text")
except Exception as e:
logger.exception(f"Error processing document {document_id}")
_push_step(r, document_id, "error", f"Failed: {str(e)[:200]}")
try:
doc = db.query(PDFDocument).filter(PDFDocument.id == document_id).first()
if doc:
doc.status = "error"
doc.error_message = str(e)[:500]
db.commit()
except Exception:
pass
finally:
db.close()