feat(search): add content-finding filters, saved searches, and text quality to Search view
- Add tags, sender, text_quality filters to search API and Meilisearch client - Add sender and ocr_text_length to Meilisearch filterable attributes - Expand saved search allowed filter keys to include q, document_type, language, sender, text_quality - Add filters panel and saved searches UI to the Search view template - Add tests for new search filters, saved search keys, and search view elements Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
This commit is contained in:
@@ -23,10 +23,14 @@ router = APIRouter(prefix="/saved-searches", tags=["saved-searches"])
|
||||
|
||||
DbSession = Annotated[Session, Depends(get_db)]
|
||||
|
||||
# Allowed filter keys that can be saved
|
||||
# Allowed filter keys that can be saved.
|
||||
# Files-view keys: search, mime_type, status, storage_provider, sort_by, sort_order
|
||||
# Search-view keys: q, document_type, language, sender, text_quality
|
||||
# Shared keys: tags, date_from, date_to
|
||||
ALLOWED_FILTER_KEYS = frozenset(
|
||||
{
|
||||
"search",
|
||||
"q",
|
||||
"mime_type",
|
||||
"status",
|
||||
"date_from",
|
||||
@@ -35,6 +39,10 @@ ALLOWED_FILTER_KEYS = frozenset(
|
||||
"tags",
|
||||
"sort_by",
|
||||
"sort_order",
|
||||
"document_type",
|
||||
"language",
|
||||
"sender",
|
||||
"text_quality",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
+13
-1
@@ -29,6 +29,12 @@ def search_api(
|
||||
mime_type: Optional[str] = Query(None, description="Filter by MIME type (e.g. application/pdf)"),
|
||||
document_type: Optional[str] = Query(None, description="Filter by document type (e.g. Invoice)"),
|
||||
language: Optional[str] = Query(None, description="Filter by language code (e.g. de, en)"),
|
||||
tags: Optional[str] = Query(None, description="Filter by tag (exact match)"),
|
||||
sender: Optional[str] = Query(None, description="Filter by sender/absender (exact match)"),
|
||||
text_quality: Optional[str] = Query(
|
||||
None,
|
||||
description="Filter by OCR text quality: no_text, low, medium, high",
|
||||
),
|
||||
date_from: Optional[int] = Query(None, description="Filter results created after this Unix timestamp"),
|
||||
date_to: Optional[int] = Query(None, description="Filter results created before this Unix timestamp"),
|
||||
page: int = Query(1, ge=1, description="Page number (1-based)"),
|
||||
@@ -50,6 +56,9 @@ def search_api(
|
||||
- mime_type: Filter by MIME type
|
||||
- document_type: Filter by document type
|
||||
- language: Filter by language code
|
||||
- tags: Filter by tag (exact match on a single tag)
|
||||
- sender: Filter by sender/absender (exact match)
|
||||
- text_quality: Filter by OCR text quality (no_text, low, medium, high)
|
||||
- date_from: Unix timestamp lower bound
|
||||
- date_to: Unix timestamp upper bound
|
||||
- page: Page number (default: 1)
|
||||
@@ -57,7 +66,7 @@ def search_api(
|
||||
|
||||
Example:
|
||||
```
|
||||
GET /api/search?q=invoice&document_type=Invoice&date_from=1704067200&page=1&per_page=20
|
||||
GET /api/search?q=invoice&document_type=Invoice&tags=amazon&date_from=1704067200&page=1&per_page=20
|
||||
```
|
||||
|
||||
Response:
|
||||
@@ -90,6 +99,9 @@ def search_api(
|
||||
mime_type=mime_type,
|
||||
document_type=document_type,
|
||||
language=language,
|
||||
tags=tags,
|
||||
sender=sender,
|
||||
text_quality=text_quality,
|
||||
date_from=date_from,
|
||||
date_to=date_to,
|
||||
page=page,
|
||||
|
||||
@@ -33,8 +33,10 @@ _INDEX_SETTINGS = {
|
||||
"document_type",
|
||||
"language",
|
||||
"tags",
|
||||
"sender",
|
||||
"created_at_ts",
|
||||
"file_id",
|
||||
"ocr_text_length",
|
||||
],
|
||||
"sortableAttributes": [
|
||||
"created_at_ts",
|
||||
@@ -55,6 +57,7 @@ _INDEX_SETTINGS = {
|
||||
"file_size",
|
||||
"created_at_ts",
|
||||
"ocr_text",
|
||||
"ocr_text_length",
|
||||
],
|
||||
"rankingRules": [
|
||||
"words",
|
||||
@@ -142,6 +145,7 @@ def _build_document(file_record: "FileRecord", text: str, metadata: dict) -> dic
|
||||
"file_size": file_record.file_size or 0,
|
||||
"created_at_ts": created_at_ts,
|
||||
"ocr_text": text or "",
|
||||
"ocr_text_length": len(text) if text else 0,
|
||||
}
|
||||
|
||||
|
||||
@@ -202,6 +206,9 @@ def search_documents(
|
||||
mime_type: Optional[str] = None,
|
||||
document_type: Optional[str] = None,
|
||||
language: Optional[str] = None,
|
||||
tags: Optional[str] = None,
|
||||
sender: Optional[str] = None,
|
||||
text_quality: Optional[str] = None,
|
||||
date_from: Optional[int] = None,
|
||||
date_to: Optional[int] = None,
|
||||
page: int = 1,
|
||||
@@ -214,6 +221,9 @@ def search_documents(
|
||||
mime_type: Optional MIME-type filter.
|
||||
document_type: Optional document type filter.
|
||||
language: Optional language filter (ISO 639-1, e.g. "de").
|
||||
tags: Optional tag filter (exact match on a single tag).
|
||||
sender: Optional sender/absender filter (exact match).
|
||||
text_quality: Optional text quality filter: no_text, low, medium, high.
|
||||
date_from: Optional lower bound Unix timestamp for created_at.
|
||||
date_to: Optional upper bound Unix timestamp for created_at.
|
||||
page: 1-based page number.
|
||||
@@ -240,6 +250,21 @@ def search_documents(
|
||||
filters.append(f'document_type = "{document_type}"')
|
||||
if language:
|
||||
filters.append(f'language = "{language}"')
|
||||
if tags:
|
||||
filters.append(f'tags = "{tags}"')
|
||||
if sender:
|
||||
filters.append(f'sender = "{sender}"')
|
||||
if text_quality:
|
||||
# Translate text_quality labels into ocr_text_length ranges
|
||||
_tq_filters = {
|
||||
"no_text": "ocr_text_length = 0",
|
||||
"low": "ocr_text_length > 0 AND ocr_text_length < 500",
|
||||
"medium": "ocr_text_length >= 500 AND ocr_text_length < 2000",
|
||||
"high": "ocr_text_length >= 2000",
|
||||
}
|
||||
tq_expr = _tq_filters.get(text_quality)
|
||||
if tq_expr:
|
||||
filters.append(tq_expr)
|
||||
if date_from is not None:
|
||||
filters.append(f"created_at_ts >= {date_from}")
|
||||
if date_to is not None:
|
||||
|
||||
Reference in New Issue
Block a user