# 🚀 Deployment Plan: Desktop & Web Applications

## Current Architecture

```
┌─────────────────────────────────────────────────────────────┐
│  Browser Extension (Chrome/Firefox)                         │
│  - JavaScript (plagis_popup.js, extJSutils.js)             │
│  - UI for search and display                                │
└────────────────┬────────────────────────────────────────────┘
                 │ HTTP Requests
                 ↓
┌─────────────────────────────────────────────────────────────┐
│  FastAPI Backend (Port 8001)                                │
│  - aumentum_api.py                                          │
│  - REST endpoints (/documents/pdf-by-document-number)       │
└────────────────┬────────────────────────────────────────────┘
                 │
                 ↓
┌─────────────────────────────────────────────────────────────┐
│  Business Logic Layer                                        │
│  - aumentum_browser_service.py                              │
│  - Hierarchical node discovery                              │
│  - PDF generation                                           │
└────────────────┬────────────────────────────────────────────┘
                 │
                 ↓
┌─────────────────────────────────────────────────────────────┐
│  Data Layer                                                  │
│  - MSSQL Database (LRS43)                                   │
│  - FreeTDS/ODBC driver                                      │
│  - Contentstore (/mnt/aumentum_contentstore)                │
└─────────────────────────────────────────────────────────────┘
```

---

## Option 1: Web Application (Recommended for Multi-User Access)

### Architecture Overview

```
┌────────────────────────────────────────────────────────┐
│  Frontend (React/Vue/HTML+JS)                          │
│  - Search interface                                    │
│  - Document viewer                                     │
│  - PDF display                                         │
└───────────────┬────────────────────────────────────────┘
                │ HTTPS
                ↓
┌────────────────────────────────────────────────────────┐
│  Nginx/Apache Reverse Proxy                            │
│  - SSL/TLS termination                                 │
│  - Static file serving                                 │
│  - Load balancing (optional)                           │
└───────────────┬────────────────────────────────────────┘
                │
                ↓
┌────────────────────────────────────────────────────────┐
│  FastAPI Backend (existing)                            │
│  - REST API endpoints                                  │
│  - Authentication (add JWT/OAuth)                      │
│  - Session management                                  │
└────────────────────────────────────────────────────────┘
```

### Step-by-Step Implementation

#### Phase 1: Backend Enhancement (1-2 days)

**1.1 Add Authentication**
```python
# Install dependencies
pip install python-jose[cryptography] passlib[bcrypt] python-multipart

# Add to aumentum_api.py
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from jose import JWTError, jwt
from passlib.context import CryptContext
from datetime import datetime, timedelta

# Configuration
SECRET_KEY = "your-secret-key-here"  # Change in production!
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 480  # 8 hours

# Password hashing
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

# User model
class User(BaseModel):
    username: str
    email: Optional[str] = None
    full_name: Optional[str] = None
    disabled: Optional[bool] = None

class UserInDB(User):
    hashed_password: str

# Authentication functions
def verify_password(plain_password, hashed_password):
    return pwd_context.verify(plain_password, hashed_password)

def get_password_hash(password):
    return pwd_context.hash(password)

def authenticate_user(username: str, password: str):
    # TODO: Query your user database
    # For now, use environment variables or config file
    if username == "admin" and password == "your-password":
        return User(username="admin", email="admin@example.com")
    return False

def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
    to_encode = data.copy()
    if expires_delta:
        expire = datetime.utcnow() + expires_delta
    else:
        expire = datetime.utcnow() + timedelta(minutes=15)
    to_encode.update({"exp": expire})
    encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
    return encoded_jwt

async def get_current_user(token: str = Depends(oauth2_scheme)):
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Could not validate credentials",
        headers={"WWW-Authenticate": "Bearer"},
    )
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        username: str = payload.get("sub")
        if username is None:
            raise credentials_exception
    except JWTError:
        raise credentials_exception
    # TODO: Query user from database
    user = User(username=username)
    if user is None:
        raise credentials_exception
    return user

# Login endpoint
@app.post("/token")
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
    user = authenticate_user(form_data.username, form_data.password)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Incorrect username or password",
            headers={"WWW-Authenticate": "Bearer"},
        )
    access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    access_token = create_access_token(
        data={"sub": user.username}, expires_delta=access_token_expires
    )
    return {"access_token": access_token, "token_type": "bearer"}

# Protected endpoint example
@app.get("/documents/pdf-by-document-number")
async def get_pdf_by_document_number(
    document_number: str = Query(...),
    document_type: int = Query(...),
    document_id: int = Query(...),
    current_user: User = Depends(get_current_user)  # ← Add this
):
    # ... existing code ...
```

