9145060d8e
Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
355 lines
9.0 KiB
Markdown
355 lines
9.0 KiB
Markdown
# File Processing Status Architecture
|
|
|
|
## Overview
|
|
|
|
DocuElevate now uses a **dual-table architecture** for tracking file processing:
|
|
|
|
1. **`FileProcessingStep`** - Current state tracking (NEW)
|
|
2. **`ProcessingLog`** - Historical audit trail (EXISTING, PRESERVED)
|
|
|
|
This separation provides both **fast status queries** and **complete audit history**.
|
|
|
|
## Architecture
|
|
|
|
### FileProcessingStep Table (Current State)
|
|
|
|
**Purpose**: Definitive source of truth for current processing status
|
|
|
|
**Structure**:
|
|
```sql
|
|
CREATE TABLE file_processing_steps (
|
|
id INTEGER PRIMARY KEY,
|
|
file_id INTEGER NOT NULL,
|
|
step_name VARCHAR NOT NULL,
|
|
status VARCHAR NOT NULL, -- 'pending', 'in_progress', 'success', 'failure', 'skipped'
|
|
started_at TIMESTAMP,
|
|
completed_at TIMESTAMP,
|
|
error_message TEXT,
|
|
created_at TIMESTAMP DEFAULT NOW(),
|
|
updated_at TIMESTAMP DEFAULT NOW(),
|
|
UNIQUE (file_id, step_name)
|
|
)
|
|
```
|
|
|
|
**Key Features**:
|
|
- One row per step per file
|
|
- Updated when step status changes
|
|
- No scanning required - direct lookups
|
|
- Indexed for fast queries
|
|
|
|
### ProcessingLog Table (Audit Trail)
|
|
|
|
**Purpose**: Complete historical record of all processing events
|
|
|
|
**Structure**: (Unchanged from original)
|
|
```sql
|
|
CREATE TABLE processing_logs (
|
|
id INTEGER PRIMARY KEY,
|
|
file_id INTEGER,
|
|
task_id VARCHAR,
|
|
step_name VARCHAR,
|
|
status VARCHAR,
|
|
message VARCHAR,
|
|
detail TEXT,
|
|
timestamp TIMESTAMP DEFAULT NOW()
|
|
)
|
|
```
|
|
|
|
**Key Features**:
|
|
- Multiple rows per step (one per event)
|
|
- Never deleted - complete history
|
|
- Used for debugging, compliance, audit
|
|
- Detailed error messages and diagnostics
|
|
|
|
## How They Work Together
|
|
|
|
### When a File is Created
|
|
|
|
```python
|
|
from app.utils.step_manager import initialize_file_steps
|
|
|
|
# Create file record
|
|
file_record = FileRecord(...)
|
|
db.add(file_record)
|
|
db.commit()
|
|
|
|
# Initialize expected processing steps
|
|
initialize_file_steps(db, file_record.id)
|
|
# Creates FileProcessingStep rows with status='pending'
|
|
```
|
|
|
|
### When a Worker Processes a Step
|
|
|
|
```python
|
|
from app.utils.step_manager import update_step_status
|
|
|
|
# Worker starts processing
|
|
update_step_status(
|
|
db, file_id, "hash_file", "in_progress",
|
|
started_at=datetime.now()
|
|
)
|
|
|
|
# Worker also logs to ProcessingLog (for history)
|
|
log = ProcessingLog(
|
|
file_id=file_id,
|
|
step_name="hash_file",
|
|
status="in_progress",
|
|
message="Starting file hash calculation"
|
|
)
|
|
db.add(log)
|
|
db.commit()
|
|
|
|
# Worker completes successfully
|
|
update_step_status(
|
|
db, file_id, "hash_file", "success",
|
|
completed_at=datetime.now()
|
|
)
|
|
|
|
# Also log completion
|
|
log = ProcessingLog(
|
|
file_id=file_id,
|
|
step_name="hash_file",
|
|
status="success",
|
|
message="Hash calculated successfully: abc123..."
|
|
)
|
|
db.add(log)
|
|
db.commit()
|
|
```
|
|
|
|
### When Dashboard Queries Status
|
|
|
|
```python
|
|
from app.utils.step_manager import get_file_overall_status, get_step_summary
|
|
|
|
# Fast lookup - no log scanning!
|
|
overall = get_file_overall_status(db, file_id)
|
|
# Returns: {"status": "processing", "has_errors": False, ...}
|
|
|
|
# Get detailed step breakdown
|
|
summary = get_step_summary(db, file_id)
|
|
# Returns: {
|
|
# "main": {"success": 3, "in_progress": 1, "pending": 4},
|
|
# "uploads": {"success": 2, "queued": 4}
|
|
# }
|
|
```
|
|
|
|
### When Viewing Processing History
|
|
|
|
```python
|
|
# Logs provide complete timeline
|
|
logs = db.query(ProcessingLog)\
|
|
.filter(ProcessingLog.file_id == file_id)\
|
|
.order_by(ProcessingLog.timestamp.asc())\
|
|
.all()
|
|
|
|
# Shows every event with timestamps, messages, details
|
|
# Example:
|
|
# 2024-01-15 10:00:00 | hash_file | in_progress | Starting...
|
|
# 2024-01-15 10:00:05 | hash_file | success | Hash: abc123
|
|
# 2024-01-15 10:00:06 | check_text | in_progress | Checking...
|
|
# ... etc
|
|
```
|
|
|
|
## Migration from Logs
|
|
|
|
For existing files with only ProcessingLog entries:
|
|
|
|
```python
|
|
from app.utils.migrate_logs_to_steps import migrate_logs_to_steps, migrate_all_files
|
|
|
|
# Migrate single file
|
|
result = migrate_logs_to_steps(db, file_id)
|
|
# Parses logs chronologically to reconstruct final state
|
|
|
|
# Migrate all files (batch processing)
|
|
summary = migrate_all_files(db, batch_size=100)
|
|
# Finds files with logs but no steps, migrates them all
|
|
|
|
# Dry run mode (test without committing)
|
|
result = migrate_logs_to_steps(db, file_id, dry_run=True)
|
|
```
|
|
|
|
The migration utility:
|
|
1. Reads all logs for a file in chronological order
|
|
2. Tracks state transitions (pending → in_progress → success/failure)
|
|
3. Determines final status per step
|
|
4. Creates FileProcessingStep entries
|
|
5. Preserves all original logs unchanged
|
|
|
|
## Benefits
|
|
|
|
### Performance
|
|
- **Old**: Scan all logs, dedupe, find latest → O(n log n)
|
|
- **New**: Single table lookup → O(1)
|
|
|
|
### Correctness
|
|
- **Old**: Dependent on log ordering, can miss retries
|
|
- **New**: Definitive state, always up-to-date
|
|
|
|
### Flexibility
|
|
- **Old**: Hard-coded step list, can't add dynamic steps
|
|
- **New**: Dynamic upload destinations, custom workflows
|
|
|
|
### Debugging
|
|
- **Old**: Only logs available
|
|
- **New**: Current state + complete history
|
|
|
|
## Example Workflow
|
|
|
|
```python
|
|
# 1. File uploaded
|
|
file = FileRecord(filehash="abc123", ...)
|
|
db.add(file)
|
|
db.commit()
|
|
|
|
# 2. Initialize steps
|
|
initialize_file_steps(db, file.id)
|
|
# FileProcessingStep: 9 rows with status='pending'
|
|
|
|
# 3. Add upload destinations (discovered later)
|
|
add_upload_steps(db, file.id, ["dropbox", "s3", "nextcloud"])
|
|
# FileProcessingStep: +6 rows with status='pending'
|
|
|
|
# 4. Worker processes each step
|
|
for step in ["hash_file", "create_file_record", "check_text", ...]:
|
|
# Start
|
|
update_step_status(db, file.id, step, "in_progress", started_at=now())
|
|
log_event(db, file.id, step, "in_progress", "Starting...")
|
|
|
|
# Do work...
|
|
|
|
# Complete
|
|
update_step_status(db, file.id, step, "success", completed_at=now())
|
|
log_event(db, file.id, step, "success", "Completed successfully")
|
|
|
|
# 5. Dashboard queries
|
|
status = get_file_overall_status(db, file.id)
|
|
# status="completed", all steps done
|
|
|
|
# 6. User views history
|
|
logs = get_processing_logs(db, file.id)
|
|
# Complete timeline with all events, messages, timestamps
|
|
```
|
|
|
|
## Status Flow
|
|
|
|
```
|
|
pending → in_progress → success
|
|
↓
|
|
failure
|
|
```
|
|
|
|
## Main Processing Steps
|
|
|
|
Defined in `app/utils/step_manager.py`:
|
|
```python
|
|
MAIN_PROCESSING_STEPS = [
|
|
"hash_file",
|
|
"create_file_record",
|
|
"check_text",
|
|
"extract_text",
|
|
"extract_metadata_with_gpt",
|
|
"embed_metadata_into_pdf",
|
|
"finalize_document_storage",
|
|
"send_to_all_destinations",
|
|
]
|
|
```
|
|
|
|
## Upload Steps
|
|
|
|
Dynamically created per destination:
|
|
- `queue_{destination}` - Queued for upload
|
|
- `upload_to_{destination}` - Actual upload
|
|
|
|
Example: Dropbox
|
|
- `queue_dropbox` - Added to upload queue
|
|
- `upload_to_dropbox` - Upload to Dropbox
|
|
|
|
## API Reference
|
|
|
|
### Step Manager Functions
|
|
|
|
```python
|
|
# Initialize steps for new file
|
|
initialize_file_steps(db: Session, file_id: int) -> None
|
|
|
|
# Add upload destination steps
|
|
add_upload_steps(db: Session, file_id: int, destinations: List[str]) -> None
|
|
|
|
# Update step status
|
|
update_step_status(
|
|
db: Session,
|
|
file_id: int,
|
|
step_name: str,
|
|
status: str,
|
|
error_message: Optional[str] = None,
|
|
started_at: Optional[datetime] = None,
|
|
completed_at: Optional[datetime] = None
|
|
) -> None
|
|
|
|
# Query step status
|
|
get_file_step_status(db: Session, file_id: int) -> Dict[str, Dict]
|
|
get_file_overall_status(db: Session, file_id: int) -> Dict
|
|
get_step_summary(db: Session, file_id: int) -> Dict
|
|
```
|
|
|
|
### Migration Functions
|
|
|
|
```python
|
|
# Migrate single file
|
|
migrate_logs_to_steps(
|
|
db: Session,
|
|
file_id: int,
|
|
dry_run: bool = False
|
|
) -> Dict
|
|
|
|
# Migrate all files
|
|
migrate_all_files(
|
|
db: Session,
|
|
batch_size: int = 100,
|
|
dry_run: bool = False
|
|
) -> Dict
|
|
|
|
# Verify migration
|
|
verify_migration(db: Session, file_id: int) -> Dict
|
|
```
|
|
|
|
## Best Practices
|
|
|
|
1. **Always log AND update status**: Workers should do both
|
|
2. **Logs are append-only**: Never delete or modify logs
|
|
3. **Status is current state**: FileProcessingStep reflects latest status
|
|
4. **Initialize early**: Create steps when file is created
|
|
5. **Add uploads dynamically**: Add upload steps when destinations are known
|
|
6. **Handle retries**: update_step_status() handles overwriting previous status
|
|
7. **Preserve history**: Use logs for debugging and compliance
|
|
|
|
## Troubleshooting
|
|
|
|
### Status shows wrong state
|
|
- Check FileProcessingStep table directly
|
|
- Use `get_file_step_status()` to see all steps
|
|
- Verify workers are calling `update_step_status()`
|
|
|
|
### Missing steps
|
|
- Check if `initialize_file_steps()` was called
|
|
- For uploads, check if `add_upload_steps()` was called
|
|
- Use migration utility to backfill from logs
|
|
|
|
### Dashboard shows old data
|
|
- Clear any caching
|
|
- Verify status table is being queried, not logs
|
|
- Check `app/views/files.py` is using `get_step_summary_from_table()`
|
|
|
|
## Migration Checklist
|
|
|
|
For deploying to existing system:
|
|
|
|
- [ ] Apply database migration: `alembic upgrade head`
|
|
- [ ] Run migration utility: `migrate_all_files(db, dry_run=True)` to test
|
|
- [ ] Run actual migration: `migrate_all_files(db, dry_run=False)`
|
|
- [ ] Verify: Check a few files with `verify_migration()`
|
|
- [ ] Update workers to call `update_step_status()`
|
|
- [ ] Update file creation to call `initialize_file_steps()`
|
|
- [ ] Monitor dashboard for correct status display
|