DocuScan is a full-stack application that extracts structured data from legal PDF documents. It combines a locally-hosted Azure Document Intelligence container for OCR with a local LLM (Phi-3.5-mini via AI Foundry Local) for intelligent entity extraction, and presents results through a modern React frontend.
All document processing happens on-premises — no raw document content is sent to external cloud services at runtime.
- Features
- Architecture Overview
- Prerequisites
- Component Setup
- Environment Variables
- Running the Full Stack
- API Reference
- Project Structure
- PDF Upload — drag-and-drop or file-picker upload of German legal/notarial PDFs.
- OCR via Azure Document Intelligence — runs in a local Docker container using the
prebuilt-layoutmodel to extract text, key-value pairs, and tables in Markdown format. - Intelligent Entity Extraction — a Phi-3.5-mini LLM agent (served locally by AI Foundry Local) extracts structured entities including:
- Names and addresses of contracting parties
- Notary name and address
- Purchase date (Kaufdatum)
- Notarial deed number (UVZ-Nr)
- Property area (Fläche in m²)
- Postal receipt date (Posteingangsdatum)
- Structured JSON Output — extracted data is returned as a clean JSON object, displayed in the UI with a one-click copy button.
- Real-time Status — Socket.IO WebSocket connection provides live processing status updates.
- Azure Key Vault Integration — secrets (API keys, endpoints) are retrieved securely at runtime using
DefaultAzureCredential.
┌─────────────────────────────────────────────────────────────────┐
│ User's Browser │
│ React + Vite Frontend (port 5173) │
│ • PDF drag-and-drop upload │
│ • Real-time status via Socket.IO │
│ • JSON result display │
└──────────────────────────┬──────────────────────────────────────┘
│ HTTP POST /api/process-pdf
▼
┌─────────────────────────────────────────────────────────────────┐
│ Flask Backend (port 8080) │
│ ApiServer.py │
│ • Receives PDF via multipart/form-data │
│ • Calls Azure Document Intelligence container for OCR │
│ • Passes OCR text to AI Foundry Local agent │
│ • Returns extracted JSON │
└──────┬──────────────────────────────────────┬───────────────────┘
│ │
▼ ▼
┌─────────────────────┐ ┌─────────────────────────────────┐
│ Azure Document │ │ AI Foundry Local │
│ Intelligence │ │ Phi-3.5-mini LLM │
│ Container │ │ (started automatically by SDK) │
│ (Docker, port 5010) │ │ • Named entity extraction │
│ prebuilt-layout model│ │ • Returns structured JSON │
└─────────────────────┘ └─────────────────────────────────┘
| Requirement | Version / Notes |
|---|---|
| Python | 3.10+ |
| Node.js | 18+ (LTS recommended) |
| Docker Desktop | Required for Azure Cognitive Services containers |
| Azure Subscription | Required to obtain billing endpoint & API key for the Document Intelligence container |
| AI Foundry Local | Installed on the host machine — download here |
| Azure CLI | Required for DefaultAzureCredential / Key Vault access |
The backend talks to a locally running Azure Document Intelligence container. This container requires a valid Azure billing endpoint and API key.
The DocuScan/docker-compose.yml file defines both the Document Intelligence and the NER containers.
Before starting, create a .env file (or set the variables directly) with your Azure credentials — do not hard-code keys in docker-compose.yml:
# docker-compose.yml (excerpt — use environment variables for secrets)
services:
forms:
image: "mcr.microsoft.com/azure-cognitive-services/form-recognizer/layout-4.0"
environment:
eula: accept
billing: ${DOC_INTELLIGENCE_ENDPOINT}
apikey: ${DOC_INTELLIGENCE_KEY}
FormRecognizer__ComputerVisionApiKey: ${DOC_INTELLIGENCE_KEY}
FormRecognizer__ComputerVisionEndpointUri: ${DOC_INTELLIGENCE_ENDPOINT}
volumes:
- type: bind
source: C:\Azure_AI\output
target: /output
- type: bind
source: C:\Azure_AI\input
target: /input
ports:
- "5010:5000"Start the container:
cd DocuScan
docker-compose up -d formsThe container will be available at http://localhost:5010. The backend connects to this endpoint automatically.
A Named Entity Recognition container is also defined in docker-compose.yml for additional text analysis:
docker-compose up -d nerThis container runs on port 5021.
DocuScan uses AI Foundry Local to serve the phi-3.5-mini model on the host machine. The Python SDK (foundry_local and agent_framework_foundry_local) starts the service and downloads the model automatically on first run.
Download and install the Foundry Local runtime from:
https://aka.ms/foundry-local
The backend will automatically:
- Start the Foundry Local service (if not already running).
- Download
phi-3.5-minito the local device on first use. - Create a
Data Extraction Agentextraction instructions. - Stream the response and parse the returned JSON.
No additional configuration is required for the LLM component beyond having Foundry Local installed.
# From the workspace root
python -m venv .venv
.venv\Scripts\activate # Windows
# source .venv/bin/activate # macOS / Linux
pip install -r requirements.txt| Package | Purpose |
|---|---|
flask + flask-cors |
HTTP API server |
flask-socketio |
WebSocket support for real-time status |
azure-ai-documentintelligence |
Azure Document Intelligence SDK |
azure-ai-textanalytics |
Azure Text Analytics SDK |
semantic-kernel |
Agent orchestration framework |
foundry_local |
AI Foundry Local model manager |
agent_framework_foundry_local |
Foundry Local agent client |
azure-keyvault-secrets |
Azure Key Vault secret retrieval |
azure-identity |
DefaultAzureCredential for Key Vault |
# Windows PowerShell
$env:KEY_VAULT_NAME = "your-keyvault-name"
$env:FLASK_APP = "DocuScan\backend\ApiServer.py"Or create a .env file in the project root (and use python-dotenv to load it):
KEY_VAULT_NAME=your-keyvault-name
The backend retrieves the Document Intelligence API key from Key Vault under the secret name DocIntellKey. Ensure this secret exists in your vault before running.
cd DocuScan/backend
python ApiServer.pyThe server starts on port 8080 and also listens for Socket.IO connections.
Alternatively, run with Flask CLI:
flask run --host 0.0.0.0 --port 8080# From workspace root
docker build -t docuscan:latest .
docker run -p 8080:8080 -e KEY_VAULT_NAME=your-keyvault-name docuscan:latestcd DocuScan/frontend
npm installCreate a .env file in DocuScan/frontend/:
VITE_BACKEND_URL=http://localhost:8080/api/process-pdf
npm run devThe frontend is served at http://localhost:5173.
npm run buildStatic output is written to DocuScan/frontend/dist/.
| Variable | Component | Description |
|---|---|---|
KEY_VAULT_NAME |
Backend | Name of the Azure Key Vault containing secrets |
VITE_BACKEND_URL |
Frontend | Full URL of the backend process-pdf endpoint |
DOC_INTELLIGENCE_ENDPOINT |
Docker Compose | Azure Document Intelligence billing endpoint |
DOC_INTELLIGENCE_KEY |
Docker Compose | Azure Document Intelligence API key |
Security note: Never commit API keys or secrets to source control. Use environment variables,
.envfiles (added to.gitignore), or Azure Key Vault.
Start all components in this order:
# 1. Start the Azure Document Intelligence container
cd DocuScan
docker-compose up -d forms
# 2. Start the Flask backend (in a separate terminal)
cd ..
.venv\Scripts\activate
python DocuScan/backend/ApiServer.py
# 3. Start the React frontend (in a separate terminal)
cd DocuScan/frontend
npm run devThen open http://localhost:5173 in your browser.
Accepts a PDF file, runs OCR via the local Document Intelligence container, and extracts structured entities using the local LLM agent.
Request
| Field | Type | Description |
|---|---|---|
file |
multipart/form-data |
The PDF document to process |
Response — 200 OK
{
}| Event | Direction | Description |
|---|---|---|
data |
server → client | Broadcasts processing status messages |
connect |
server → client | Emitted when a client connects |
Docuscan/
├── Dockerfile # Backend container image
├── requirements.txt # Python dependencies
├── README.md # This file
└── DocuScan/
├── docker-compose.yml # Azure Cognitive Services containers
├── backend/
│ ├── ApiServer.py # Flask app — main entry point
│ ├── AzServiceApiLogic.py # Standalone OCR helper class
│ ├── foundryLocal.py # Foundry Local LLM test script
│ ├── contracts/
│ │ └── vollmacht.json # Sample contract JSON schema
│ └── prompts/
│ └── system_contract_extractor.md # LLM system prompt
└── frontend/
├── package.json
├── vite.config.ts
└── src/
├── App.tsx
├── components/
│ ├── ContentArea/ # Main layout wrapper
│ │ └── components/
│ │ ├── UploadArea/ # PDF drag-and-drop upload
│ │ ├── ResultArea/ # JSON result display
│ │ └── WorkPanel/ # Orchestrates upload + result
│ ├── Header/ # App header
│ ├── ErrorAlert/ # Error message component
│ └── StatusAlert/ # Processing status component
└── utils/
├── getFileSizeInMB.ts
└── isPdf.ts