**1.2 Add CORS for Web Frontend**
```python
# Already in aumentum_api.py, but verify:
app.add_middleware(
    CORSMiddleware,
    allow_origins=[
        "http://localhost:3000",  # React dev server
        "http://localhost:8080",  # Vue dev server
        "https://yourdomain.com",  # Production domain
    ],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)
```

**1.3 Add Logging and Monitoring**
```python
import logging
from logging.handlers import RotatingFileHandler

# Setup logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    handlers=[
        RotatingFileHandler('aumentum_api.log', maxBytes=10485760, backupCount=5),
        logging.StreamHandler()
    ]
)
logger = logging.getLogger(__name__)

# Add to endpoints
@app.get("/documents/pdf-by-document-number")
async def get_pdf_by_document_number(...):
    logger.info(f"User {current_user.username} requested document {document_number}")
    # ... existing code ...
```

**1.4 Add Rate Limiting**
```python
pip install slowapi

from slowapi import Limiter, _rate_limit_exceeded_handler
from slowapi.util import get_remote_address
from slowapi.errors import RateLimitExceeded

limiter = Limiter(key_func=get_remote_address)
app.state.limiter = limiter
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)

@app.get("/documents/pdf-by-document-number")
@limiter.limit("100/minute")  # 100 requests per minute per IP
async def get_pdf_by_document_number(request: Request, ...):
    # ... existing code ...
```

---

#### Phase 2: Frontend Development (3-5 days)

**Option A: React Frontend (Modern, Component-Based)**

**2.1 Project Setup**
```bash
# Create React app
npx create-react-app aumentum-web-frontend
cd aumentum-web-frontend

# Install dependencies
npm install axios react-router-dom @mui/material @emotion/react @emotion/styled
npm install react-pdf pdfjs-dist
npm install jwt-decode

# Project structure
aumentum-web-frontend/
├── public/
├── src/
│   ├── components/
│   │   ├── Login.jsx
│   │   ├── SearchForm.jsx
│   │   ├── DocumentList.jsx
│   │   ├── DocumentViewer.jsx
│   │   └── PDFViewer.jsx
│   ├── services/
│   │   ├── api.js
│   │   └── auth.js
│   ├── utils/
│   │   └── constants.js
│   ├── App.jsx
│   ├── App.css
│   └── index.js
└── package.json
```

**2.2 API Service (`src/services/api.js`)**
```javascript
import axios from 'axios';

const API_BASE_URL = process.env.REACT_APP_API_URL || 'http://localhost:8001';

const api = axios.create({
  baseURL: API_BASE_URL,
  headers: {
    'Content-Type': 'application/json',
  },
});

// Add token to requests
api.interceptors.request.use(
  (config) => {
    const token = localStorage.getItem('token');
    if (token) {
      config.headers.Authorization = `Bearer ${token}`;
    }
    return config;
  },
  (error) => Promise.reject(error)
);

// Handle 401 errors
api.interceptors.response.use(
  (response) => response,
  (error) => {
    if (error.response?.status === 401) {
      localStorage.removeItem('token');
      window.location.href = '/login';
    }
    return Promise.reject(error);
  }
);

export const searchDocuments = async (documentNumber) => {
  const response = await api.get(`/documents/by-document-number`, {
    params: { document_number: documentNumber },
  });
  return response.data;
};

export const getPDF = async (documentNumber, documentType, documentId) => {
  const response = await api.get(`/documents/pdf-by-document-number`, {
    params: {
      document_number: documentNumber,
      document_type: documentType,
      document_id: documentId,
    },
    responseType: 'blob',
  });
  return response.data;
};

export const login = async (username, password) => {
  const formData = new FormData();
  formData.append('username', username);
  formData.append('password', password);
  
  const response = await api.post('/token', formData, {
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
    },
  });
  return response.data;
};

export default api;
```

