Upload 7 files
Browse files- .env.example +10 -0
- .gitattributes +1 -0
- Dockerfile +27 -0
- README.md +118 -1
- app.py +371 -0
- model.md +106 -0
- requirements.txt +8 -0
- testimage.jpeg +3 -0
.env.example
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Cloudinary
|
| 2 |
+
CLOUDINARY_CLOUD_NAME=your_cloud_name
|
| 3 |
+
CLOUDINARY_API_KEY=your_api_key
|
| 4 |
+
CLOUDINARY_API_SECRET=your_api_secret
|
| 5 |
+
|
| 6 |
+
# NVIDIA API
|
| 7 |
+
NVIDIA_API_KEY=your_nvidia_api_key
|
| 8 |
+
|
| 9 |
+
# MongoDB
|
| 10 |
+
MONGODB_URI=mongodb+srv://username:password@cluster0.sxci1.mongodb.net/?retryWrites=true&w=majority
|
.gitattributes
CHANGED
|
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
testimage.jpeg filter=lfs diff=lfs merge=lfs -text
|
Dockerfile
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.10-slim
|
| 2 |
+
|
| 3 |
+
# Install system dependencies including Tesseract OCR
|
| 4 |
+
RUN apt-get update && apt-get install -y \
|
| 5 |
+
tesseract-ocr \
|
| 6 |
+
libtesseract-dev \
|
| 7 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 8 |
+
|
| 9 |
+
# Set working directory
|
| 10 |
+
WORKDIR /app
|
| 11 |
+
|
| 12 |
+
# Copy requirements and install
|
| 13 |
+
COPY requirements.txt .
|
| 14 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 15 |
+
|
| 16 |
+
# Copy application files
|
| 17 |
+
COPY . .
|
| 18 |
+
|
| 19 |
+
# Set environment variables for Gradio
|
| 20 |
+
ENV GRADIO_SERVER_NAME="0.0.0.0"
|
| 21 |
+
ENV GRADIO_SERVER_PORT="7860"
|
| 22 |
+
|
| 23 |
+
# Expose the Gradio port
|
| 24 |
+
EXPOSE 7860
|
| 25 |
+
|
| 26 |
+
# Run the application
|
| 27 |
+
CMD ["python", "app.py"]
|
README.md
CHANGED
|
@@ -10,4 +10,121 @@ app_file: app.py
|
|
| 10 |
pinned: false
|
| 11 |
---
|
| 12 |
|
| 13 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
pinned: false
|
| 11 |
---
|
| 12 |
|
| 13 |
+
# ⚖️ Automated Legal Document Digitization System
|
| 14 |
+
|
| 15 |
+
Upload a photo of a legal document (bailable warrant, summon, etc.) and get back structured JSON data — automatically.
|
| 16 |
+
|
| 17 |
+
## Pipeline
|
| 18 |
+
|
| 19 |
+
```
|
| 20 |
+
Image Upload → Cloudinary Hosting → Tesseract OCR → NVIDIA Qwen 2.5 LLM → Structured JSON
|
| 21 |
+
```
|
| 22 |
+
|
| 23 |
+
---
|
| 24 |
+
|
| 25 |
+
## Prerequisites
|
| 26 |
+
|
| 27 |
+
### 1. Install Tesseract OCR (System Binary)
|
| 28 |
+
|
| 29 |
+
Python's `pytesseract` is only a wrapper — you need the **Tesseract engine** installed on your OS.
|
| 30 |
+
|
| 31 |
+
#### Windows
|
| 32 |
+
1. Download the installer from: https://github.com/UB-Mannheim/tesseract/wiki
|
| 33 |
+
2. Run the installer (default path: `C:\Program Files\Tesseract-OCR\`)
|
| 34 |
+
3. **Add to PATH** or uncomment the line in `app.py`:
|
| 35 |
+
```python
|
| 36 |
+
pytesseract.pytesseract.tesseract_cmd = r"C:\Program Files\Tesseract-OCR\tesseract.exe"
|
| 37 |
+
```
|
| 38 |
+
|
| 39 |
+
#### Linux (Debian / Ubuntu)
|
| 40 |
+
```bash
|
| 41 |
+
sudo apt-get update
|
| 42 |
+
sudo apt-get install tesseract-ocr
|
| 43 |
+
```
|
| 44 |
+
|
| 45 |
+
#### macOS
|
| 46 |
+
```bash
|
| 47 |
+
brew install tesseract
|
| 48 |
+
```
|
| 49 |
+
|
| 50 |
+
### 2. Verify Tesseract
|
| 51 |
+
```bash
|
| 52 |
+
tesseract --version
|
| 53 |
+
```
|
| 54 |
+
|
| 55 |
+
---
|
| 56 |
+
|
| 57 |
+
## Setup
|
| 58 |
+
|
| 59 |
+
### 1. Clone & Install Dependencies
|
| 60 |
+
|
| 61 |
+
```bash
|
| 62 |
+
cd police
|
| 63 |
+
pip install -r requirements.txt
|
| 64 |
+
```
|
| 65 |
+
|
| 66 |
+
### 2. Create a `.env` File
|
| 67 |
+
|
| 68 |
+
Create a `.env` file in the project root with your credentials:
|
| 69 |
+
|
| 70 |
+
```env
|
| 71 |
+
# Cloudinary
|
| 72 |
+
CLOUDINARY_CLOUD_NAME=your_cloud_name
|
| 73 |
+
CLOUDINARY_API_KEY=your_api_key
|
| 74 |
+
CLOUDINARY_API_SECRET=your_api_secret
|
| 75 |
+
|
| 76 |
+
# NVIDIA API
|
| 77 |
+
NVIDIA_API_KEY=your_nvidia_api_key
|
| 78 |
+
```
|
| 79 |
+
|
| 80 |
+
| Variable | Where to get it |
|
| 81 |
+
|---|---|
|
| 82 |
+
| `CLOUDINARY_*` | [Cloudinary Console](https://console.cloudinary.com/) → Dashboard |
|
| 83 |
+
| `NVIDIA_API_KEY` | [NVIDIA Build](https://build.nvidia.com/) → API Catalog → Get API Key |
|
| 84 |
+
|
| 85 |
+
### 3. Run the App
|
| 86 |
+
|
| 87 |
+
```bash
|
| 88 |
+
python app.py
|
| 89 |
+
```
|
| 90 |
+
|
| 91 |
+
The Gradio interface will launch at **http://127.0.0.1:7860**.
|
| 92 |
+
|
| 93 |
+
---
|
| 94 |
+
|
| 95 |
+
## Output Fields
|
| 96 |
+
|
| 97 |
+
The LLM extracts these fields into a JSON object:
|
| 98 |
+
|
| 99 |
+
| Key | Description |
|
| 100 |
+
|---|---|
|
| 101 |
+
| `Case_FIR_Number` | FIR or case reference number |
|
| 102 |
+
| `Act_and_Sections` | Applicable legal acts and sections |
|
| 103 |
+
| `Type_of_Document` | Warrant, summon, notice, etc. |
|
| 104 |
+
| `Target_Police_Station` | Police station the document is addressed to |
|
| 105 |
+
| `IO_Name_and_Belt_No` | Investigating Officer's name and belt number |
|
| 106 |
+
| `IO_Mobile_Number` | IO's contact number |
|
| 107 |
+
| `Person_Name_To_Serve` | Name of the person to be served |
|
| 108 |
+
| `Person_Address` | Address of the person |
|
| 109 |
+
| `Court_Name` | Issuing court name |
|
| 110 |
+
| `Hearing_Date` | Scheduled hearing / appearance date |
|
| 111 |
+
|
| 112 |
+
---
|
| 113 |
+
|
| 114 |
+
## Project Structure
|
| 115 |
+
|
| 116 |
+
```
|
| 117 |
+
police/
|
| 118 |
+
├── app.py # Main application (single file)
|
| 119 |
+
├── requirements.txt # Python dependencies
|
| 120 |
+
├── .env # API keys (DO NOT commit)
|
| 121 |
+
└── README.md # This file
|
| 122 |
+
```
|
| 123 |
+
|
| 124 |
+
---
|
| 125 |
+
|
| 126 |
+
## Notes
|
| 127 |
+
|
| 128 |
+
- OCR accuracy depends on image quality. Clear, well-lit photos produce the best results.
|
| 129 |
+
- The LLM sets fields to `null` when they can't be extracted from the OCR text.
|
| 130 |
+
- All uploaded images are stored in the `warrants/` folder on your Cloudinary account.
|
app.py
ADDED
|
@@ -0,0 +1,371 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Automated Legal Document Digitization System
|
| 3 |
+
=============================================
|
| 4 |
+
Accepts an image of a legal document (warrant, summon, etc.),
|
| 5 |
+
hosts it on Cloudinary, extracts text via Tesseract OCR,
|
| 6 |
+
and parses structured data using NVIDIA's Qwen3-Coder-480B-A35B-Instruct.
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
import os
|
| 10 |
+
import re
|
| 11 |
+
import json
|
| 12 |
+
import traceback
|
| 13 |
+
|
| 14 |
+
import gradio as gr
|
| 15 |
+
import cloudinary
|
| 16 |
+
import cloudinary.uploader
|
| 17 |
+
import pytesseract
|
| 18 |
+
from PIL import Image
|
| 19 |
+
from openai import OpenAI
|
| 20 |
+
from dotenv import load_dotenv
|
| 21 |
+
|
| 22 |
+
# ──────────────────────────────────────────────
|
| 23 |
+
# A. Setup & Configuration
|
| 24 |
+
# ──────────────────────────────────────────────
|
| 25 |
+
|
| 26 |
+
load_dotenv()
|
| 27 |
+
|
| 28 |
+
# Cloudinary
|
| 29 |
+
cloudinary.config(
|
| 30 |
+
cloud_name=os.environ.get("CLOUDINARY_CLOUD_NAME"),
|
| 31 |
+
api_key=os.environ.get("CLOUDINARY_API_KEY"),
|
| 32 |
+
api_secret=os.environ.get("CLOUDINARY_API_SECRET"),
|
| 33 |
+
)
|
| 34 |
+
|
| 35 |
+
# NVIDIA API via OpenAI SDK
|
| 36 |
+
client = OpenAI(
|
| 37 |
+
base_url="https://integrate.api.nvidia.com/v1",
|
| 38 |
+
api_key=os.environ.get("NVIDIA_API_KEY"),
|
| 39 |
+
)
|
| 40 |
+
|
| 41 |
+
# Optional: point to Tesseract binary on Windows
|
| 42 |
+
# In Docker/Linux, tesseract is in the system PATH.
|
| 43 |
+
if os.name == 'nt':
|
| 44 |
+
tesseract_windows_path = r"C:\Program Files\Tesseract-OCR\tesseract.exe"
|
| 45 |
+
if os.path.exists(tesseract_windows_path):
|
| 46 |
+
pytesseract.pytesseract.tesseract_cmd = tesseract_windows_path
|
| 47 |
+
|
| 48 |
+
from pymongo import MongoClient
|
| 49 |
+
from datetime import datetime
|
| 50 |
+
|
| 51 |
+
# MongoDB Connection
|
| 52 |
+
mongo_uri = os.environ.get("MONGODB_URI")
|
| 53 |
+
mongo_client = None
|
| 54 |
+
db = None
|
| 55 |
+
collection = None
|
| 56 |
+
|
| 57 |
+
if mongo_uri:
|
| 58 |
+
try:
|
| 59 |
+
mongo_client = MongoClient(mongo_uri, serverSelectionTimeoutMS=5000)
|
| 60 |
+
# Ping the server to verify connection immediately
|
| 61 |
+
mongo_client.admin.command('ping')
|
| 62 |
+
db = mongo_client["police_db"]
|
| 63 |
+
collection = db["warrants"]
|
| 64 |
+
print("Connected successfully to MongoDB!")
|
| 65 |
+
except Exception as exc:
|
| 66 |
+
print(f"MongoDB connection failed: {exc}")
|
| 67 |
+
collection = None
|
| 68 |
+
|
| 69 |
+
# ──────────────────────────────────────────────
|
| 70 |
+
# B. Core Processing Logic
|
| 71 |
+
# ──────────────────────────────────────────────
|
| 72 |
+
|
| 73 |
+
SYSTEM_PROMPT = (
|
| 74 |
+
"You are an expert legal document parser. "
|
| 75 |
+
"I will provide raw, messy OCR text from a bailable warrant. "
|
| 76 |
+
"Extract the following fields and return ONLY a valid JSON object. "
|
| 77 |
+
"No markdown, no explanations.\n\n"
|
| 78 |
+
"Required keys:\n"
|
| 79 |
+
" Case_FIR_Number\n"
|
| 80 |
+
" Act_and_Sections\n"
|
| 81 |
+
" Type_of_Document\n"
|
| 82 |
+
" Target_Police_Station\n"
|
| 83 |
+
" IO_Name_and_Belt_No\n"
|
| 84 |
+
" IO_Mobile_Number\n"
|
| 85 |
+
" Person_Name_To_Serve\n"
|
| 86 |
+
" Person_Address\n"
|
| 87 |
+
" Court_Name\n"
|
| 88 |
+
" Hearing_Date\n\n"
|
| 89 |
+
"If a field cannot be found, set its value to null."
|
| 90 |
+
)
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
def process_document(image_path: str):
|
| 94 |
+
"""
|
| 95 |
+
End-to-end pipeline:
|
| 96 |
+
1. Upload image to Cloudinary
|
| 97 |
+
2. Run Tesseract OCR
|
| 98 |
+
3. Send raw text to NVIDIA Qwen for structured parsing
|
| 99 |
+
4. Return (cloudinary_url, raw_ocr_text, parsed_json)
|
| 100 |
+
"""
|
| 101 |
+
|
| 102 |
+
if image_path is None:
|
| 103 |
+
raise gr.Error("Please upload an image first.")
|
| 104 |
+
|
| 105 |
+
# ── Step 1: Cloudinary Upload ──────────────────────────
|
| 106 |
+
try:
|
| 107 |
+
upload_result = cloudinary.uploader.upload(
|
| 108 |
+
image_path,
|
| 109 |
+
folder="warrants",
|
| 110 |
+
resource_type="image",
|
| 111 |
+
)
|
| 112 |
+
cloudinary_url = upload_result.get("secure_url", "")
|
| 113 |
+
except Exception as exc:
|
| 114 |
+
raise gr.Error(f"Cloudinary upload failed: {exc}")
|
| 115 |
+
|
| 116 |
+
# ── Step 2: OCR via Tesseract ──────────────────────────
|
| 117 |
+
try:
|
| 118 |
+
img = Image.open(image_path)
|
| 119 |
+
raw_text = pytesseract.image_to_string(img, lang="eng")
|
| 120 |
+
except Exception as exc:
|
| 121 |
+
raw_text = f"[OCR Error] {exc}"
|
| 122 |
+
|
| 123 |
+
if not raw_text.strip():
|
| 124 |
+
raw_text = "[OCR returned empty text — image may be blank or unreadable]"
|
| 125 |
+
|
| 126 |
+
# ── Step 3 & 4: LLM Prompt + API Call ──────────────────
|
| 127 |
+
prompt = f"{SYSTEM_PROMPT}\n\n--- RAW OCR TEXT ---\n{raw_text}\n--- END ---"
|
| 128 |
+
|
| 129 |
+
llm_response = None
|
| 130 |
+
last_exception = None
|
| 131 |
+
models_to_try = [
|
| 132 |
+
"qwen/qwen3-coder-480b-a35b-instruct",
|
| 133 |
+
"meta/llama-3.3-70b-instruct",
|
| 134 |
+
"nvidia/llama-3.1-nemotron-70b-instruct",
|
| 135 |
+
"qwen/qwen3.5-122b-a10b",
|
| 136 |
+
]
|
| 137 |
+
|
| 138 |
+
for model_name in models_to_try:
|
| 139 |
+
try:
|
| 140 |
+
print(f"Attempting API call with model: {model_name}...")
|
| 141 |
+
completion = client.chat.completions.create(
|
| 142 |
+
model=model_name,
|
| 143 |
+
messages=[{"role": "user", "content": prompt}],
|
| 144 |
+
temperature=0.7,
|
| 145 |
+
top_p=0.8,
|
| 146 |
+
max_tokens=4096,
|
| 147 |
+
stream=True,
|
| 148 |
+
)
|
| 149 |
+
chunks = []
|
| 150 |
+
print(f"\n--- LLM Response Streaming ({model_name}) ---")
|
| 151 |
+
for chunk in completion:
|
| 152 |
+
if chunk.choices and chunk.choices[0].delta.content is not None:
|
| 153 |
+
content = chunk.choices[0].delta.content
|
| 154 |
+
print(content, end="", flush=True)
|
| 155 |
+
chunks.append(content)
|
| 156 |
+
print("\n--- End of Streaming ---\n")
|
| 157 |
+
llm_response = "".join(chunks)
|
| 158 |
+
break
|
| 159 |
+
except Exception as exc:
|
| 160 |
+
print(f"Model {model_name} failed: {exc}")
|
| 161 |
+
last_exception = exc
|
| 162 |
+
continue
|
| 163 |
+
|
| 164 |
+
if llm_response is None:
|
| 165 |
+
raise gr.Error(f"NVIDIA API call failed on all models. Last error: {last_exception}")
|
| 166 |
+
|
| 167 |
+
parsed_json = _clean_and_parse_json(llm_response)
|
| 168 |
+
|
| 169 |
+
# ── Step 6: Store in MongoDB ───────────────────────────
|
| 170 |
+
if collection is not None and "_parse_error" not in parsed_json:
|
| 171 |
+
try:
|
| 172 |
+
record = {
|
| 173 |
+
**parsed_json,
|
| 174 |
+
"cloudinary_url": cloudinary_url,
|
| 175 |
+
"raw_ocr_text": raw_text,
|
| 176 |
+
"uploaded_at": datetime.utcnow(),
|
| 177 |
+
}
|
| 178 |
+
collection.insert_one(record)
|
| 179 |
+
print("Record stored successfully in MongoDB!")
|
| 180 |
+
except Exception as exc:
|
| 181 |
+
print(f"Failed to store in MongoDB: {exc}")
|
| 182 |
+
|
| 183 |
+
return cloudinary_url, raw_text, parsed_json
|
| 184 |
+
|
| 185 |
+
|
| 186 |
+
REQUIRED_KEYS = [
|
| 187 |
+
"Case_FIR_Number",
|
| 188 |
+
"Act_and_Sections",
|
| 189 |
+
"Type_of_Document",
|
| 190 |
+
"Target_Police_Station",
|
| 191 |
+
"IO_Name_and_Belt_No",
|
| 192 |
+
"IO_Mobile_Number",
|
| 193 |
+
"Person_Name_To_Serve",
|
| 194 |
+
"Person_Address",
|
| 195 |
+
"Court_Name",
|
| 196 |
+
"Hearing_Date"
|
| 197 |
+
]
|
| 198 |
+
|
| 199 |
+
|
| 200 |
+
def _clean_and_parse_json(raw_response: str) -> dict:
|
| 201 |
+
"""
|
| 202 |
+
Strip markdown code fences (```json ... ```) and parse JSON.
|
| 203 |
+
Normalizes keys to match REQUIRED_KEYS, handling casing and spacing.
|
| 204 |
+
Falls back to a descriptive error dict on failure.
|
| 205 |
+
"""
|
| 206 |
+
cleaned = raw_response.strip()
|
| 207 |
+
|
| 208 |
+
# Remove ```json ... ``` or ``` ... ```
|
| 209 |
+
cleaned = re.sub(r"^```(?:json)?\s*", "", cleaned)
|
| 210 |
+
cleaned = re.sub(r"\s*```$", "", cleaned)
|
| 211 |
+
cleaned = cleaned.strip()
|
| 212 |
+
|
| 213 |
+
try:
|
| 214 |
+
data = json.loads(cleaned)
|
| 215 |
+
if isinstance(data, dict):
|
| 216 |
+
# Normalize key typos (e.g. double underscores, trailing spaces)
|
| 217 |
+
normalized = {}
|
| 218 |
+
for k, v in data.items():
|
| 219 |
+
norm_k = k.replace("__", "_").strip()
|
| 220 |
+
normalized[norm_k] = v
|
| 221 |
+
|
| 222 |
+
# Reconstruct dict ensuring 100% exact required keys are returned
|
| 223 |
+
final_data = {}
|
| 224 |
+
for req in REQUIRED_KEYS:
|
| 225 |
+
match_val = None
|
| 226 |
+
found = False
|
| 227 |
+
for k, v in normalized.items():
|
| 228 |
+
if k.lower() == req.lower():
|
| 229 |
+
match_val = v
|
| 230 |
+
found = True
|
| 231 |
+
break
|
| 232 |
+
final_data[req] = match_val if found else None
|
| 233 |
+
return final_data
|
| 234 |
+
return data
|
| 235 |
+
except json.JSONDecodeError:
|
| 236 |
+
return {
|
| 237 |
+
"_parse_error": True,
|
| 238 |
+
"_raw_llm_response": raw_response,
|
| 239 |
+
"_message": "Could not parse LLM response as JSON. See raw response above.",
|
| 240 |
+
}
|
| 241 |
+
|
| 242 |
+
|
| 243 |
+
def fetch_live_warrants(search_query: str = ""):
|
| 244 |
+
"""
|
| 245 |
+
Fetch all warrants from MongoDB, filter by search_query if provided,
|
| 246 |
+
and return a list of lists representing the table rows.
|
| 247 |
+
"""
|
| 248 |
+
if search_query is None or not isinstance(search_query, str):
|
| 249 |
+
search_query = ""
|
| 250 |
+
|
| 251 |
+
if collection is None:
|
| 252 |
+
return [["Database connection not available", "", "", "", "", "", "", "", "", "", ""]]
|
| 253 |
+
|
| 254 |
+
query = {}
|
| 255 |
+
if search_query.strip():
|
| 256 |
+
# Search across major fields case-insensitively
|
| 257 |
+
rgx = {"$regex": search_query.strip(), "$options": "i"}
|
| 258 |
+
query = {
|
| 259 |
+
"$or": [
|
| 260 |
+
{"Case_FIR_Number": rgx},
|
| 261 |
+
{"Type_of_Document": rgx},
|
| 262 |
+
{"Target_Police_Station": rgx},
|
| 263 |
+
{"IO_Name_and_Belt_No": rgx},
|
| 264 |
+
{"Person_Name_To_Serve": rgx},
|
| 265 |
+
{"Court_Name": rgx},
|
| 266 |
+
]
|
| 267 |
+
}
|
| 268 |
+
|
| 269 |
+
try:
|
| 270 |
+
cursor = collection.find(query).sort("uploaded_at", -1)
|
| 271 |
+
rows = []
|
| 272 |
+
for item in cursor:
|
| 273 |
+
uploaded_str = ""
|
| 274 |
+
if "uploaded_at" in item:
|
| 275 |
+
dt = item["uploaded_at"]
|
| 276 |
+
uploaded_str = dt.strftime("%Y-%m-%d %H:%M:%S")
|
| 277 |
+
|
| 278 |
+
rows.append([
|
| 279 |
+
uploaded_str,
|
| 280 |
+
item.get("Case_FIR_Number") or "",
|
| 281 |
+
item.get("Type_of_Document") or "",
|
| 282 |
+
item.get("Target_Police_Station") or "",
|
| 283 |
+
item.get("IO_Name_and_Belt_No") or "",
|
| 284 |
+
item.get("IO_Mobile_Number") or "",
|
| 285 |
+
item.get("Person_Name_To_Serve") or "",
|
| 286 |
+
item.get("Person_Address") or "",
|
| 287 |
+
item.get("Court_Name") or "",
|
| 288 |
+
item.get("Hearing_Date") or "",
|
| 289 |
+
item.get("cloudinary_url") or "",
|
| 290 |
+
])
|
| 291 |
+
|
| 292 |
+
if not rows:
|
| 293 |
+
return [["No records found matching search", "", "", "", "", "", "", "", "", "", ""]]
|
| 294 |
+
|
| 295 |
+
return rows
|
| 296 |
+
except Exception as exc:
|
| 297 |
+
return [[f"Error fetching data: {exc}", "", "", "", "", "", "", "", "", "", ""]]
|
| 298 |
+
|
| 299 |
+
|
| 300 |
+
# ──────────────────────────────────────────────
|
| 301 |
+
# C. Gradio Interface
|
| 302 |
+
# ──────────────────────────────────────────────
|
| 303 |
+
|
| 304 |
+
DESCRIPTION = """
|
| 305 |
+
Upload a photo of a **bailable warrant**, **summon**, or similar legal document.
|
| 306 |
+
The system will:
|
| 307 |
+
1. **Host** the image on Cloudinary
|
| 308 |
+
2. **Extract** raw text via Tesseract OCR
|
| 309 |
+
3. **Parse** structured data using NVIDIA Qwen 3 Coder 480B
|
| 310 |
+
4. **Store** the output securely in MongoDB for live police tracking
|
| 311 |
+
"""
|
| 312 |
+
|
| 313 |
+
with gr.Blocks(title="⚖️ Automated Legal Document Digitization") as demo:
|
| 314 |
+
gr.Markdown("# ⚖️ Automated Legal Document Digitization System")
|
| 315 |
+
|
| 316 |
+
with gr.Tabs():
|
| 317 |
+
with gr.Tab("📥 Digitization Pipeline"):
|
| 318 |
+
gr.Markdown(DESCRIPTION)
|
| 319 |
+
with gr.Row():
|
| 320 |
+
with gr.Column(scale=1):
|
| 321 |
+
image_input = gr.Image(type="filepath", label="Upload Warrant / Summon Photo")
|
| 322 |
+
submit_btn = gr.Button("🚀 Process Document", variant="primary")
|
| 323 |
+
with gr.Column(scale=2):
|
| 324 |
+
cloudinary_url_out = gr.Textbox(label="☁️ Cloudinary URL")
|
| 325 |
+
raw_ocr_out = gr.Textbox(label="🔍 Raw OCR Text (Debugging)", lines=8)
|
| 326 |
+
json_out = gr.JSON(label="📋 Extracted Structured Data (JSON)")
|
| 327 |
+
|
| 328 |
+
# Action wiring for process_document
|
| 329 |
+
submit_btn.click(
|
| 330 |
+
fn=process_document,
|
| 331 |
+
inputs=[image_input],
|
| 332 |
+
outputs=[cloudinary_url_out, raw_ocr_out, json_out]
|
| 333 |
+
)
|
| 334 |
+
|
| 335 |
+
with gr.Tab("👮 Live Police Dashboard") as dashboard_tab:
|
| 336 |
+
gr.Markdown("## 📋 Real-Time Stored Warrants & Summons")
|
| 337 |
+
gr.Markdown("View and search all digitized legal documents stored securely in MongoDB.")
|
| 338 |
+
|
| 339 |
+
with gr.Row():
|
| 340 |
+
search_box = gr.Textbox(placeholder="🔍 Search by Case Number, IO Name, Person, or Station...", show_label=False, scale=4)
|
| 341 |
+
refresh_btn = gr.Button("🔄 Refresh Database", variant="secondary", scale=1)
|
| 342 |
+
|
| 343 |
+
headers = [
|
| 344 |
+
"Uploaded At",
|
| 345 |
+
"Case/FIR Number",
|
| 346 |
+
"Type of Document",
|
| 347 |
+
"Target Station",
|
| 348 |
+
"IO Name & Belt No",
|
| 349 |
+
"IO Mobile",
|
| 350 |
+
"Person to Serve",
|
| 351 |
+
"Address",
|
| 352 |
+
"Court Name",
|
| 353 |
+
"Hearing Date",
|
| 354 |
+
"Cloudinary Link"
|
| 355 |
+
]
|
| 356 |
+
|
| 357 |
+
db_df = gr.Dataframe(
|
| 358 |
+
value=fetch_live_warrants(""),
|
| 359 |
+
headers=headers,
|
| 360 |
+
datatype=["str"] * len(headers),
|
| 361 |
+
column_count=(len(headers), "fixed"),
|
| 362 |
+
interactive=False,
|
| 363 |
+
wrap=True,
|
| 364 |
+
)
|
| 365 |
+
|
| 366 |
+
# Wire up search and refresh
|
| 367 |
+
search_box.change(fn=fetch_live_warrants, inputs=[search_box], outputs=[db_df])
|
| 368 |
+
refresh_btn.click(fn=fetch_live_warrants, inputs=[search_box], outputs=[db_df])
|
| 369 |
+
|
| 370 |
+
if __name__ == "__main__":
|
| 371 |
+
demo.launch(server_name="0.0.0.0", server_port=7860)
|
model.md
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Qwen3-Coder-480B-A35B-Instruct
|
| 2 |
+
Model Overview
|
| 3 |
+
Description:
|
| 4 |
+
Qwen3-Coder-480B-A35B-Instruct is a state-of-the-art large language model specifically designed for code generation and agentic coding tasks. It is a mixture-of-experts (MoE) model with 480B total parameters and 35B activated parameters, featuring native support for 262,144 tokens context length and extendable up to 1M tokens using YaRN.
|
| 5 |
+
|
| 6 |
+
This model demonstrates significant performance among open models on Agentic Coding, Agentic Browser-Use, and other foundational coding tasks, achieving results comparable to Claude Sonnet. It supports function calling and tool choice capabilities, making it ideal for complex coding workflows and agentic applications.
|
| 7 |
+
|
| 8 |
+
This model is ready for commercial use.
|
| 9 |
+
|
| 10 |
+
License/Terms of Use
|
| 11 |
+
GOVERNING TERMS: This trial service is governed by the NVIDIA API Trial Terms of Service. Use of this model is governed by the NVIDIA Community Model License. Additional Information: Apache 2.0.
|
| 12 |
+
|
| 13 |
+
Deployment Geography
|
| 14 |
+
Deployment Geography: Global
|
| 15 |
+
|
| 16 |
+
Use Cases
|
| 17 |
+
Code Generation: Generate high-quality code from natural language descriptions
|
| 18 |
+
Agentic Coding: Execute complex coding workflows with function calling
|
| 19 |
+
Repository Understanding: Process large codebases with long-context capabilities
|
| 20 |
+
Tool Integration: Interface with development tools and APIs
|
| 21 |
+
Code Review and Analysis: Analyze and improve existing code
|
| 22 |
+
Documentation Generation: Create code documentation and comments
|
| 23 |
+
Browser Automation: Agentic browser-use scenarios
|
| 24 |
+
Function Calling: Structured tool execution and API integration
|
| 25 |
+
Release Information
|
| 26 |
+
Release Date: 08/22/2025
|
| 27 |
+
Build.NVIDIA.com: Available via link
|
| 28 |
+
|
| 29 |
+
Third-Party Community Consideration
|
| 30 |
+
This model is not owned or developed by NVIDIA. This model has been developed by Qwen (Alibaba Cloud). This model has been developed and built to a third-party's requirements for this application and use case; see link to Qwen3-Coder-480B-A35B-Instruct.
|
| 31 |
+
|
| 32 |
+
References
|
| 33 |
+
Qwen3-Coder: A Large Language Model for Code Generation
|
| 34 |
+
Qwen3-Coder GitHub Repository
|
| 35 |
+
Qwen Documentation
|
| 36 |
+
Hugging Face Model Page
|
| 37 |
+
Qwen3 Technical Report (arXiv:2505.09388)
|
| 38 |
+
Model Architecture
|
| 39 |
+
Architecture Type: mixture-of-experts (MoE) with Sparse Activation
|
| 40 |
+
Network Architecture: Qwen3MoeForCausalLM (Transformer-based decoder-only)
|
| 41 |
+
Parameter Count: 480B total parameters with 35B activated parameters
|
| 42 |
+
Expert Configuration: 160 experts with 8 activated per forward pass
|
| 43 |
+
Attention Mechanism: Grouped Query Attention (GQA) with 96 query heads and 8 KV heads
|
| 44 |
+
Number of Layers: 62
|
| 45 |
+
Hidden Size: 6144
|
| 46 |
+
Head Dimension: 128
|
| 47 |
+
Intermediate Size: 8192
|
| 48 |
+
MoE Intermediate Size: 2560
|
| 49 |
+
Context Length: 262,144 tokens (native), extendable to 1M with YaRN
|
| 50 |
+
Vocabulary Size: 151,936
|
| 51 |
+
|
| 52 |
+
Input
|
| 53 |
+
Input Type(s): Text, Code, Function calls
|
| 54 |
+
Input Format(s): Natural language prompts, code snippets, structured function calls
|
| 55 |
+
Input Parameters:
|
| 56 |
+
|
| 57 |
+
Max input length: 262,144 tokens (native), up to 1M with YaRN
|
| 58 |
+
Support for function calling format
|
| 59 |
+
Tool choice enabled
|
| 60 |
+
Trust remote code execution
|
| 61 |
+
Custom tool call parser (qwen3_coder)
|
| 62 |
+
Output
|
| 63 |
+
Output Type(s): Text, Code, Function responses
|
| 64 |
+
Output Format(s): Natural language responses, code generation, structured function outputs
|
| 65 |
+
Output Parameters: One-Dimensional (1D)
|
| 66 |
+
|
| 67 |
+
Max output length: Configurable based on remaining context
|
| 68 |
+
Function call responses in structured format
|
| 69 |
+
Other Properties Related to Output:
|
| 70 |
+
|
| 71 |
+
Non-thinking mode (no <think></think> blocks)
|
| 72 |
+
Auto tool choice responses
|
| 73 |
+
Software Integration
|
| 74 |
+
Runtime Engine: vLLM, Transformers (4.51.0+)
|
| 75 |
+
Supported Hardware Platform(s): NVIDIA Hopper
|
| 76 |
+
Supported Operating System(s): Linux
|
| 77 |
+
Data Type: FP8
|
| 78 |
+
Data Modality: Text
|
| 79 |
+
Model Version: v1.0
|
| 80 |
+
|
| 81 |
+
Training, Testing, and Evaluation Datasets
|
| 82 |
+
Training Dataset
|
| 83 |
+
Data Collection Method by dataset: The model was trained on a diverse dataset including code repositories, documentation, and natural language text related to programming
|
| 84 |
+
Labeling Method by dataset: Supervised fine-tuning with instruction-following data
|
| 85 |
+
Properties: Multi-language code support, instruction-following capabilities, function calling training
|
| 86 |
+
Testing Dataset
|
| 87 |
+
Data Collection Method by dataset: Standard benchmarks for code generation and agentic tasks
|
| 88 |
+
Labeling Method by dataset: Automated evaluation metrics
|
| 89 |
+
Properties: HumanEval, MBPP, Agentic coding benchmarks
|
| 90 |
+
Evaluation Dataset
|
| 91 |
+
Data Collection Method by dataset: Public benchmarks and custom evaluation sets
|
| 92 |
+
Labeling Method by dataset: Automated metrics and human evaluation
|
| 93 |
+
Properties: Code generation quality, function calling accuracy, agentic task performance
|
| 94 |
+
Benchmark Results
|
| 95 |
+
The model achieves significant performance among open models on:
|
| 96 |
+
|
| 97 |
+
Agentic Coding tasks
|
| 98 |
+
Agentic Browser-Use scenarios
|
| 99 |
+
Foundational coding benchmarks
|
| 100 |
+
Results comparable to Claude Sonnet on various coding tasks
|
| 101 |
+
Inference
|
| 102 |
+
Acceleration Engine: vLLM
|
| 103 |
+
Test Hardware: NVIDIA Hopper
|
| 104 |
+
|
| 105 |
+
Ethical Considerations
|
| 106 |
+
NVIDIA believes Trustworthy AI is a shared responsibility and we have established policies and practices to enable development for a wide array of AI applications. When downloaded or used in accordance with our terms of service, developers should work with their internal model team to ensure this model meets requirements for the relevant industry and use case and addresses unforeseen product misuse.
|
requirements.txt
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
gradio==4.44.1
|
| 2 |
+
cloudinary
|
| 3 |
+
pytesseract
|
| 4 |
+
pillow
|
| 5 |
+
openai
|
| 6 |
+
python-dotenv
|
| 7 |
+
pymongo
|
| 8 |
+
dnspython
|
testimage.jpeg
ADDED
|
Git LFS Details
|