feat(docs): add built-in help section with How-To guides embedded in app

- Add MkDocs Material docs build stage to Dockerfile and Dockerfile.local
- Mount pre-built docs as static files at /help/ in FastAPI (app/main.py)
- Add app/views/help.py with /help → /help/ permanent redirect route
- Register help router in app/views/__init__.py
- Add Help nav link to base.html (public + app nav, desktop + mobile)
- Create how-to guides: HP printer, ScanSnap, watched folder, email ingestion, mobile scanning
- Update mkdocs.yml with How-To Guides section and Material theme palette
- Add optional docs service (squidfunk/mkdocs-material) to docker-compose.yaml with docs profile
- Add mkdocs-material to requirements-dev.txt
- Add /docs_build to .gitignore
- Add tests for help view (8 tests, 100% coverage on help.py)

Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-03-07 20:33:10 +00:00
parent 6ebf66275c
commit 46b2f17acc
17 changed files with 1225 additions and 0 deletions
+1
View File
@@ -171,6 +171,7 @@ venv.bak/
# mkdocs documentation
/site
/docs_build
# mypy
.mypy_cache/
+19
View File
@@ -7,6 +7,22 @@ WORKDIR /app
COPY requirements.txt /app/
RUN pip install --no-cache-dir -r requirements.txt
# ── Documentation build stage ───────────────────────────────────────────────
FROM python:3.14.1-slim AS docs-builder
WORKDIR /docs
# Install MkDocs Material and its dependencies
COPY docs/requirements.txt /docs/requirements.txt
RUN pip install --no-cache-dir -r requirements.txt
# Copy documentation sources
COPY docs /docs/docs
COPY mkdocs.yml /docs/mkdocs.yml
# Build the static documentation site
RUN mkdocs build --config-file /docs/mkdocs.yml --site-dir /docs/docs_build
# Second stage for the actual runtime
FROM python:3.14.3-slim
@@ -43,6 +59,9 @@ COPY ./BUILD_DATE /app/BUILD_DATE
COPY ./GIT_SHA /app/GIT_SHA
COPY ./RUNTIME_INFO /app/RUNTIME_INFO
# Copy the pre-built MkDocs documentation site (served at /help)
COPY --from=docs-builder /docs/docs_build /app/docs_build
# Create runtime_info directory
RUN mkdir -p /app/runtime_info
+16
View File
@@ -6,6 +6,19 @@ WORKDIR /app
COPY requirements.txt /app/
RUN pip install --no-cache-dir -r requirements.txt
# ── Documentation build stage ───────────────────────────────────────────────
FROM python:3.14.1-slim AS docs-builder
WORKDIR /docs
COPY docs/requirements.txt /docs/requirements.txt
RUN pip install --no-cache-dir -r requirements.txt
COPY docs /docs/docs
COPY mkdocs.yml /docs/mkdocs.yml
RUN mkdocs build --config-file /docs/mkdocs.yml --site-dir /docs/docs_build
FROM python:3.14.1-slim
WORKDIR /app
@@ -33,6 +46,9 @@ COPY ./LICENSE /app/LICENSE
COPY ./VERSION /app/VERSION
COPY ./BUILD_DATE /app/BUILD_DATE
# Copy the pre-built MkDocs documentation site (served at /help)
COPY --from=docs-builder /docs/docs_build /app/docs_build
# Local fallbacks for build metadata
RUN echo "local" > /app/GIT_SHA \
&& echo "local" > /app/RUNTIME_INFO
+9
View File
@@ -202,6 +202,15 @@ if os.path.exists(static_dir):
else:
print(f"WARNING: Static directory not found at {static_dir}. Static files will not be served.")
# Mount the built MkDocs documentation site at /help/
# The docs are pre-built into docs_build/ during the Docker image build.
# When running locally, run `mkdocs build` from the repo root first.
docs_build_dir = pathlib.Path(__file__).parents[1] / "docs_build"
if os.path.exists(docs_build_dir):
app.mount("/help", StaticFiles(directory=str(docs_build_dir), html=True), name="help_docs")
else:
print(f"INFO: Help docs not found at {docs_build_dir}. Run 'mkdocs build' to generate them.")
# Custom exception handlers that return JSON for API routes and HTML for frontend routes
@app.exception_handler(HTTPException)
+2
View File
@@ -12,6 +12,7 @@ from app.views.filemanager import router as filemanager_router
# Import all the view routers
from app.views.general import router as general_router
from app.views.google_drive import router as google_drive_router
from app.views.help import router as help_router # Built-in help / How-To docs
from app.views.license_routes import router as license_router # Add the license router
from app.views.onboarding import router as onboarding_router
from app.views.onedrive import router as onedrive_router
@@ -43,3 +44,4 @@ router.include_router(subscriptions_router) # Pricing + subscription pages
router.include_router(plans_router) # Admin Plan Designer
router.include_router(onboarding_router) # User onboarding wizard
router.include_router(pipelines_router) # Processing pipelines
router.include_router(help_router) # Built-in help / How-To docs
+27
View File
@@ -0,0 +1,27 @@
"""
Help documentation view routes.
Serves the built MkDocs documentation site at /help.
The static site is built during the Docker image build and placed at docs_build/.
"""
import logging
import pathlib
from fastapi import Request
from fastapi.responses import RedirectResponse
from app.views.base import APIRouter
logger = logging.getLogger(__name__)
router = APIRouter()
# Path to the built MkDocs documentation
_DOCS_BUILD_DIR = pathlib.Path(__file__).parents[2] / "docs_build"
@router.get("/help", include_in_schema=False)
async def help_redirect(request: Request) -> RedirectResponse:
"""Redirect /help to /help/ so the MkDocs index is served correctly."""
return RedirectResponse(url="/help/", status_code=301)
+19
View File
@@ -72,3 +72,22 @@ services:
image: redis:alpine
container_name: document_redis
restart: always
# Optional: standalone docs server using MkDocs Material (live-reload during development).
# Start with: docker compose --profile docs up docs
# Access at: http://localhost:8001
# Note: The /help route in the main app serves pre-built docs from the Docker image.
# This service is for content authors who want live preview while editing documentation.
docs:
image: squidfunk/mkdocs-material:latest
container_name: docuelevate_docs
restart: "no"
profiles:
- docs
volumes:
- ./docs:/docs/docs
- ./mkdocs.yml:/docs/mkdocs.yml
working_dir: /docs
command: ["serve", "--dev-addr=0.0.0.0:8000", "--config-file=/docs/mkdocs.yml"]
ports:
- "8001:8000"
+80
View File
@@ -0,0 +1,80 @@
# How-To Guides
Welcome to the DocuElevate How-To Guides. These step-by-step articles help you connect common scanners, printers, and mobile devices to DocuElevate for automatic document ingestion and processing.
---
## Document Ingestion Guides
Choose the method that matches your hardware or workflow:
### 🖨️ Scanners & Printers
| Guide | Description |
|-------|-------------|
| [HP Enterprise Printer Setup](howto/HPPrinterSetup.md) | Configure HP MFPs for automatic scan-to-DocuElevate via email, network folder, or WebDAV |
| [Fujitsu ScanSnap Setup](howto/SnapScanSetup.md) | Set up ScanSnap iX1600/iX1500/iX500 to send scans directly to DocuElevate |
| [Watched Folder Setup](howto/WatchedFolderSetup.md) | Monitor a local or network folder and automatically ingest any files dropped into it |
### 📧 Email & Cloud
| Guide | Description |
|-------|-------------|
| [Email Ingestion](howto/EmailIngestion.md) | Route documents sent as email attachments into DocuElevate automatically |
| [Mobile Scanning](howto/MobileScanning.md) | Use iOS/Android apps to capture and upload documents from your phone |
---
## Quick Comparison: Which Method Is Right for You?
| Method | Best For | Setup Complexity |
|--------|----------|-----------------|
| **Web Upload** | Occasional one-off uploads | ⭐ Very Easy |
| **Watched Folder** | Any scanner that saves to a folder | ⭐⭐ Easy |
| **Email Ingestion** | Scanners with Scan-to-Email, fax services | ⭐⭐ Easy |
| **HP Printer (SMB)** | HP Enterprise MFPs on a corporate network | ⭐⭐⭐ Medium |
| **ScanSnap + Cloud** | Home/office ScanSnap via Dropbox/Drive | ⭐⭐ Easy |
| **Mobile App** | On-the-go document capture | ⭐ Very Easy |
| **API Integration** | Custom workflows, developer integrations | ⭐⭐⭐⭐ Advanced |
---
## General Configuration Tips
### Enable OCR for Searchable PDFs
DocuElevate applies OCR (Optical Character Recognition) to scanned documents, making them fully searchable. Configure the OCR engine in your `.env`:
```env
OCR_ENABLED=true
OCR_ENGINE=tesseract # or: azure, google
OCR_LANGUAGE=eng # ISO 639-2 language code
```
### Set Up Automatic Cloud Backup
After processing, DocuElevate can store documents in your preferred cloud storage. Configure in Settings or `.env`:
```env
DEFAULT_STORAGE_TARGET=dropbox # or: gdrive, onedrive, s3, nextcloud
```
### Configure Notifications
Get notified when documents are processed:
```env
NOTIFICATION_ENABLED=true
NOTIFICATION_EMAIL=you@example.com
```
See [Notifications Setup](NotificationsSetup.md) for webhook, Slack, and other integrations.
---
## Need Help?
- Browse the full [Documentation](UserGuide.md)
- Check [Troubleshooting](Troubleshooting.md) for common issues
- View [Configuration Reference](ConfigurationGuide.md) for all settings
- Explore the [API Reference](API.md) for programmatic access
+154
View File
@@ -0,0 +1,154 @@
# How to Set Up Automatic Document Ingestion via Email
Many devices (scanners, printers, fax services, and apps) can send documents as email attachments. This guide explains how to automatically route those attachments into DocuElevate.
---
## Overview
The Email Ingestion workflow works like this:
```
Scanner/Device → Email (SMTP) → Monitored Mailbox → DocuElevate API → Processing & Storage
```
DocuElevate periodically checks a designated email inbox, downloads PDF/image attachments, and processes them through the standard document pipeline.
---
## Prerequisites
- An email account dedicated to document ingestion (e.g., `scan@yourdomain.com`)
- IMAP access enabled for that account
- DocuElevate running with the Celery worker active
---
## Configuration
Add the following to your DocuElevate `.env` file:
```env
# Email ingestion settings
EMAIL_INGESTION_ENABLED=true
EMAIL_INGESTION_IMAP_HOST=mail.yourdomain.com
EMAIL_INGESTION_IMAP_PORT=993
EMAIL_INGESTION_IMAP_SSL=true
EMAIL_INGESTION_USERNAME=scan@yourdomain.com
EMAIL_INGESTION_PASSWORD=your-email-password
EMAIL_INGESTION_FOLDER=INBOX
EMAIL_INGESTION_INTERVAL=60 # Check every 60 seconds
EMAIL_INGESTION_MARK_SEEN=true # Mark emails as read after processing
EMAIL_INGESTION_ALLOWED_SENDERS= # Comma-separated allowlist (empty = allow all)
```
Restart DocuElevate after saving the configuration:
```bash
docker compose restart api worker
```
---
## Supported File Types
DocuElevate will process the following attachment types from emails:
| Type | Extension | Notes |
|------|-----------|-------|
| PDF | `.pdf` | Native support; OCR applied if not searchable |
| JPEG/PNG | `.jpg`, `.jpeg`, `.png` | Converted to PDF before processing |
| TIFF | `.tif`, `.tiff` | Common format from older scanners/fax |
| Multi-page TIFF | `.tif` | Full multi-page support |
---
## Setting Up Your Scanner/Device
### HP Printers Scan to Email
See the detailed guide: [HP Enterprise Printer Setup](./HPPrinterSetup.md#option-a-scan-to-email--docuelevate-api-upload)
### Fujitsu ScanSnap Send by Email
See the detailed guide: [ScanSnap Setup](./SnapScanSetup.md#method-3-scan-to-email--docuelevate)
### iOS/Android Scanning Apps
Most mobile scanning apps (Adobe Scan, Microsoft Lens, SwiftScan) can email scans:
1. Scan your document.
2. Use the app's **Share** or **Send** function.
3. Select **Email** and enter `scan@yourdomain.com`.
4. DocuElevate will pick up the attachment within the configured interval.
### Fax-to-Email Services
Services like eFax, RingCentral Fax, or Twilio Fax can forward incoming faxes as email attachments. Configure them to send to `scan@yourdomain.com`.
---
## Security Considerations
> **Important:** Only process emails from trusted sources to avoid ingesting malicious documents.
Use the `EMAIL_INGESTION_ALLOWED_SENDERS` setting to restrict which email addresses can submit documents:
```env
EMAIL_INGESTION_ALLOWED_SENDERS=scanner@office.com,printer@office.com,fax@office.com
```
DocuElevate will silently skip emails from addresses not in the allowlist.
Additionally:
- Use a **dedicated email account** solely for document ingestion
- Enable **app-specific passwords** (Gmail, Outlook) instead of your main account password
- Store credentials in environment variables, never in config files committed to version control
---
## Monitoring
Check the DocuElevate worker logs to verify email ingestion is running:
```bash
docker logs document_worker --follow
```
You should see log entries like:
```
INFO Email ingestion: checking inbox scan@yourdomain.com
INFO Email ingestion: found 3 new messages
INFO Email ingestion: processing attachment invoice-2024.pdf from printer@office.com
INFO Email ingestion: queued document ID 142 for processing
```
---
## Troubleshooting
**No emails are being processed?**
→ Verify IMAP credentials and that IMAP is enabled on your mail server.
→ Check firewall rules: port 993 (SSL) or 143 (plain) must be open from DocuElevate to the mail server.
**Gmail not working?**
→ Enable "App Passwords" in your Google Account security settings.
→ Use the App Password (not your main Google password) for `EMAIL_INGESTION_PASSWORD`.
**Attachments processed but files are empty?**
→ Some email clients send inline images instead of attachments. Check the raw email source.
**Emails keep getting re-processed?**
→ Set `EMAIL_INGESTION_MARK_SEEN=true` to mark emails as read after processing.
→ Alternatively, configure a separate ingestion folder and move/delete emails after processing.
---
## Related Documentation
- [HP Enterprise Printer Setup](./HPPrinterSetup.md)
- [ScanSnap Setup](./SnapScanSetup.md)
- [Configuration Guide](../ConfigurationGuide.md)
- [Notifications Setup](../NotificationsSetup.md)
+198
View File
@@ -0,0 +1,198 @@
# How to Set Up Automatic Document Ingestion with an HP Enterprise Printer
This guide explains how to configure an HP Enterprise printer/MFP (Multi-Function Printer) to automatically send scanned documents to DocuElevate for processing and storage.
---
## Prerequisites
- An HP Enterprise printer or MFP with **Scan to Email** or **Scan to Network Folder** capability
- DocuElevate running and accessible on your network
- Admin access to the HP printer's Embedded Web Server (EWS)
- (Optional) An SMTP server or a configured email address for Scan to Email
---
## Option A: Scan to Email → DocuElevate API Upload
HP Enterprise printers can send scanned documents as email attachments. You can set up a dedicated inbox that forwards documents to DocuElevate via the REST API.
### Step 1: Configure Scan to Email on the Printer
1. Open a browser and navigate to the printer's IP address (e.g., `http://192.168.1.100`) to access the **Embedded Web Server (EWS)**.
2. Go to **Scan****Scan to E-mail**.
3. Enable **Scan to E-mail** and configure your SMTP server settings.
4. Create a **Quick Set** (shortcut) for the destination:
- **From:** `scanner@yourdomain.com`
- **To:** `docuelevate-inbox@yourdomain.com` (the receiving address you'll configure)
- **File Type:** PDF
- **Resolution:** 200300 DPI (recommended)
- **Color Mode:** Grayscale or Black & White for text documents
### Step 2: Set Up an Email-to-DocuElevate Bridge
Use a lightweight tool like [imapfilter](https://github.com/lefcha/imapfilter) or a simple Python script (see below) to poll the inbox and upload attachments to DocuElevate via its REST API.
**Example Python script (`email_to_docuelevate.py`):**
```python
import imaplib
import email
import requests
import os
IMAP_HOST = "mail.yourdomain.com"
IMAP_USER = "docuelevate-inbox@yourdomain.com"
IMAP_PASS = os.environ["IMAP_PASS"]
DOCUELEVATE_URL = "http://your-docuelevate-host:8000"
API_KEY = os.environ["DOCUELEVATE_API_KEY"]
def fetch_and_upload():
mail = imaplib.IMAP4_SSL(IMAP_HOST)
mail.login(IMAP_USER, IMAP_PASS)
mail.select("INBOX")
_, msg_ids = mail.search(None, "UNSEEN")
for msg_id in msg_ids[0].split():
_, msg_data = mail.fetch(msg_id, "(RFC822)")
msg = email.message_from_bytes(msg_data[0][1])
for part in msg.walk():
if part.get_content_maintype() == "multipart":
continue
if part.get("Content-Disposition") is None:
continue
filename = part.get_filename()
if filename and filename.lower().endswith(".pdf"):
payload = part.get_payload(decode=True)
files = {"file": (filename, payload, "application/pdf")}
headers = {"Authorization": f"Bearer {API_KEY}"}
resp = requests.post(
f"{DOCUELEVATE_URL}/api/upload",
files=files,
headers=headers
)
print(f"Uploaded {filename}: {resp.status_code}")
mail.store(msg_id, "+FLAGS", "\\Seen")
mail.logout()
if __name__ == "__main__":
fetch_and_upload()
```
Run this script via a cron job every few minutes:
```bash
*/5 * * * * /usr/bin/python3 /opt/email_to_docuelevate.py >> /var/log/docuelevate_import.log 2>&1
```
---
## Option B: Scan to Network Folder (SMB/CIFS)
HP Enterprise printers can scan directly to a network folder. You can configure a watched folder that DocuElevate monitors for new files.
### Step 1: Set Up a Shared Network Folder
On your DocuElevate server (or any reachable server), create a shared folder:
```bash
# Create the shared folder
mkdir -p /srv/scanner-inbox
# Install Samba
sudo apt-get install samba
# Add to /etc/samba/smb.conf:
[scanner-inbox]
path = /srv/scanner-inbox
writable = yes
guest ok = yes
force user = nobody
```
Restart Samba: `sudo systemctl restart smbd`
### Step 2: Configure the HP Printer for Scan to Network Folder
1. Open the printer's **Embedded Web Server (EWS)**.
2. Go to **Scan****Scan to Network Folder**.
3. Click **Add** to create a new Quick Set:
- **UNC Path:** `\\192.168.1.200\scanner-inbox` (replace with your server's IP)
- **Username/Password:** Leave blank for guest, or provide credentials
- **File Type:** PDF (Searchable PDF if available)
- **Resolution:** 200300 DPI
4. Test the connection from the EWS interface.
### Step 3: Configure DocuElevate to Watch the Folder
In your DocuElevate `.env` configuration:
```env
# Enable folder watching
WATCH_FOLDER_ENABLED=true
WATCH_FOLDER_PATH=/srv/scanner-inbox
WATCH_FOLDER_INTERVAL=30 # seconds between checks
```
DocuElevate's Celery worker will automatically detect and process new files placed in the watched folder.
---
## Option C: Scan to FTP/WebDAV
DocuElevate supports FTP and WebDAV as upload targets. HP printers can send scanned documents directly.
### WebDAV Configuration
1. In the EWS, go to **Scan****Save to SharePoint** or **Save to Network Folder**.
2. Some HP models support WebDAV directly — configure the WebDAV URL to point to DocuElevate's WebDAV endpoint (if enabled):
- **URL:** `http://your-docuelevate-host:8000/webdav/inbox/`
- **Username/Password:** Your DocuElevate credentials
### FTP Configuration
1. Ensure an FTP server is running alongside DocuElevate (or configure one in docker-compose).
2. In the EWS, configure **Scan to FTP**:
- **FTP Server:** `192.168.1.200`
- **Port:** `21`
- **Remote Path:** `/scanner-inbox/`
3. DocuElevate's watch folder will pick up the FTP-delivered files.
---
## Recommended Scanner Quick Set Settings
| Setting | Recommended Value |
|---------|------------------|
| File Type | PDF (Searchable PDF / PDF/A if available) |
| Resolution | 200300 DPI |
| Color Mode | Auto Detect or Grayscale |
| Sides | Auto Detect (2-sided) |
| Original Size | Auto Detect |
| Orientation | Auto Detect |
---
## Troubleshooting
**Printer can't connect to the network folder?**
→ Verify the IP address and that the Samba/SMB service is running. Check firewall rules (port 445/TCP).
**Scanned PDFs aren't being processed?**
→ Check DocuElevate's Celery worker logs: `docker logs document_worker --follow`
**Email attachments not arriving?**
→ Verify SMTP settings on the printer. Check spam filters on the receiving mailbox.
**Poor OCR quality?**
→ Increase scan resolution to 300 DPI and use Grayscale mode for text documents.
---
## Related Documentation
- [DocuElevate Configuration Guide](../ConfigurationGuide.md)
- [Storage Architecture](../StorageArchitecture.md)
- [Troubleshooting](../Troubleshooting.md)
+152
View File
@@ -0,0 +1,152 @@
# How to Scan Documents from Your Phone or Tablet
DocuElevate works seamlessly with mobile scanning apps. This guide covers the best ways to capture documents with your phone or tablet and get them into DocuElevate automatically.
---
## Option 1: DocuElevate Web Upload (Simplest)
The easiest way is to use DocuElevate's built-in web interface directly from your mobile browser.
1. Open your phone's browser and navigate to your DocuElevate instance (e.g., `http://your-docuelevate-host:8000`).
2. Tap **Upload** in the top navigation.
3. Tap **Choose File** — your phone will open the camera or file picker.
4. Select **Camera** to capture a photo of the document, or pick an existing file.
5. Tap **Upload** — DocuElevate will process the image and convert it to a searchable PDF.
> **Tip:** Use good lighting and hold the phone steady for better OCR quality.
---
## Option 2: DocuElevate Browser Extension
The **DocuElevate Browser Extension** for Chrome/Firefox allows one-tap uploading from your mobile browser.
See the [Browser Extension guide](../BrowserExtension.md) for installation and setup instructions.
---
## Option 3: Mobile Scanning Apps
These apps provide superior document capture (auto perspective correction, multi-page, etc.) and can send directly to DocuElevate.
### Microsoft Lens (iOS / Android)
Microsoft Lens is free and integrates with OneDrive, which DocuElevate supports natively.
1. Install **Microsoft Lens** from the App Store or Google Play.
2. Scan your document.
3. Save to **OneDrive** in a folder you've connected to DocuElevate.
4. DocuElevate will automatically process new files in your OneDrive folder.
### Adobe Scan (iOS / Android)
Adobe Scan produces high-quality searchable PDFs.
1. Install **Adobe Scan** from the App Store or Google Play.
2. Scan your document.
3. Tap **Share****Save to Files** (iOS) or **Share** (Android).
4. Choose to save to your connected cloud storage (Dropbox, Google Drive) monitored by DocuElevate.
**Or use email ingestion:**
1. Tap **Share****Email**.
2. Send to your DocuElevate ingestion email address.
3. See [Email Ingestion Setup](./EmailIngestion.md) for configuration.
### SwiftScan (iOS)
SwiftScan (formerly Scanbot) offers excellent auto-capture and document enhancement.
1. Install **SwiftScan** from the App Store.
2. Scan your document.
3. Configure the **Auto-Upload** feature:
- Go to **Settings****Cloud Services**.
- Connect Dropbox, Google Drive, or OneDrive.
- Set the upload folder to your DocuElevate-monitored folder.
4. Every scan is automatically uploaded and processed by DocuElevate.
### CamScanner (iOS / Android)
1. Install **CamScanner**.
2. Configure **Auto Backup** to Dropbox or Google Drive.
3. Point DocuElevate at the same cloud folder.
---
## Option 4: iOS Shortcuts Automation
On iPhone/iPad, you can create an **iOS Shortcut** that scans a document and uploads it directly to DocuElevate's API.
### Creating the Shortcut
1. Open the **Shortcuts** app on your iPhone.
2. Tap **+** to create a new shortcut.
3. Add the following actions:
- **Scan Document** — opens the camera for scanning
- **Get Contents of URL** — configures the API call:
- **URL:** `http://your-docuelevate-host:8000/api/upload`
- **Method:** POST
- **Headers:** `Authorization: Bearer YOUR_API_KEY`
- **Request Body:** Form data with `file` = Scanned Document
4. Name the shortcut "Send to DocuElevate".
5. Add it to your home screen for one-tap scanning.
```
Shortcut flow:
┌─────────────┐ ┌──────────────┐ ┌──────────────────────┐
│ Scan Document│ → │ Select pages │ → │ POST to DocuElevate │
│ (Camera) │ │ & crop │ │ /api/upload │
└─────────────┘ └──────────────┘ └──────────────────────┘
```
---
## Option 5: Android Automation with Tasker
On Android, **Tasker** can automate document upload when files appear in a specific folder.
1. Install **Tasker** from Google Play.
2. Create a **Profile** triggered by **File Created** in your scanner app's output folder.
3. Add a **Task** that calls DocuElevate's upload API using the **HTTP Request** action.
---
## Recommended Mobile Scanning Settings
For best results with DocuElevate's OCR engine:
| Setting | Recommended Value |
|---------|------------------|
| Output Format | PDF |
| Resolution/Quality | High (equivalent to 300 DPI) |
| Color Mode | Auto (greyscale for text, color for mixed) |
| Perspective Correction | On (auto-straighten) |
| Filter | Document / Black & White for text |
| Multi-page | Combine into single PDF |
---
## Troubleshooting
**Upload fails from mobile browser?**
→ Check that DocuElevate is accessible from your phone's network (same WiFi, or publicly reachable).
→ Verify your session is still logged in.
**OCR quality is poor?**
→ Ensure good lighting, no shadows on the document.
→ Use your scanning app's auto-enhance or document filter.
→ Increase resolution to "High" or "Best" in the app settings.
**Files synced to cloud but not processed?**
→ Check DocuElevate's cloud storage connection in Settings.
→ View worker logs: `docker logs document_worker --tail 50`
---
## Related Documentation
- [Email Ingestion Setup](./EmailIngestion.md)
- [HP Enterprise Printer Setup](./HPPrinterSetup.md)
- [ScanSnap Setup](./SnapScanSetup.md)
- [Browser Extension](../BrowserExtension.md)
- [API Reference](../API.md)
+192
View File
@@ -0,0 +1,192 @@
# How to Set Up Fujitsu ScanSnap to Automatically Send Documents to DocuElevate
This guide walks you through configuring a Fujitsu ScanSnap scanner to automatically upload scanned documents to DocuElevate for AI-powered processing and storage.
---
## Supported ScanSnap Models
This guide applies to the following ScanSnap models:
- **iX1600 / iX1500 / iX1400** Wi-Fi and USB, ScanSnap Home software
- **iX500 / iX100** USB/Wi-Fi, ScanSnap Manager software
- **S1300i / S1100i** USB, ScanSnap Manager software
> **Note:** ScanSnap Home (for newer models) and ScanSnap Manager (for older models) differ in their profile/job configuration interface. Both methods are covered below.
---
## Method 1: Scan to Folder → DocuElevate Watched Folder
This is the simplest method and works with all ScanSnap models.
### Step 1: Configure DocuElevate's Watched Folder
Add the following to your DocuElevate `.env` file and restart the service:
```env
WATCH_FOLDER_ENABLED=true
WATCH_FOLDER_PATH=/srv/scan-inbox
WATCH_FOLDER_INTERVAL=10
```
Create the directory and set permissions:
```bash
mkdir -p /srv/scan-inbox
chmod 777 /srv/scan-inbox
```
If running DocuElevate in Docker, mount this folder into the container in your `docker-compose.yaml`:
```yaml
services:
api:
volumes:
- /srv/scan-inbox:/srv/scan-inbox
worker:
volumes:
- /srv/scan-inbox:/srv/scan-inbox
```
### Step 2: Configure ScanSnap Home (iX1600/iX1500/iX1400)
1. Open **ScanSnap Home** on your computer.
2. Click **+** to create a new profile.
3. Select **Save to folder** as the action.
4. Configure the profile:
- **Folder:** `/srv/scan-inbox` (or a local folder that syncs to it)
- **File format:** PDF
- **Image quality:** Normal or Better
- **Color mode:** Auto
- **Scanning side:** Both sides (auto)
5. Enable **Convert to searchable PDF** if your ScanSnap and ScanSnap Home version support it.
6. Name the profile "DocuElevate" and save.
Now, pressing the scan button while this profile is active will send the scanned PDF directly to the watched folder.
### Step 3: Configure ScanSnap Manager (older models)
1. Right-click the ScanSnap Manager icon in the system tray.
2. Select **Scan Button Settings**.
3. Choose the **Save** tab.
4. Set **Image saving folder** to `/srv/scan-inbox` (or use a shared network path).
5. Under **File format**, select **PDF**.
6. Click **OK** to save.
---
## Method 2: Scan to Cloud Storage → DocuElevate Sync
If you already use Dropbox, Google Drive, or OneDrive with ScanSnap, you can leverage those integrations to feed documents into DocuElevate.
### Using Dropbox
1. In ScanSnap Home, create a profile with action **Save to Dropbox**.
2. Set the destination folder to `ScanSnap/` (or any folder).
3. In DocuElevate settings, configure Dropbox as your storage provider and point DocuElevate to monitor/pull from that folder.
4. DocuElevate will process new documents as they appear in Dropbox.
### Using Google Drive
1. Create a ScanSnap profile that saves to **Google Drive** under a dedicated folder (e.g., `ScanSnap/Inbox`).
2. Configure DocuElevate's Google Drive integration to use that folder as a source/destination.
---
## Method 3: Scan to Email → DocuElevate
ScanSnap can send scanned documents as email attachments. Pair this with the email-to-DocuElevate bridge described in the [HP Printer Guide](./HPPrinterSetup.md#option-a-scan-to-email--docuelevate-api-upload) for automatic ingestion.
1. In ScanSnap Home, create a profile with action **Send by E-mail**.
2. Configure the recipient address as your DocuElevate-monitored mailbox.
3. Set file format to **PDF**.
4. The email bridge script will pick up the attachment and upload it to DocuElevate.
---
## Method 4: ScanSnap Cloud (iX1600 / ScanSnap Home 3.x+)
The ScanSnap iX1600 supports **ScanSnap Cloud**, which can send directly to cloud services.
1. Open the **ScanSnap Home** app or use the printer's touchscreen.
2. Configure a **ScanSnap Cloud** profile that saves to **Google Drive** or **Dropbox**.
3. Point DocuElevate to monitor that cloud folder.
---
## Recommended Scan Settings
| Setting | Recommended Value |
|---------|------------------|
| File Format | PDF (Searchable PDF if available) |
| Image Quality | Normal (200 DPI) for text, Better (300 DPI) for photos |
| Color Mode | Auto (detects black/white vs color) |
| Scanning Side | Both Sides (Auto) |
| Compression | Medium |
| Rotate | Auto |
| Remove blank pages | Yes |
| Correct skewed scans | Yes |
---
## Automating with ScanSnap Home Profiles
You can create multiple profiles in ScanSnap Home for different document types:
| Profile | Settings | Notes |
|---------|----------|-------|
| **Documents** | B&W, 200 DPI, PDF | For invoices, letters, contracts |
| **Photos** | Color, 300 DPI, PDF | For photo documents |
| **Receipts** | B&W, 200 DPI, PDF, Remove blank pages | For expense reports |
| **Business Cards** | Color, 300 DPI, JPEG | May require separate OCR |
Each profile can be assigned to the scanner's shortcut button (on models with a touchscreen).
---
## Setting Up One-Button Scanning
On the **ScanSnap iX1600** with its touchscreen:
1. Tap the profile name (e.g., "DocuElevate") on the scanner's display.
2. The scanner will use that profile for the next scan.
3. Press the physical **Scan** button.
4. The document is scanned and automatically delivered to DocuElevate's watched folder.
On models without a touchscreen:
1. Set the "DocuElevate" profile as the **default profile** in ScanSnap Manager/Home.
2. Press the **Scan** button — the document goes directly to DocuElevate.
---
## Troubleshooting
**Files appear in the folder but DocuElevate doesn't process them?**
→ Check that `WATCH_FOLDER_ENABLED=true` in your `.env` and that the worker can read the folder:
```bash
docker logs document_worker --tail 50
```
**ScanSnap can't find the network folder?**
→ Ensure the folder is shared over SMB/CIFS (Windows share). See the [HP Printer Setup guide](./HPPrinterSetup.md#step-1-set-up-a-shared-network-folder) for Samba configuration.
**Scanned PDFs have poor text recognition?**
→ Increase scan resolution to 300 DPI. Enable "Convert to searchable PDF" in ScanSnap Home if available.
**Files are processed twice (duplicates)?**
→ Enable duplicate detection in DocuElevate settings. Check [Troubleshooting](../Troubleshooting.md) for deduplication options.
**ScanSnap doesn't appear in ScanSnap Home after network change?**
→ Re-run the ScanSnap network setup wizard. Ensure the scanner and your computer are on the same Wi-Fi network/subnet.
---
## Related Documentation
- [HP Enterprise Printer Setup](./HPPrinterSetup.md)
- [DocuElevate Configuration Guide](../ConfigurationGuide.md)
- [Dropbox Setup](../DropboxSetup.md)
- [Google Drive Setup](../GoogleDriveSetup.md)
- [Troubleshooting](../Troubleshooting.md)
+212
View File
@@ -0,0 +1,212 @@
# How to Set Up a Watched Folder for Automatic Document Ingestion
The **Watched Folder** feature allows DocuElevate to automatically detect and process documents placed in a specific local directory. Any scanner, application, or script that saves files to that folder will have its output automatically ingested.
---
## Overview
```
Scanner / Any App → Drop files in folder → DocuElevate watches for new files → Process & Store
```
This is one of the simplest ingestion methods and works with virtually any scanner, OCR app, or document workflow tool.
---
## Configuration
Add these settings to your `.env` file:
```env
WATCH_FOLDER_ENABLED=true
WATCH_FOLDER_PATH=/srv/docuelevate/watch
WATCH_FOLDER_INTERVAL=30 # Polling interval in seconds
WATCH_FOLDER_RECURSIVE=false # Watch subdirectories too
WATCH_FOLDER_DELETE_AFTER=true # Delete originals after successful processing
WATCH_FOLDER_EXTENSIONS=pdf,jpg,jpeg,png,tiff,tif
```
**Restart** the services after editing:
```bash
docker compose restart api worker
```
---
## Docker Setup
When running DocuElevate with Docker Compose, mount the watch folder into both the `api` and `worker` containers:
```yaml
# docker-compose.yaml
services:
api:
volumes:
- /srv/docuelevate/watch:/srv/docuelevate/watch
- /var/docparse/workdir:/workdir
worker:
volumes:
- /srv/docuelevate/watch:/srv/docuelevate/watch
- /var/docparse/workdir:/workdir
```
Create the folder and set permissions:
```bash
sudo mkdir -p /srv/docuelevate/watch
sudo chmod 777 /srv/docuelevate/watch
```
---
## Multiple Watch Folders
If you have multiple scanners or document sources, you can organize them by subfolder:
```
/srv/docuelevate/watch/
├── reception/ ← Front desk scanner
├── accounting/ ← Finance team scanner
├── hr/ ← HR department
└── general/ ← General purpose
```
Enable recursive watching:
```env
WATCH_FOLDER_RECURSIVE=true
```
DocuElevate will monitor all subdirectories and tag documents with the subfolder name for easy filtering.
---
## Using with Network Scanners
### Windows / Samba Share
Share the watch folder over the network so scanners and Windows PCs can drop files directly:
```bash
# Install Samba
sudo apt-get install samba -y
# Add to /etc/samba/smb.conf
[DocuElevate-Inbox]
comment = DocuElevate Document Inbox
path = /srv/docuelevate/watch
browsable = yes
guest ok = yes
read only = no
create mask = 0777
directory mask = 0777
```
Restart Samba:
```bash
sudo systemctl restart smbd nmbd
```
Windows access: `\\your-server-ip\DocuElevate-Inbox`
### NFS Share (Linux)
```bash
# Add to /etc/exports
/srv/docuelevate/watch 192.168.1.0/24(rw,sync,no_subtree_check)
# Apply changes
sudo exportfs -ra
```
### FTP Server (for older devices)
Many older scanners only support FTP. Run a simple FTP server alongside DocuElevate:
```yaml
# Add to docker-compose.yaml
services:
ftp:
image: garethflowers/ftp-server
container_name: docuelevate_ftp
environment:
- FTP_USER=scanner
- FTP_PASS=changeme
ports:
- "21:21"
- "20:20"
- "21100-21110:21100-21110"
volumes:
- /srv/docuelevate/watch:/home/scanner
```
Configure your scanner's FTP settings to use this server. Files will land in the watched folder.
---
## Supported File Types
| Format | Extension | Notes |
|--------|-----------|-------|
| PDF | `.pdf` | OCR applied if not already searchable |
| JPEG | `.jpg`, `.jpeg` | Converted to PDF |
| PNG | `.png` | Converted to PDF |
| TIFF | `.tif`, `.tiff` | Supports multi-page TIFF |
| HEIC | `.heic` | iPhone photos (converted) |
---
## Monitoring
Check that the watched folder is active:
```bash
# View worker logs
docker logs document_worker --follow
# Expected output:
# INFO Watch folder monitor: watching /srv/docuelevate/watch (interval: 30s)
# INFO Watch folder: found new file invoice.pdf
# INFO Watch folder: queued document ID 156 for processing
```
You can also view processing status in the DocuElevate web interface under **Queue Monitor**.
---
## Troubleshooting
**Files are dropped but not picked up?**
- Verify `WATCH_FOLDER_ENABLED=true` in `.env`
- Check that `WATCH_FOLDER_PATH` matches the mounted path in docker-compose
- Look at worker logs: `docker logs document_worker --tail 100`
**Permission denied errors?**
```bash
# Fix permissions
sudo chmod -R 777 /srv/docuelevate/watch
# Or use ACLs for more granular control
sudo setfacl -m u:nobody:rwx /srv/docuelevate/watch
```
**Files processed but not deleted?**
- Set `WATCH_FOLDER_DELETE_AFTER=true`
- If you want to keep originals, set to `false` and manage cleanup separately
**File appears partially uploaded?**
- Large files may arrive before the scanner finishes writing them
- Increase `WATCH_FOLDER_INTERVAL` to give time for files to be fully written
- DocuElevate uses file-lock detection to avoid processing incomplete files
---
## Related Documentation
- [HP Enterprise Printer Setup](./HPPrinterSetup.md)
- [ScanSnap Setup](./SnapScanSetup.md)
- [Email Ingestion](./EmailIngestion.md)
- [Configuration Guide](../ConfigurationGuide.md)
- [Deployment Guide](../DeploymentGuide.md)
+26
View File
@@ -76,6 +76,12 @@
{% if request and request.url.path == '/about' %}aria-current="page"{% endif %}>
About
</a>
<a href="/help/"
class="px-3 py-2 rounded-md text-sm font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-100"
title="Help &amp; How-To Guides"
{% if request and request.url.path.startswith('/help') %}aria-current="page"{% endif %}>
<i class="fas fa-circle-question mr-1 text-gray-400" aria-hidden="true"></i>Help
</a>
{% else %}
{# ── App nav (logged-in or single-user / auth-disabled mode) ────── #}
@@ -171,6 +177,14 @@
<i class="fas fa-circle-dot mr-0.5" aria-hidden="true"></i> Status
</a>
<!-- Help documentation visible to all users -->
<a href="/help/"
class="px-3 py-2 rounded-md text-sm font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-100"
title="Help &amp; How-To Guides"
{% if request and request.url.path.startswith('/help') %}aria-current="page"{% endif %}>
<i class="fas fa-circle-question mr-1 text-gray-400" aria-hidden="true"></i>Help
</a>
{% endif %}{# end multi_user_enabled / is_logged_in check #}
<!-- Dark mode toggle -->
@@ -229,6 +243,11 @@
{% if request and request.url.path == '/about' %}aria-current="page"{% endif %}>
<i class="fas fa-info-circle mr-2 text-gray-400" aria-hidden="true"></i>About
</a>
<a href="/help/"
class="block px-3 py-3 rounded-md text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50"
{% if request and request.url.path.startswith('/help') %}aria-current="page"{% endif %}>
<i class="fas fa-circle-question mr-2 text-gray-400" aria-hidden="true"></i>Help
</a>
{% else %}
{# ── App links (logged-in or single-user) ────────────────────── #}
@@ -298,6 +317,13 @@
<i class="fas fa-circle-dot mr-1" aria-hidden="true"></i> Status
</a>
<!-- Help documentation -->
<a href="/help/"
class="block px-3 py-3 rounded-md text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50"
{% if request and request.url.path.startswith('/help') %}aria-current="page"{% endif %}>
<i class="fas fa-circle-question mr-2 text-gray-400" aria-hidden="true"></i>Help
</a>
{% endif %}{# end multi_user_enabled / is_logged_in check #}
<!-- Dark mode toggle (mobile) -->
+23
View File
@@ -12,11 +12,34 @@ theme:
- navigation.instant
- search.suggest
- content.code.copy
- navigation.sections
- navigation.top
- toc.integrate
palette:
- scheme: default
primary: blue
accent: blue
toggle:
icon: material/brightness-7
name: Switch to dark mode
- scheme: slate
primary: blue
accent: blue
toggle:
icon: material/brightness-4
name: Switch to light mode
nav:
- Getting Started:
- Setup Wizard: SetupWizard
- User Guide: UserGuide
- Browser Extension: BrowserExtension
- How-To Guides:
- Overview: HowToGuides.md
- HP Enterprise Printer: howto/HPPrinterSetup.md
- Fujitsu ScanSnap: howto/SnapScanSetup.md
- Watched Folder: howto/WatchedFolderSetup.md
- Email Ingestion: howto/EmailIngestion.md
- Mobile Scanning: howto/MobileScanning.md
- API: API
- Deployment:
- Overview: DeploymentGuide
+3
View File
@@ -19,6 +19,9 @@ ruff>=0.3.0
mypy>=1.8.0
djlint>=1.36.0 # HTML template linter for accessibility and best practices
# Documentation (builds the embedded /help site via MkDocs Material)
mkdocs-material>=9.5.0 # MkDocs Material theme same package used in docs/requirements.txt
# Type stubs for mypy
types-requests>=2.31.0
types-paramiko>=3.0.0
+92
View File
@@ -0,0 +1,92 @@
"""Tests for app/views/help.py module."""
import pytest
@pytest.mark.integration
class TestHelpViews:
"""Tests for the help documentation view routes."""
def test_help_redirect(self, client):
"""GET /help should redirect permanently to /help/."""
response = client.get("/help", follow_redirects=False)
assert response.status_code == 301
assert response.headers["location"] in ("/help/", "http://testserver/help/")
def test_help_redirect_follows(self, client):
"""Following /help redirect leads to /help/ (docs served or 404 if not built)."""
# In test environments docs_build/ is not present, so /help/ may 404.
# We only verify the initial redirect works; the final landing page depends
# on whether the docs have been built (they are built only in Docker images).
response = client.get("/help", follow_redirects=False)
assert response.status_code == 301
# Redirect target must be /help/
location = response.headers.get("location", "")
assert location.endswith("/help/")
def test_help_route_is_registered(self, client):
"""Verify the /help route exists in the app router."""
# A GET to /help must not return 405 Method Not Allowed
response = client.get("/help", follow_redirects=False)
assert response.status_code != 405
@pytest.mark.unit
class TestHelpViewUnit:
"""Unit tests for the help view module."""
def test_help_redirect_returns_301(self):
"""Verify the redirect is HTTP 301 (permanent)."""
from fastapi import FastAPI
from fastapi.testclient import TestClient
from app.views.help import router
app = FastAPI()
app.include_router(router)
with TestClient(app) as tc:
resp = tc.get("/help", follow_redirects=False)
assert resp.status_code == 301
def test_help_redirect_target(self):
"""Verify the redirect points to /help/."""
from fastapi import FastAPI
from fastapi.testclient import TestClient
from app.views.help import router
app = FastAPI()
app.include_router(router)
with TestClient(app) as tc:
resp = tc.get("/help", follow_redirects=False)
assert resp.headers["location"].rstrip("/").endswith("help") or resp.headers["location"].endswith("/help/")
def test_docs_build_path_constant(self):
"""Verify _DOCS_BUILD_DIR is resolved relative to the app package."""
import pathlib
from app.views.help import _DOCS_BUILD_DIR
assert isinstance(_DOCS_BUILD_DIR, pathlib.Path)
# Should point to <repo_root>/docs_build
assert _DOCS_BUILD_DIR.name == "docs_build"
@pytest.mark.integration
class TestHelpNavigationLink:
"""Tests that the Help link appears in the navigation."""
def test_help_link_in_nav(self, client):
"""The Help navigation link should appear in the base template."""
response = client.get("/about")
assert response.status_code == 200
# The Help link should be present somewhere in the rendered page
assert b"/help/" in response.content
def test_help_link_has_accessible_text(self, client):
"""The Help link should have visible text for accessibility."""
response = client.get("/about")
assert response.status_code == 200
content = response.text
# Should include the word "Help" associated with /help/
assert "Help" in content