**2.3 Search Component (`src/components/SearchForm.jsx`)**
```javascript
import React, { useState } from 'react';
import { TextField, Button, Box, CircularProgress } from '@mui/material';
import { searchDocuments } from '../services/api';

function SearchForm({ onResults }) {
  const [documentNumber, setDocumentNumber] = useState('');
  const [loading, setLoading] = useState(false);

  const handleSubmit = async (e) => {
    e.preventDefault();
    setLoading(true);
    
    try {
      const results = await searchDocuments(documentNumber.trim());
      onResults(results);
    } catch (error) {
      console.error('Search failed:', error);
      alert('Search failed. Please try again.');
    } finally {
      setLoading(false);
    }
  };

  return (
    <Box component="form" onSubmit={handleSubmit} sx={{ mb: 3 }}>
      <TextField
        fullWidth
        label="Document Number"
        value={documentNumber}
        onChange={(e) => setDocumentNumber(e.target.value)}
        placeholder="e.g., PL11089"
        disabled={loading}
        sx={{ mb: 2 }}
      />
      <Button
        type="submit"
        variant="contained"
        disabled={loading || !documentNumber.trim()}
      >
        {loading ? <CircularProgress size={24} /> : 'Search'}
      </Button>
    </Box>
  );
}

export default SearchForm;
```

**2.4 Document Viewer (`src/components/DocumentViewer.jsx`)**
```javascript
import React, { useState, useEffect } from 'react';
import { Document, Page, pdfjs } from 'react-pdf';
import { getPDF } from '../services/api';
import { Box, CircularProgress, IconButton, Typography } from '@mui/material';
import { ChevronLeft, ChevronRight } from '@mui/icons-material';

// Set PDF.js worker
pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.min.js`;

function DocumentViewer({ documentNumber, documentType, documentId, pageCount }) {
  const [pdfData, setPdfData] = useState(null);
  const [numPages, setNumPages] = useState(null);
  const [currentPage, setCurrentPage] = useState(1);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const loadPDF = async () => {
      setLoading(true);
      try {
        const blob = await getPDF(documentNumber, documentType, documentId);
        const url = URL.createObjectURL(blob);
        setPdfData(url);
      } catch (error) {
        console.error('Failed to load PDF:', error);
        alert('Failed to load document');
      } finally {
        setLoading(false);
      }
    };

    loadPDF();

    return () => {
      if (pdfData) {
        URL.revokeObjectURL(pdfData);
      }
    };
  }, [documentNumber, documentType, documentId]);

  const onDocumentLoadSuccess = ({ numPages }) => {
    setNumPages(numPages);
  };

  if (loading) {
    return (
      <Box display="flex" justifyContent="center" alignItems="center" minHeight="400px">
        <CircularProgress />
      </Box>
    );
  }

  return (
    <Box>
      <Box display="flex" justifyContent="space-between" alignItems="center" mb={2}>
        <IconButton
          onClick={() => setCurrentPage(Math.max(1, currentPage - 1))}
          disabled={currentPage <= 1}
        >
          <ChevronLeft />
        </IconButton>
        
        <Typography>
          Page {currentPage} of {numPages || pageCount}
        </Typography>
        
        <IconButton
          onClick={() => setCurrentPage(Math.min(numPages || pageCount, currentPage + 1))}
          disabled={currentPage >= (numPages || pageCount)}
        >
          <ChevronRight />
        </IconButton>
      </Box>

      <Document
        file={pdfData}
        onLoadSuccess={onDocumentLoadSuccess}
        loading={<CircularProgress />}
      >
        <Page pageNumber={currentPage} width={800} />
      </Document>
    </Box>
  );
}

