Files
gh-christianlouis-docuelevate/app/utils/file_queries.py
T
copilot-swe-agent[bot] 37a3f7aae7 fix(ocr): sync workflow steps with process_with_ocr replacing legacy azure step
- Update _compute_processing_flow to recognize process_with_ocr as the OCR
  stage and remap legacy process_with_azure_document_intelligence log entries
  for backward compatibility
- Normalize legacy OCR step name in _compute_step_summary log fallback
- Add process_with_ocr to REAL_MAIN_STEPS/REAL_STEPS in step_manager,
  file_status, and file_queries (keeping legacy name for old DB entries)
- Update retry logic in api/files.py to retry failed OCR via process_with_ocr
  (handles both step names as aliases)
- Fix process_document.py to log process_with_ocr as skipped (not azure step)
  for the local text extraction path

Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
2026-02-24 22:22:06 +00:00

125 lines
4.8 KiB
Python

"""
Shared file query utilities for filtering files by processing status.
This module contains reusable query logic for filtering FileRecord objects
based on their processing status using the FileProcessingStep table.
"""
from typing import Optional
from sqlalchemy import or_
from sqlalchemy.orm import Query, Session
from app.models import FileProcessingStep, FileRecord
def apply_status_filter(query: Query, db: Session, status: Optional[str]) -> Query:
"""
Apply status filter to a FileRecord query using FileProcessingStep table.
This function modifies a SQLAlchemy query to filter files based on their
processing status by examining associated FileProcessingStep entries.
Only tracks "real" processing steps that represent user-facing status:
- Main steps: create_file_record, check_text, extract_text, process_with_ocr,
process_with_azure_document_intelligence (legacy), extract_metadata_with_gpt,
embed_metadata_into_pdf, finalize_document_storage, send_to_all_destinations
- Upload steps: queue_*, upload_to_*
Diagnostic/internal steps (poll_task, upload_file, set_custom_fields, etc.) are ignored
as they may not complete properly and don't affect the actual status.
Args:
query: The base SQLAlchemy query for FileRecord objects
db: Database session for creating subqueries
status: Status filter to apply. Valid values:
- "pending": Files with no real FileProcessingStep entries
- "processing": Files with in_progress real steps
- "failed": Files with failure real steps
- "completed": Files with all real steps success/skipped
- None: No filter applied (returns query unchanged)
Returns:
Modified query with status filter applied
Examples:
>>> query = db.query(FileRecord)
>>> query = apply_status_filter(query, db, "completed")
>>> files = query.all()
"""
if not status:
return query
# Define which steps are "real" status-determining steps
# Only high-level logical steps and actual upload destinations (not queue_* steps)
from app.config import settings
REAL_STEPS = {
"create_file_record",
"check_text",
"extract_text",
"process_with_ocr",
"process_with_azure_document_intelligence",
"extract_metadata_with_gpt",
"embed_metadata_into_pdf",
"finalize_document_storage",
"send_to_all_destinations",
"upload_to_dropbox",
"upload_to_paperless",
"upload_to_google_drive",
"upload_to_ftp",
"upload_to_onedrive",
"upload_to_webdav",
"upload_to_sftp",
"upload_to_nextcloud",
"upload_to_paperless_ngx",
"upload_to_email",
"upload_to_s3",
}
# Add check_for_duplicates if deduplication is enabled
if settings.enable_deduplication:
REAL_STEPS.add("check_for_duplicates")
# Filter to only real steps
real_steps_subq = db.query(FileProcessingStep).filter(FileProcessingStep.step_name.in_(REAL_STEPS))
if status == "pending":
# Files with no real steps (never started processing)
subq = real_steps_subq.distinct().subquery()
query = query.filter(~FileRecord.id.in_(db.query(subq.c.file_id)))
elif status == "processing":
# Files with in_progress real steps
subq = real_steps_subq.filter(FileProcessingStep.status == "in_progress").distinct().subquery()
query = query.filter(FileRecord.id.in_(db.query(subq.c.file_id)))
elif status == "failed":
# Files with failure real steps
subq = real_steps_subq.filter(FileProcessingStep.status == "failure").distinct().subquery()
query = query.filter(FileRecord.id.in_(db.query(subq.c.file_id)))
elif status == "completed":
# Files where all real steps are either success or skipped (no failures or in_progress)
# Exclude duplicates from completed
query = query.filter(FileRecord.is_duplicate.is_(False))
# Get files that have real steps
files_with_real_steps = real_steps_subq.distinct().subquery()
# Get files with failures or in_progress on real steps
files_with_issues = (
real_steps_subq.filter(
or_(FileProcessingStep.status == "failure", FileProcessingStep.status == "in_progress")
)
.distinct()
.subquery()
)
# Select files with real steps that don't have issues
query = query.filter(FileRecord.id.in_(db.query(files_with_real_steps.c.file_id))).filter(
~FileRecord.id.in_(db.query(files_with_issues.c.file_id))
)
elif status == "duplicate":
# Files marked as duplicates
query = query.filter(FileRecord.is_duplicate.is_(True))
return query