feat: add OCR text extraction and Postgres full-text search
- Create ocr_text table for storing per-region OCR results - Add tsvector search_vector column to photos with GIN index and auto-update trigger on filename/user_title/user_notes - Implement ocr_photo Celery task using rapidocr-onnxruntime - Add FTS leg to hybrid search: queries photos.search_vector and ocr_text via UNION, fused with semantic results via RRF (k=60) Migration 0004 backfills search_vector for existing rows. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -62,9 +62,30 @@ async def hybrid_search(
|
||||
except Exception as e:
|
||||
logger.warning("Semantic search failed (models may not be loaded): %s", e)
|
||||
|
||||
# ── FTS search (placeholder for PR5) ──────────────────────────────
|
||||
# Will be: tsvector @@ plainto_tsquery(q), ranked by ts_rank.
|
||||
# For now, skip.
|
||||
# ── FTS search (photos.search_vector + ocr_text) ────────────────
|
||||
if q:
|
||||
try:
|
||||
fts_stmt = text("""
|
||||
SELECT id, ts_rank(search_vector, plainto_tsquery('english', :q)) AS rank
|
||||
FROM photos
|
||||
WHERE search_vector @@ plainto_tsquery('english', :q)
|
||||
UNION
|
||||
SELECT o.photo_id AS id,
|
||||
MAX(o.confidence) AS rank
|
||||
FROM ocr_text o
|
||||
WHERE to_tsvector('english', o.text) @@ plainto_tsquery('english', :q)
|
||||
GROUP BY o.photo_id
|
||||
ORDER BY rank DESC
|
||||
LIMIT 200
|
||||
""")
|
||||
fts_rows = (await db.execute(fts_stmt, {"q": q})).fetchall()
|
||||
for rank, (photo_id, score) in enumerate(fts_rows):
|
||||
if photo_id not in results:
|
||||
results[photo_id] = {"semantic_rank": None, "fts_rank": rank}
|
||||
else:
|
||||
results[photo_id]["fts_rank"] = rank
|
||||
except Exception as e:
|
||||
logger.warning("FTS search failed: %s", e)
|
||||
|
||||
# ── RRF fusion ────────────────────────────────────────────────────
|
||||
k = 60
|
||||
|
||||
Reference in New Issue
Block a user