export default DocumentViewer;
```

**Option B: Simple HTML/JavaScript (Lightweight)**

**2.5 Single-Page Application (`public/index.html`)**
```html
<!DOCTYPE html>
<html>
<head>
    <title>Aumentum Document Viewer</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body { font-family: Arial, sans-serif; padding: 20px; }
        .container { max-width: 1200px; margin: 0 auto; }
        .search-form { margin-bottom: 20px; }
        input[type="text"] { padding: 10px; width: 300px; }
        button { padding: 10px 20px; background: #4CAF50; color: white; border: none; cursor: pointer; }
        button:hover { background: #45a049; }
        .results { margin-top: 20px; }
        .document-item { padding: 10px; border: 1px solid #ddd; margin: 5px 0; cursor: pointer; }
        .document-item:hover { background: #f0f0f0; }
        #pdf-viewer { margin-top: 20px; }
        iframe { width: 100%; height: 800px; border: 1px solid #ddd; }
    </style>
</head>
<body>
    <div class="container">
        <h1>Aumentum Document Viewer</h1>
        
        <div class="search-form">
            <input type="text" id="documentNumber" placeholder="Enter document number (e.g., PL11089)">
            <button onclick="searchDocuments()">Search</button>
        </div>
        
        <div id="results" class="results"></div>
        <div id="pdf-viewer"></div>
    </div>

    <script>
        const API_BASE = 'http://localhost:8001';
        let currentToken = localStorage.getItem('token');

        async function searchDocuments() {
            const docNumber = document.getElementById('documentNumber').value.trim();
            if (!docNumber) return;

            try {
                const response = await fetch(
                    `${API_BASE}/documents/by-document-number?document_number=${docNumber}`,
                    {
                        headers: {
                            'Authorization': `Bearer ${currentToken}`
                        }
                    }
                );
                
                if (!response.ok) throw new Error('Search failed');
                
                const data = await response.json();
                displayResults(data);
            } catch (error) {
                alert('Search failed: ' + error.message);
            }
        }

        function displayResults(data) {
            const resultsDiv = document.getElementById('results');
            resultsDiv.innerHTML = '<h2>Search Results</h2>';
            
            data.items.forEach(doc => {
                const div = document.createElement('div');
                div.className = 'document-item';
                div.innerHTML = `
                    <strong>Type ${doc.document_type}</strong>: 
                    ${doc.page_count} pages 
                    (${doc.available_images} images available)
                `;
                div.onclick = () => viewDocument(data.document_number, doc.document_type, doc.id, doc.page_count);
                resultsDiv.appendChild(div);
            });
        }

        function viewDocument(docNumber, docType, docId, pageCount) {
            const url = `${API_BASE}/documents/pdf-by-document-number?document_number=${docNumber}&document_type=${docType}&document_id=${docId}`;
            
            const viewerDiv = document.getElementById('pdf-viewer');
            viewerDiv.innerHTML = `
                <h3>Document: ${docNumber} (Type ${docType}) - ${pageCount} pages</h3>
                <iframe src="${url}#toolbar=1"></iframe>
            `;
        }
    </script>
</body>
</html>
```

---

#### Phase 3: Deployment (2-3 days)

**3.1 Backend Deployment**

**Option A: Systemd Service (Linux)**
```bash
# Create service file
sudo nano /etc/systemd/system/aumentum-api.service
```

```ini
[Unit]
Description=Aumentum Document API
After=network.target

[Service]
Type=simple
User=plagis
WorkingDirectory=/home/plagis/workspace/plagis_aumentum
Environment="PATH=/home/plagis/workspace/plagis_aumentum/venv/bin"
ExecStart=/home/plagis/workspace/plagis_aumentum/venv/bin/python3 -m uvicorn aumentum_api:app --host 0.0.0.0 --port 8001 --workers 4
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target
```

```bash
# Enable and start service
sudo systemctl daemon-reload
sudo systemctl enable aumentum-api
sudo systemctl start aumentum-api
sudo systemctl status aumentum-api
```

**Option B: Docker Deployment**
```dockerfile
# Dockerfile
FROM python:3.10-slim

WORKDIR /app

# Install system dependencies
RUN apt-get update && apt-get install -y \
    unixodbc \
    unixodbc-dev \
    freetds-dev \
    freetds-bin \
    tdsodbc \
    && rm -rf /var/lib/apt/lists/*

# Copy requirements
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copy application
COPY . .

# Expose port
EXPOSE 8001

# Run application
CMD ["uvicorn", "aumentum_api:app", "--host", "0.0.0.0", "--port", "8001", "--workers", "4"]
```

```yaml
# docker-compose.yml
version: '3.8'

services:
  api:
    build: .
    ports:
      - "8001:8001"
    environment:
      - CONTENTSTORE_BASE=/mnt/contentstore
    volumes:
      - /mnt/aumentum_contentstore:/mnt/contentstore:ro
      - ./aumentum_pdfs:/tmp/aumentum_pdfs
    restart: unless-stopped
    
  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./frontend/build:/usr/share/nginx/html:ro
      - ./ssl:/etc/nginx/ssl:ro
    depends_on:
      - api
    restart: unless-stopped
```

**3.2 Nginx Configuration**
```nginx
# nginx.conf
upstream api_backend {
    server api:8001;
}

server {
    listen 80;
    server_name yourdomain.com;
    
    # Redirect to HTTPS
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name yourdomain.com;
    
    ssl_certificate /etc/nginx/ssl/cert.pem;
    ssl_certificate_key /etc/nginx/ssl/key.pem;
    
    # Frontend
    location / {
        root /usr/share/nginx/html;
        try_files $uri $uri/ /index.html;
    }
    
    # API
    location /api/ {
        rewrite ^/api/(.*) /$1 break;
        proxy_pass http://api_backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        
        # Increase timeouts for PDF generation
        proxy_connect_timeout 300s;
        proxy_send_timeout 300s;
        proxy_read_timeout 300s;
    }
    
    # WebSocket support (if needed)
    location /ws/ {
        proxy_pass http://api_backend;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}
```

---

## Option 2: Desktop Application

### Architecture Overview

```
┌────────────────────────────────────────────────────────┐
│  Electron/PyQt Desktop UI                              │
│  - Native window                                       │
│  - Embedded browser (Chromium)                         │
│  - PDF viewer                                          │
└───────────────┬────────────────────────────────────────┘
                │ IPC / HTTP
                ↓
┌────────────────────────────────────────────────────────┐
│  Embedded FastAPI Server                               │
│  - Runs in background thread                           │
│  - localhost:8001                                      │
│  - No network exposure                                 │
└────────────────────────────────────────────────────────┘
```

### Step-by-Step Implementation

#### Option A: Electron + Python Backend (Cross-Platform)

**1.1 Project Setup**
```bash
# Create Electron project
mkdir aumentum-desktop
cd aumentum-desktop
npm init -y

# Install Electron dependencies
npm install electron electron-builder
npm install axios electron-store

# Project structure
aumentum-desktop/
├── backend/
│   ├── aumentum_api.py
│   ├── aumentum_browser_service.py
│   ├── requirements.txt
│   └── venv/
├── frontend/
│   ├── index.html
│   ├── renderer.js
│   ├── styles.css
│   └── preload.js
├── main.js
├── package.json
└── build/
```

**1.2 Main Process (`main.js`)**
```javascript
const { app, BrowserWindow, ipcMain } = require('electron');
const { spawn } = require('child_process');
const path = require('path');
const axios = require('axios');

let mainWindow;
let pythonProcess;
const API_PORT = 8001;

// Start Python backend
function startPythonBackend() {
    const pythonPath = path.join(__dirname, 'backend', 'venv', 'bin', 'python3');
    const scriptPath = path.join(__dirname, 'backend', 'aumentum_api.py');
    
    pythonProcess = spawn(pythonPath, [
        '-m', 'uvicorn',
        'aumentum_api:app',
        '--host', '127.0.0.1',
        '--port', String(API_PORT)
    ], {
        cwd: path.join(__dirname, 'backend')
    });
    
    pythonProcess.stdout.on('data', (data) => {
        console.log(`Python: ${data}`);
    });
    
    pythonProcess.stderr.on('data', (data) => {
        console.error(`Python Error: ${data}`);
    });
    
    // Wait for server to start
    return waitForServer();
}

async function waitForServer(retries = 30) {
    for (let i = 0; i < retries; i++) {
        try {
            await axios.get(`http://127.0.0.1:${API_PORT}/`);
            console.log('Backend server ready');
            return true;
        } catch (error) {
            await new Promise(resolve => setTimeout(resolve, 1000));
        }
    }
    throw new Error('Backend server failed to start');
}

function createWindow() {
    mainWindow = new BrowserWindow({
        width: 1200,
        height: 800,
        webPreferences: {
            nodeIntegration: false,
            contextIsolation: true,
            preload: path.join(__dirname, 'frontend', 'preload.js')
        },
        icon: path.join(__dirname, 'assets', 'icon.png')
    });
    
    mainWindow.loadFile(path.join(__dirname, 'frontend', 'index.html'));
    
    // Open DevTools in development
    if (process.env.NODE_ENV === 'development') {
        mainWindow.webContents.openDevTools();
    }
}

// App lifecycle
app.whenReady().then(async () => {
    try {
        await startPythonBackend();
        createWindow();
    } catch (error) {
        console.error('Failed to start application:', error);
        app.quit();
    }
});

app.on('window-all-closed', () => {
    if (process.platform !== 'darwin') {
        app.quit();
    }
});

app.on('before-quit', () => {
    if (pythonProcess) {
        pythonProcess.kill();
    }
});

app.on('activate', () => {
    if (BrowserWindow.getAllWindows().length === 0) {
        createWindow();
    }
});

// IPC handlers
ipcMain.handle('search-documents', async (event, documentNumber) => {
    try {
        const response = await axios.get(
            `http://127.0.0.1:${API_PORT}/documents/by-document-number`,
            { params: { document_number: documentNumber } }
        );
        return response.data;
    } catch (error) {
        throw new Error(error.message);
    }
});

ipcMain.handle('get-pdf', async (event, { documentNumber, documentType, documentId }) => {
    try {
        const response = await axios.get(
            `http://127.0.0.1:${API_PORT}/documents/pdf-by-document-number`,
            {
                params: {
                    document_number: documentNumber,
                    document_type: documentType,
                    document_id: documentId
                },
                responseType: 'arraybuffer'
            }
        );
        return Buffer.from(response.data).toString('base64');
    } catch (error) {
        throw new Error(error.message);
    }
});
```

**1.3 Preload Script (`frontend/preload.js`)**
```javascript
const { contextBridge, ipcRenderer } = require('electron');

contextBridge.exposeInMainWorld('electronAPI', {
    searchDocuments: (documentNumber) => 
        ipcRenderer.invoke('search-documents', documentNumber),
    
    getPDF: (params) => 
        ipcRenderer.invoke('get-pdf', params)
});
```

**1.4 Frontend HTML (`frontend/index.html`)**
```html
<!DOCTYPE html>
<html>
<head>
    <title>Aumentum Document Viewer</title>
    <link rel="stylesheet" href="styles.css">
</head>
<body>
    <div class="app-container">
        <header>
            <h1>🗂️ Aumentum Document Viewer</h1>
        </header>
        
        <main>
            <div class="search-panel">
                <input type="text" id="documentNumber" placeholder="Document Number (e.g., PL11089)">
                <button id="searchBtn">Search</button>
            </div>
            
            <div id="results" class="results-panel"></div>
            <div id="viewer" class="viewer-panel"></div>
        </main>
    </div>
    
    <script src="renderer.js"></script>
</body>
</html>
```

**1.5 Renderer Process (`frontend/renderer.js`)**
```javascript
const searchBtn = document.getElementById('searchBtn');
const documentInput = document.getElementById('documentNumber');
const resultsDiv = document.getElementById('results');
const viewerDiv = document.getElementById('viewer');

searchBtn.addEventListener('click', async () => {
    const docNumber = documentInput.value.trim();
    if (!docNumber) return;
    
    searchBtn.disabled = true;
    searchBtn.textContent = 'Searching...';
    
    try {
        const results = await window.electronAPI.searchDocuments(docNumber);
        displayResults(results);
    } catch (error) {
        alert('Search failed: ' + error.message);
    } finally {
        searchBtn.disabled = false;
        searchBtn.textContent = 'Search';
    }
});

function displayResults(data) {
    resultsDiv.innerHTML = '<h2>Search Results</h2>';
    
    data.items.forEach(doc => {
        const div = document.createElement('div');
        div.className = 'result-item';
        div.innerHTML = `
            <h3>Type ${doc.document_type}: ${doc.document_type_label}</h3>
            <p>Pages: ${doc.page_count} | Available Images: ${doc.available_images}</p>
        `;
        div.addEventListener('click', () => viewDocument(data.document_number, doc));
        resultsDiv.appendChild(div);
    });
}

async function viewDocument(docNumber, doc) {
    viewerDiv.innerHTML = '<p>Loading PDF...</p>';
    
    try {
        const pdfBase64 = await window.electronAPI.getPDF({
            documentNumber: docNumber,
            documentType: doc.document_type,
            documentId: doc.id
        });
        
        // Display PDF
        const pdfData = `data:application/pdf;base64,${pdfBase64}`;
        viewerDiv.innerHTML = `
            <h3>${docNumber} - Type ${doc.document_type}</h3>
            <embed src="${pdfData}" type="application/pdf" width="100%" height="800px" />
        `;
    } catch (error) {
        viewerDiv.innerHTML = '<p>Failed to load PDF</p>';
        alert('Failed to load document: ' + error.message);
    }
}
```

**1.6 Build Configuration (`package.json`)**
```json
{
  "name": "aumentum-document-viewer",
  "version": "1.0.0",
  "description": "Aumentum Document Viewer Desktop Application",
  "main": "main.js",
  "scripts": {
    "start": "electron .",
    "build": "electron-builder",
    "build:win": "electron-builder --win",
    "build:mac": "electron-builder --mac",
    "build:linux": "electron-builder --linux"
  },
  "build": {
    "appId": "com.aumentum.documentviewer",
    "productName": "Aumentum Document Viewer",
    "directories": {
      "output": "dist"
    },
    "files": [
      "**/*",
      "!backend/venv/**/*",
      "backend/venv/lib/python*/site-packages/**/*"
    ],
    "extraResources": [
      {
        "from": "backend",
        "to": "backend",
        "filter": ["**/*"]
      }
    ],
    "win": {
      "target": "nsis",
      "icon": "assets/icon.ico"
    },
    "mac": {
      "target": "dmg",
      "icon": "assets/icon.icns"
    },
    "linux": {
      "target": ["AppImage", "deb"],
      "icon": "assets/icon.png",
      "category": "Office"
    }
  },
  "dependencies": {
    "axios": "^1.6.0",
    "electron-store": "^8.1.0"
  },
  "devDependencies": {
    "electron": "^27.0.0",
    "electron-builder": "^24.6.0"
  }
}
```

**1.7 Build and Package**
```bash
# Install Python dependencies in venv
cd backend
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt

# Build desktop app
cd ..
npm install
npm run build:linux  # or build:win / build:mac
```

---

#### Option B: PyQt6 Desktop Application (Python-Native)

**2.1 Project Setup**
```bash
# Install PyQt6
pip install PyQt6 PyQt6-WebEngine

# Project structure
aumentum-desktop-pyqt/
├── main.py
├── ui/
│   ├── main_window.py
│   ├── search_widget.py
│   └── document_viewer.py
├── services/
│   ├── aumentum_browser_service.py
│   └── database.py
├── assets/
│   └── icon.png
└── requirements.txt
```

**2.2 Main Application (`main.py`)**
```python
import sys
from PyQt6.QtWidgets import QApplication
from PyQt6.QtGui import QIcon
from ui.main_window import MainWindow

def main():
    app = QApplication(sys.argv)
    app.setApplicationName("Aumentum Document Viewer")
    app.setOrganizationName("Aumentum")
    
    # Set application icon
    app.setWindowIcon(QIcon('assets/icon.png'))
    
    # Create and show main window
    window = MainWindow()
    window.show()
    
    sys.exit(app.exec())

if __name__ == '__main__':
    main()
```

**2.3 Main Window (`ui/main_window.py`)**
```python
from PyQt6.QtWidgets import (
    QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
    QLineEdit, QPushButton, QListWidget, QListWidgetItem,
    QSplitter, QLabel, QStatusBar, QMessageBox
)
from PyQt6.QtCore import Qt, QThread, pyqtSignal
from PyQt6.QtWebEngineWidgets import QWebEngineView
from services.aumentum_browser_service import AumentumBrowserService, DEFAULT_DB_CONFIG, DEFAULT_CONTENTSTORE_BASE
import tempfile
import os

class SearchThread(QThread):
    """Background thread for database queries"""
    finished = pyqtSignal(list)
    error = pyqtSignal(str)
    
    def __init__(self, service, document_number):
        super().__init__()
        self.service = service
        self.document_number = document_number
    
    def run(self):
        try:
            results = self.service.resolve_store_urls_by_document_number(self.document_number)
            self.finished.emit(results)
        except Exception as e:
            self.error.emit(str(e))

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.service = AumentumBrowserService(DEFAULT_DB_CONFIG, DEFAULT_CONTENTSTORE_BASE)
        self.current_pdf = None
        self.initUI()
    
    def initUI(self):
        self.setWindowTitle('Aumentum Document Viewer')
        self.setGeometry(100, 100, 1200, 800)
        
        # Central widget
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        
        # Main layout
        layout = QVBoxLayout(central_widget)
        
        # Search bar
        search_layout = QHBoxLayout()
        self.search_input = QLineEdit()
        self.search_input.setPlaceholderText('Enter document number (e.g., PL11089)')
        self.search_input.returnPressed.connect(self.search_documents)
        
        self.search_button = QPushButton('Search')
        self.search_button.clicked.connect(self.search_documents)
        
        search_layout.addWidget(QLabel('Document Number:'))
        search_layout.addWidget(self.search_input)
        search_layout.addWidget(self.search_button)
        
        layout.addLayout(search_layout)
        
        # Splitter for results and viewer
        splitter = QSplitter(Qt.Orientation.Horizontal)
        
        # Results list
        self.results_list = QListWidget()
        self.results_list.itemClicked.connect(self.view_document)
        splitter.addWidget(self.results_list)
        
        # PDF viewer
        self.pdf_viewer = QWebEngineView()
        splitter.addWidget(self.pdf_viewer)
        
        splitter.setStretchFactor(0, 1)
        splitter.setStretchFactor(1, 3)
        
        layout.addWidget(splitter)
        
        # Status bar
        self.statusBar().showMessage('Ready')
    
    def search_documents(self):
        document_number = self.search_input.text().strip()
        if not document_number:
            QMessageBox.warning(self, 'Error', 'Please enter a document number')
            return
        
        self.search_button.setEnabled(False)
        self.statusBar().showMessage(f'Searching for {document_number}...')
        
        # Run search in background thread
        self.search_thread = SearchThread(self.service, document_number)
        self.search_thread.finished.connect(self.display_results)
        self.search_thread.error.connect(self.search_error)
        self.search_thread.start()
    
    def display_results(self, results):
        self.results_list.clear()
        
        for result in results:
            doc_type = result['document_type']
            page_count = result['page_count']
            image_count = len(result['images'])
            
            item = QListWidgetItem(
                f"Type {doc_type}: {page_count} pages ({image_count} images)"
            )
            item.setData(Qt.ItemDataRole.UserRole, result)
            self.results_list.addItem(item)
        
        self.search_button.setEnabled(True)
        self.statusBar().showMessage(f'Found {len(results)} document type(s)')
    
    def search_error(self, error_msg):
        QMessageBox.critical(self, 'Search Error', f'Search failed: {error_msg}')
        self.search_button.setEnabled(True)
        self.statusBar().showMessage('Search failed')
    
    def view_document(self, item):
        result = item.data(Qt.ItemDataRole.UserRole)
        document_number = self.search_input.text().strip()
        
        self.statusBar().showMessage('Generating PDF...')
        
        try:
            # Generate PDF
            pdf_path = self.service.get_pdf_by_document_number(
                document_number,
                result['document_type'],
                result['document_id']
            )
            
            # Display PDF
            self.pdf_viewer.setUrl(f'file://{pdf_path}')
            self.current_pdf = pdf_path
            
            self.statusBar().showMessage(
                f"Viewing {document_number} Type {result['document_type']}"
            )
        except Exception as e:
            QMessageBox.critical(self, 'Error', f'Failed to load PDF: {str(e)}')
            self.statusBar().showMessage('Failed to load document')
```

**2.4 Build Executable**
```bash
# Install PyInstaller
pip install pyinstaller

# Create executable
pyinstaller --onefile --windowed \
    --name "AumentumDocumentViewer" \
    --icon assets/icon.ico \
    --add-data "assets:assets" \
    main.py

# Output will be in dist/ folder
```

---

## Comparison: Web vs Desktop

| Feature | Web Application | Desktop Application |
|---------|-----------------|---------------------|
| **Deployment** | Centralized server | Individual installations |
| **Updates** | Instant (refresh browser) | Need to reinstall |
| **Access** | Any device with browser | Specific computer |
| **Performance** | Network-dependent | Local, faster |
| **Security** | HTTPS, firewall | Local-only, more secure |
| **Maintenance** | Easier (one server) | Harder (multiple installs) |
| **Cost** | Server hosting costs | No hosting needed |
| **Offline** | ❌ Requires internet | ✅ Works offline |
| **Multi-user** | ✅ Built-in | ❌ Needs separate licensing |

---

## Recommended Approach

### For Internal Office Use (< 50 users):
**→ Web Application (React + FastAPI + Nginx)**
- Easier maintenance
- Automatic updates
- No installation required
- Better for multiple users

### For Field Workers / Offline Use:
**→ Desktop Application (Electron + Python)**
- Works without internet
- Faster performance
- More secure (no network exposure)
- Portable

### For Both:
**→ Progressive Web App (PWA)**
- Install as desktop app
- Works offline (with Service Workers)
- Best of both worlds

---

## Timeline Estimates

| Phase | Web App | Desktop App |
|-------|---------|-------------|
| Backend Enhancement | 1-2 days | N/A |
| Frontend Development | 3-5 days | 4-6 days |
| Testing | 2-3 days | 2-3 days |
| Deployment Setup | 2-3 days | 1-2 days |
| **Total** | **8-13 days** | **7-11 days** |

---

## Next Steps

1. **Choose deployment type** (web/desktop/both)
2. **Setup development environment**
3. **Implement authentication** (if web)
4. **Build frontend UI**
5. **Test with real users**
6. **Deploy to production**

Would you like me to help with any specific implementation step?

