Search India's legal universe - Acts, GRs, Judgments, Circulars, Schemes - powered by a fully local AI that never sends your queries to the cloud.
- About the Project
- Key Features
- Tech Stack
- Project Structure
- Getting Started
- Usage
- API Documentation
- Configuration
- Testing
- Deployment
- Contributing
- Roadmap
- License
- Acknowledgements
- Contact / Author
India produces thousands of legal documents every year - Government Resolutions, Acts, Amendments, Judgments, Gazette Notifications, Circulars, Schemes - spread across dozens of central and state portals with no single place to find them. A lawyer in Ahmedabad researching a land acquisition judgment, a civil servant cross-checking a pension circular, or a student studying labour law reform should not have to stitch together a dozen bookmarked websites.
ULAGIP was built to fix that. It automatically scrapes government portals (eCourts, eGazette, India Code, Indian Kanoon, Gujarat GAD, and multiple ministry sites), indexes every document into Elasticsearch with a custom legal analyser, and exposes a clean search interface powered by a fully local Microsoft Phi-3.5-mini-instruct LLM - no API key required, no data sent to a third-party AI service.
The platform is designed for citizens, legal professionals, researchers, and government employees who need fast, reliable, multilingual access to official Indian legal documents. It supports all 22 scheduled Indian languages out of the box.
AI-Powered Search - Natural language queries are expanded using legal synonym dictionaries, NLP entity extraction (spaCy), and semantic embedding similarity before hitting Elasticsearch, so "land acquisition Gujarat HC order" finds the right documents even if the exact words don't match.
Fully Local LLM - Microsoft Phi-3.5-mini-instruct runs on-device via Transformers or llama-cpp-python (GGUF), meaning your search queries never leave your server. GPU inference with 8-bit quantisation is supported; a CPU-friendly GGUF fallback is automatically used when a GPU is not available.
AI Chatbot - A context-aware legal assistant chat interface lets users ask follow-up questions about documents, get plain-language summaries, and request related judgments or amendments - all grounded in the indexed document store.
Document Comparison - Side-by-side semantic diff of two documents using sentence-transformer embeddings and difflib, with AI-generated summary of key differences.
Automated Web Scraping - Seven Scrapy spiders run on a configurable schedule (default daily at 3 AM IST) targeting eCourts, eGazette, India Code, Indian Kanoon, Gujarat GAD, Gujarat State portal, and Union Ministry portals.
Push and Email Alerts - Users can subscribe to topic keywords and receive browser push notifications (VAPID / Web Push) or scheduled emails (daily or weekly) whenever matching new documents are indexed.
Progressive Web App - Fully installable PWA with a service worker, offline page, app shortcuts, and a complete Web App Manifest targeting government, legal, productivity, and education categories.
Multi-Language UI - The interface and AI responses support all 22 constitutionally scheduled Indian languages including Hindi, Gujarati, Marathi, Tamil, Telugu, Bengali, Urdu, and Sanskrit.
Rich Analytics Dashboard - Admins see trending queries, daily search volume, top documents, department breakdowns, document type distributions, and Celery task health - all drawn from a Redis + PostgreSQL analytics pipeline.
PDF Export - Any document detail page can be exported as a professionally formatted A4 PDF generated with ReportLab, complete with metadata, key provisions, cross-links, and an AI confidence badge.
OTP Authentication for Public Users - Public users register and log in via email OTP (no passwords stored in plaintext). Admin staff use a separate bcrypt-hashed credential system with role-based access (superadmin / admin / viewer).
Production Security Hardening - CSP headers, HSTS, X-Frame-Options: DENY, X-Content-Type-Options, CSRF protection on all forms, rate limiting via Flask-Limiter backed by Redis, and session hardening are all applied in app/__init__.py.
Backend
| Package | Purpose |
|---|---|
| Flask | Web framework and application factory |
| Flask-SQLAlchemy + Flask-Migrate | ORM and database migrations |
| Flask-Login | Admin session management |
| Flask-WTF | CSRF-protected forms |
| Flask-Limiter | IP-based rate limiting |
| Flask-Compress | Gzip response compression |
| Flask-Mail | Transactional email (OTP, alerts, welcome) |
| Gunicorn | WSGI production server |
| Celery + celery.beat | Async task queue and scheduled jobs |
| Scrapy + BeautifulSoup4 + lxml | Web scraping pipeline |
| Elasticsearch (python client) | Full-text document search and indexing |
| Transformers + PyTorch | Phi-3.5-mini-instruct inference |
| sentence-transformers | Multilingual embedding generation |
| llama-cpp-python | GGUF CPU fallback inference |
| spaCy | NLP entity extraction and keyword analysis |
| langdetect | Language detection for query routing |
| pdfplumber + PyMuPDF + python-docx + openpyxl | Document ingestion from multiple formats |
| ReportLab | PDF export generation |
| pywebpush | VAPID Web Push notifications |
| bcrypt | Password hashing |
| bleach | HTML sanitisation |
| redis | Caching and Celery broker |
| psycopg2-binary | PostgreSQL adapter |
Frontend
| Technology | Purpose |
|---|---|
| Jinja2 templates | Server-side HTML rendering |
| Bootstrap (CDN) | Responsive UI grid and components |
| Vanilla JavaScript | Search, chatbot, document viewer, compare, PWA, IndexedDB |
Service Worker (sw.js) |
Offline caching and background sync |
| Web App Manifest | PWA installability |
| Chart.js | Analytics charts |
| IndexedDB | Client-side search history and bookmark cache |
Infrastructure
| Tool | Role |
|---|---|
| PostgreSQL 15 | Primary relational database (production) |
| SQLite | Development database (auto-created, zero config) |
| Redis 7 | Cache layer, Celery broker and result backend |
| Elasticsearch 8.15 | Document index with custom legal analyser |
| Kibana 8.15 | Elasticsearch dev UI (optional, --profile dev) |
| Nginx 1.25 | Reverse proxy and static file serving |
| Docker + Docker Compose | Containerised stack |
ULAGIP-Hackathon-main/
├── app/ # Core Flask application package
│ ├── __init__.py # App factory, blueprint registration, security headers
│ ├── config.py # Development / Production / Testing config classes
│ ├── extensions.py # Shared Flask extensions (db, migrate, login_manager, etc.)
│ ├── data/
│ │ └── legal_synonyms.json # Domain synonym map for query expansion (GR, HC, RTI, etc.)
│ ├── forms/ # WTForms form definitions
│ │ ├── admin_form.py # Admin dashboard forms (create user, system config, alert filter)
│ │ ├── alert_form.py # Alert subscription form
│ │ ├── auth_form.py # Admin login form
│ │ ├── contact_form.py # Public contact form
│ │ ├── public_user_forms.py # Registration, OTP, password reset forms
│ │ └── search_form.py # Main search and filter form
│ ├── models/ # SQLAlchemy database models
│ │ ├── alerts.py # Anonymous alert subscriptions
│ │ ├── analytics.py # Search queries, access logs, daily rollups, compare history
│ │ ├── public_user.py # Public user, OTP, bookmarks, chat, comparisons, export logs
│ │ ├── system.py # SystemConfig, AdminAuditLog, CeleryTaskLog
│ │ └── user.py # Admin user model (superadmin / admin / viewer)
│ ├── routes/ # Flask blueprints (one file per feature domain)
│ │ ├── admin.py # Admin dashboard: users, config, logs, cache, analytics, tasks
│ │ ├── alerts.py # Alert subscription and push notification management
│ │ ├── analytics.py # Analytics API and public analytics dashboard
│ │ ├── auth.py # Admin login / logout
│ │ ├── chatbot.py # AI chatbot endpoint (/chatbot/message)
│ │ ├── compare.py # Document comparison view and history
│ │ ├── documents.py # Document detail, PDF export, API docs, health check
│ │ ├── main.py # Home page, trending, contact, help, about, privacy, terms
│ │ ├── public_auth.py # Public user registration, OTP verification, login, reset
│ │ ├── search.py # Search results, load-more, suggestions, trending
│ │ ├── search_history.py # Per-user and per-device search history API
│ │ └── user.py # Public user dashboard, bookmarks, chat history, profile
│ ├── scrapers/ # Scrapy scraping pipeline
│ │ ├── items.py # LegalDocumentItem field definitions
│ │ ├── middlewares.py # Custom Scrapy middlewares
│ │ ├── pipelines.py # Document validation, deduplication, and persistence pipeline
│ │ ├── settings.py # Scrapy settings (download delay, robots.txt, retries)
│ │ └── spiders/ # One spider class per source
│ │ ├── common.py # BaseGovernmentSpider with shared parsing logic
│ │ ├── ecourts_spider.py # NJDG eCourts (Judgment)
│ │ ├── egazette_spider.py# eGazette of India (Notification)
│ │ ├── gujarat_gr_spider.py # Gujarat GAD (GR)
│ │ ├── gujarat_state_spider.py # Gujarat State portal
│ │ ├── indiacode_spider.py # India Code NIC (Act)
│ │ ├── indian_kanoon_spider.py # Indian Kanoon (Judgment)
│ │ └── ministry_spider.py # MoEF, Labour, DoP, MoSPI, MHA (Policy)
│ ├── services/ # Business logic services (all singletons)
│ │ ├── ai_service.py # ModelLoader + AIService: LLM inference, search grounding, chatbot
│ │ ├── cache_service.py # Redis cache wrapper with TTL helpers
│ │ ├── elasticsearch_service.py # ES client, index creation, CRUD, bulk indexing
│ │ ├── email_service.py # Transactional email sending via Flask-Mail
│ │ ├── embedding_service.py # SentenceTransformer encoder and cosine similarity
│ │ ├── nlp_service.py # Entity extraction, keyword extraction, query expansion
│ │ ├── pdf_service.py # ReportLab A4 PDF document export
│ │ ├── prompt_builder.py # Structured prompt assembly for search and chat LLM calls
│ │ ├── push_service.py # VAPID Web Push notification sender
│ │ ├── search_service.py # Search orchestration: ES query + AI enrichment + caching
│ │ └── validator.py # Input sanitisation, filter validation, prompt injection guard
│ ├── tasks/ # Celery async and scheduled tasks
│ │ ├── celery_app.py # Celery factory with beat_schedule (7 periodic jobs)
│ │ ├── aggregate_analytics.py# Daily analytics rollup (runs at 02:00 IST)
│ │ ├── ai_tasks.py # AI health check (runs every 2 hours)
│ │ ├── check_new_grs.py # Detect and index new GRs (runs every 6 hours)
│ │ ├── dispatch_alerts.py # Send daily (08:00) and weekly (Mon 09:00) alert emails/push
│ │ ├── email_tasks.py # Async email dispatch task
│ │ ├── reindex_documents.py # Rebuild Elasticsearch index from database
│ │ ├── scrape_documents.py # Run all Scrapy spiders (runs daily at 03:00 IST)
│ │ └── warm_cache.py # Pre-warm Redis cache for top queries (every 30 minutes)
│ └── utils/ # Shared utility helpers
│ ├── decorators.py # @admin_required, @log_admin_action
│ ├── doc_types.py # DOC_TYPE_BADGES, STATUS_BADGES, CONFIDENCE_TIERS, DEPARTMENTS
│ ├── helpers.py # Date formatting, hash generation, query normalisation
│ ├── languages.py # INDIAN_LANGUAGES dict (23 languages) and UI_LANGUAGES
│ └── public_auth_utils.py # Session helpers for public user authentication
├── data/
│ └── sample_documents.json # Sample document seed data for development
├── scripts/
│ ├── download_models.py # Pre-download Phi-3.5 tokenizer, embeddings, spaCy and GGUF
│ ├── initial_scrape.py # One-shot trigger for all spiders
│ └── seed_elasticsearch.py # Seed Elasticsearch from sample_documents.json
├── static/
│ ├── css/ # Feature-scoped stylesheets (main, results, admin, compare, etc.)
│ ├── images/
│ │ ├── icons/ # PWA icons: 72, 96, 128, 144, 152, 192, 384, 512px
│ │ ├── logo.svg # ULAGIP SVG logo
│ │ ├── screenshot-desktop.png
│ │ └── screenshot-mobile.png
│ ├── js/ # Feature-scoped JavaScript modules
│ │ ├── search.js # Search form submission and URL state management
│ │ ├── results.js # Results page with infinite scroll and filter panel
│ │ ├── chatbot.js # Chatbot UI, session management, streaming response
│ │ ├── compare.js # Document diff view and comparison history
│ │ ├── document.js # Document detail viewer, bookmarking, PDF export trigger
│ │ ├── analytics.js # Analytics dashboard charts
│ │ ├── admin.js # Admin dashboard interactions
│ │ ├── accessibility.js # Keyboard nav, screen reader enhancements, high-contrast
│ │ ├── indexeddb.js # Client-side history, bookmarks and offline data via IndexedDB
│ │ ├── language.js # Dynamic language switching via Google Translate API
│ │ ├── pwa.js # PWA install prompt and service worker registration
│ │ ├── app.js # Global app bootstrap
│ │ └── user_sync.js # Sync IndexedDB state to server for logged-in users
│ ├── manifest.json # Web App Manifest for PWA installation
│ └── sw.js # Service worker (cache-first static, network-first dynamic)
├── templates/
│ ├── base.html # Base layout with navigation, footer, PWA meta
│ ├── admin/ # Admin panel page templates
│ ├── auth/ # Admin login template
│ ├── components/ # Reusable Jinja2 partials (chatbot bubble, result card, etc.)
│ ├── emails/ # HTML email templates (OTP, alerts, welcome, contact)
│ ├── errors/ # Error pages (400, 404, 429, 500)
│ ├── pages/ # Public-facing page templates
│ │ ├── index.html # Home page with search bar and trending queries
│ │ ├── results.html # Search results page with filter panel
│ │ ├── document_detail.html # Full document view with AI summary and cross-links
│ │ ├── chatbot.html # AI chatbot interface
│ │ ├── compare.html # Document comparison view
│ │ ├── alerts.html # Alert subscription management
│ │ ├── analytics.html # Public analytics dashboard
│ │ ├── my_documents.html # Saved bookmarks and export history
│ │ ├── auth/ # Public user auth pages (login, register, OTP, reset)
│ │ └── user/ # User dashboard, profile, chat history, comparisons
│ └── index.html # Minimal landing redirect
├── .env.example # Complete environment variable reference
├── .gitignore # Python, virtualenv, secrets, and IDE ignores
├── docker-compose.yml # Full stack: postgres, redis, elasticsearch, flask, celery, nginx
├── Dockerfile # Multi-stage build (builder + runtime, non-root ulagip user)
├── Makefile # Developer shortcuts: install, run, seed, docker-up, health
├── nginx.conf # Nginx reverse proxy configuration with static file serving
├── requirements.txt # Python dependencies (pinned to major versions)
├── run.py # Development entry point (creates tables, runs seed, starts Flask)
├── seed.py # Idempotent database seeder (admin user, system config defaults)
├── wsgi.py # Gunicorn WSGI entry point
├── generate_vapid.py # Helper to generate VAPID key pair
└── generate_pwa_icons.py # Helper to generate PWA icon set from a source image
Make sure you have the following installed before you begin:
| Tool | Minimum Version | Install |
|---|---|---|
| Python | 3.11 | https://www.python.org/downloads/ |
| pip | latest | bundled with Python 3.11 |
| Git | any | https://git-scm.com/ |
| Docker Desktop | 24+ | https://docs.docker.com/get-docker/ |
| Docker Compose | v2 (bundled with Docker Desktop) | https://docs.docker.com/compose/ |
For local development without Docker you will also need:
| Service | Version | Notes |
|---|---|---|
| Redis | 7+ | https://redis.io/download |
| Elasticsearch | 8.15 | https://www.elastic.co/downloads/elasticsearch |
| PostgreSQL | 15+ (optional) | SQLite is used automatically in development |
For GPU-accelerated LLM inference (optional):
| Requirement | Notes |
|---|---|
| NVIDIA GPU with 6 GB+ VRAM | The model loader checks this automatically |
| CUDA Toolkit 11.8+ | https://developer.nvidia.com/cuda-downloads |
If no GPU is available the application automatically falls back to the GGUF CPU model. You do not need to configure anything.
1. Clone the repository
git clone https://github.com/shahram8708/ULAGIP-Hackathon.git
cd ULAGIP-Hackathon2. Create and activate a virtual environment
python -m venv venv
source venv/bin/activate # Linux / macOS
venv\Scripts\activate # Windows3. Install Python dependencies
pip install -r requirements.txt4. Download spaCy language models
python -m spacy download en_core_web_sm
python -m spacy download xx_ent_wiki_sm5. Copy the environment file and fill in your values
cp .env.example .env
# Open .env in your editor and fill in at minimum:
# FLASK_SECRET_KEY
# MAIL_EMAIL and MAIL_PASSWORD (for OTP emails)
# VAPID_PUBLIC_KEY and VAPID_PRIVATE_KEY (for push notifications)6. Generate VAPID keys (if you do not have them yet)
python generate_vapid.py
# Copy the printed keys into your .env file7. Create the database tables and seed default data
make seed
# or manually:
# python -c "from dotenv import load_dotenv; load_dotenv(); from app import create_app; from app.extensions import db; from seed import run_seed; app=create_app(); ctx=app.app_context(); ctx.push(); db.create_all(); run_seed(); ctx.pop()"8. (Optional) Pre-download AI models
The models are downloaded lazily on first use, but you can pre-fetch them to avoid a cold-start delay:
python scripts/download_models.pyCopy .env.example to .env and fill in every value before running. Here is a reference for every variable:
| Variable | Description | Example |
|---|---|---|
FLASK_ENV |
Runtime mode: development or production |
development |
FLASK_SECRET_KEY |
64-character hex secret for session signing | a1b2c3... |
DATABASE_URL |
SQLAlchemy connection string. Leave empty in dev to use SQLite | postgresql://user:pass@localhost:5432/ulagip_db |
POSTGRES_DB |
PostgreSQL database name (used by Docker Compose) | ulagip_db |
POSTGRES_USER |
PostgreSQL username (used by Docker Compose) | ulagip_user |
POSTGRES_PASSWORD |
PostgreSQL password (used by Docker Compose) | StrongPass123! |
REDIS_URL |
Redis connection string | redis://localhost:6379/0 |
CELERY_BROKER_URL |
Celery message broker (usually same as Redis) | redis://localhost:6379/0 |
CELERY_RESULT_BACKEND |
Celery result backend | redis://localhost:6379/1 |
ELASTICSEARCH_URL |
Elasticsearch host URL | http://localhost:9200 |
ELASTICSEARCH_INDEX |
Elasticsearch index name | ulagip_documents |
HF_HOME |
HuggingFace model cache directory | /app/model_cache |
FORCE_CPU |
Force CPU inference even when GPU is available | false |
CPU_THREADS |
Thread count for llama-cpp CPU inference | 4 |
USE_GGUF |
Use GGUF model instead of Transformers | false |
GENERATION_MODEL |
HuggingFace model ID for text generation | microsoft/Phi-3.5-mini-instruct |
EMBEDDING_MODEL |
SentenceTransformer model ID for embeddings | sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2 |
MODEL_MAX_NEW_TOKENS |
Maximum tokens the LLM generates per call | 512 |
MODEL_TEMPERATURE |
LLM temperature (lower = more deterministic) | 0.1 |
MAIL_SERVER |
SMTP server hostname | smtp.gmail.com |
MAIL_PORT |
SMTP port | 587 |
MAIL_USE_TLS |
Enable STARTTLS | true |
MAIL_USE_SSL |
Enable SSL (mutually exclusive with TLS) | false |
MAIL_EMAIL |
Sender email address | noreply@ulagip.in |
MAIL_PASSWORD |
SMTP app password | your-app-password |
MAIL_SENDER_NAME |
Display name for outgoing emails | ULAGIP |
MAIL_ADMIN_EMAIL |
Email address that receives contact form submissions | admin@ulagip.in |
VAPID_PUBLIC_KEY |
VAPID public key for Web Push | BNxxxx... |
VAPID_PRIVATE_KEY |
VAPID private key for Web Push | xxxx... |
VAPID_CLAIM_EMAIL |
Contact email for VAPID claims | admin@ulagip.in |
SUPER_ADMIN_DEFAULT_PASSWORD |
Default password seeded for the superadmin account | ULAGIP@Admin2026! |
PORT |
Port Flask or Gunicorn listens on | 5000 |
FLASK_DEBUG_TOOLBAR |
Enable Flask debug toolbar in development | false |
SCRAPE_INTERVAL_HOURS |
Target scrape frequency for Celery beat | 24 |
SCRAPE_ENABLED |
Enable or disable the scheduled scrape task | true |
SCRAPY_CACHE_DIR |
Directory for Scrapy HTTP cache | /app/scrapy_cache |
Development mode (SQLite, hot reload, no Docker needed):
make run
# or
python run.pyThe app will be available at http://localhost:5000.
Start Celery worker (in a separate terminal - needed for background tasks):
celery -A app.tasks.celery_app.celery worker --loglevel=infoStart Celery beat (in a separate terminal - needed for scheduled jobs):
celery -A app.tasks.celery_app.celery beat --loglevel=infoProduction mode with Docker (recommended - starts everything with one command):
make docker-up
# or
docker compose up -d --buildThis brings up: PostgreSQL, Redis, Elasticsearch, Flask (Gunicorn), Celery worker, Celery beat, and Nginx - all networked together on 172.20.0.0/16.
Check health:
make health
# or
curl http://localhost:5000/healthzTail logs:
make logs
# or
docker compose logs -fStart with Kibana for Elasticsearch inspection (dev profile only):
docker compose --profile dev up -d
# Kibana: http://localhost:5601Seed Elasticsearch from the sample document set:
python scripts/seed_elasticsearch.pyRun an initial scrape manually:
python scripts/initial_scrape.pySearching documents
Open the home page (http://localhost:5000), type a query in natural language or use legal shorthand, and press Enter. Examples:
pension Gujarat government employees
land acquisition Right to Fair Compensation Act
RTI circular Central Government 2024
SC judgment on Article 21 education rights
Gujarat GR housing scheme 2023
Filters on the results page let you narrow by document type (GR, Act, Judgment, Circular, etc.), jurisdiction (Central / Gujarat / Both), department, status (Active / Superseded / Repealed / Under Challenge), date range, authority level, applicable region, and subject domain.
AI Chatbot
Visit /chatbot to open the legal AI assistant. You can ask it to:
Summarise the Right to Fair Compensation Act in plain Gujarati
What GRs exist about agricultural subsidies in Gujarat after 2020?
Compare the 2015 and 2022 amendments to the Gujarat Land Revenue Code
The chatbot is grounded in your indexed document store - it will not fabricate document references.
Document comparison
Navigate to /compare?doc_id_a=<id>&doc_id_b=<id> or use the "Compare" button on any two document detail pages. The view shows a semantic diff, highlights structural differences, and provides an AI-generated summary of what changed between the two documents.
Setting up an alert
Go to /alerts and subscribe to a keyword topic (for example, pension Gujarat or RTI amendment). Choose instant, daily, or weekly frequency. Enable browser notifications when prompted to receive Web Push alerts when new matching documents are indexed.
Admin panel
Access the admin panel at /admin after logging in with the seeded superadmin credentials. From there you can manage users, inspect system config, view and clear Redis cache, read application logs, trigger background tasks manually, and view full analytics with CSV export.
PDF export
On any document detail page, click "Export PDF" to download an A4-formatted report containing the document metadata, AI summary, key provisions, penalties, eligibility criteria, deadlines, cross-links, and source URLs.
All routes serve HTML by default. The following JSON endpoints are available for programmatic use:
| Method | Path | Description | Query Parameters |
|---|---|---|---|
GET |
/search/suggestions |
Autocomplete suggestion strings | q (min 2 chars) |
GET |
/results |
Full search results page (HTML) | q, lang, doc_type, jurisdiction, department, status, date_from, date_to, authority, region, subject_domain |
GET |
/results/load-more |
Paginated results JSON for infinite scroll | q, page, lang, plus all filter params |
POST |
/chatbot/message |
AI chatbot message (JSON in, JSON out) | Body: {"message": "...", "history": [...], "lang": "en", "session_id": "..."} |
GET |
/analytics/data |
Analytics time-series JSON for charts | date_from, date_to, department |
GET |
/api/explain-term |
AI plain-language explanation of a legal term | term (max 100 chars) |
GET |
/document/<doc_id> |
Document detail page (HTML) | lang |
GET |
/document/<doc_id>/export-pdf |
Download AI-enriched PDF of a document | none |
POST |
/alerts/subscribe-push |
Register a Web Push subscription | Body: {"subscription": {...}, "topic_keyword": "...", "frequency": "daily"} |
GET |
/healthz |
Health check endpoint (returns 200 OK with JSON) | none |
Chatbot message request example:
POST /chatbot/message
Content-Type: application/json
X-CSRFToken: <token>
{
"message": "What are the key provisions of the Gujarat Land Revenue Code?",
"history": [],
"lang": "en",
"session_id": "abc123"
}Chatbot message response example:
{
"response": "The Gujarat Land Revenue Code (1879) ...",
"session_id": "abc123",
"sources": [{"id": "...", "title": "Gujarat Land Revenue Code"}]
}Search suggestions response example:
["pension Gujarat government employees", "pension rules amendment 2022", "pension fund circular"]Rate limits (default, adjustable via SystemConfig): 30 requests/minute for /results, 20 requests/minute for /chatbot/message, 5 requests/minute for /alerts.
System configuration is stored in the system_config database table, managed from the admin panel at /admin/config. Changes take effect immediately without a restart. Key configurable values include:
| Config Key | Type | Default | What it controls |
|---|---|---|---|
local_ai_base_system_prompt |
string | ULAGIP assistant prompt | The system prompt injected into every LLM call |
url_whitelist_domains |
JSON array | gov.in, nic.in, indiankanoon.org, etc. |
Domains the AI is allowed to cite as source URLs |
max_results_per_search |
integer | 20 | Maximum documents returned per search |
cache_ttl_search |
integer | 3600 | Redis TTL in seconds for search result cache |
cache_ttl_document |
integer | 7200 | Redis TTL in seconds for document detail cache |
cache_ttl_suggestions |
integer | 300 | Redis TTL in seconds for autocomplete suggestions |
rate_limit_results_per_minute |
integer | 30 | Rate limit for the /results route |
rate_limit_chatbot_per_minute |
integer | 20 | Rate limit for /chatbot/message |
rate_limit_alerts_per_minute |
integer | 5 | Rate limit for alert subscription |
trending_window_days |
integer | 7 | Rolling window for trending search calculation |
cache_warm_top_n |
integer | 50 | Number of top queries to pre-warm in Redis |
disclaimer_text |
string | AI disclaimer | Footer disclaimer text shown on all pages |
Static configuration files:
app/config.py - Flask config classes (DevelopmentConfig, ProductionConfig, TestingConfig). Switch between them by setting FLASK_ENV=production in your .env.
app/scrapers/settings.py - Scrapy settings: download delay (2 seconds), robots.txt compliance, and retry settings.
nginx.conf - Nginx reverse proxy config. Adjust worker_processes, proxy_read_timeout, and the static file cache headers here.
No automated test suite is present in this repository at the time of writing. The Makefile includes a make test target that calls pytest -q, but no test files (test_*.py) were found in the codebase.
If you want to add tests, the TestingConfig class in app/config.py is already set up with an in-memory SQLite database and CSRF disabled:
class TestingConfig(BaseConfig):
TESTING = True
SQLALCHEMY_DATABASE_URI = "sqlite:///:memory:"
WTF_CSRF_ENABLED = FalseA good starting point for a test suite would be:
pip install pytest pytest-flask
# Create tests/test_search.py, tests/test_auth.py, etc.
make testThe full production stack runs with a single command:
# 1. Clone and configure
git clone https://github.com/shahram8708/ULAGIP-Hackathon.git
cd ULAGIP-Hackathon
cp .env.example .env
# Edit .env: set FLASK_ENV=production, DATABASE_URL, FLASK_SECRET_KEY, mail config, VAPID keys
# 2. Build and start
docker compose up -d --build
# 3. Verify health
curl http://localhost/healthzThe compose stack starts these services:
| Service | What it runs |
|---|---|
postgres |
PostgreSQL 15-alpine with healthcheck |
redis |
Redis 7-alpine with 512 MB LRU cache |
elasticsearch |
Elasticsearch 8.15 (single-node, security disabled) |
flask |
Gunicorn with 3 workers, 120s timeout, bound to port 5000 |
celery_worker |
Celery worker with concurrency 2, max 100 tasks/child |
celery_beat |
Celery beat scheduler with persistent scheduler |
nginx |
Nginx reverse proxy on ports 80 and 443 |
The Dockerfile uses a two-stage build. The builder stage installs all dependencies under /root/.local. The runtime stage copies only those built packages into a minimal image running as a non-root ulagip user (UID 1001).
# Install dependencies and activate venv
pip install -r requirements.txt
# Run database migrations
flask db upgrade
# Start Gunicorn
gunicorn wsgi:app \
--workers 3 \
--worker-class sync \
--bind 0.0.0.0:5000 \
--timeout 120
# Start Celery worker (separate process)
celery -A app.tasks.celery_app.celery worker --loglevel=info --concurrency=2
# Start Celery beat (separate process)
celery -A app.tasks.celery_app.celery beat --loglevel=infoIf you replace the logo, regenerate the full icon set:
python generate_pwa_icons.pypython generate_vapid.py
# Outputs VAPID_PUBLIC_KEY and VAPID_PRIVATE_KEY - add both to your .envContributions are welcome. Here is how to get involved:
1. Fork the repository and create a feature branch from main:
git checkout -b feature/your-feature-name2. Make your changes. Follow these conventions:
- Python: PEP 8 style, 4-space indentation, descriptive variable names
- Keep service logic in
app/services/, route logic inapp/routes/, and data models inapp/models/ - All user-facing input must pass through
ValidatorService.sanitise_input()before use - New environment variables must be added to
.env.examplewith a description
3. Test your changes manually (automated tests are not yet present):
make run
# Verify your feature works end to end4. Commit with a clear message:
git commit -m "feat: add PDF text extraction for DOCX uploads in scraper pipeline"5. Open a Pull Request against main describing what you changed and why.
Reporting bugs:
Open a GitHub Issue and include:
- Steps to reproduce
- Expected behaviour
- Actual behaviour
- Python version, OS, and whether you are using Docker or local setup
- Relevant log output from
docker compose logs flaskor the terminal
Requesting features:
Open a GitHub Issue with the enhancement label. Describe the use case first - what problem does it solve, who benefits, and how would you expect it to behave?
Based on the codebase structure and TODO signals found in comments, here are the areas most ready for improvement:
Done
- Full-text Elasticsearch search with custom legal analyser and edge-ngram support
- Local LLM inference (Transformers + GGUF fallback)
- Seven Scrapy spiders across Central and Gujarat government sources
- Public user system with OTP email authentication
- Celery beat with 7 scheduled jobs
- Web Push (VAPID) and email alert subscriptions
- Document comparison with semantic diff
- PDF export with ReportLab
- PWA with service worker and full manifest
- Analytics dashboard with daily rollup aggregation
- Multi-language support (23 Indian languages)
- Docker Compose production stack
Planned / In Progress
- Add an automated
pytesttest suite (the infrastructure inTestingConfigis ready - tests just need to be written) - Expand spider coverage to more State government portals (Rajasthan, Maharashtra, Karnataka)
- Add vector search as a first-class retrieval path alongside Elasticsearch BM25
- Support DOCX and XLSX ingestion in the scraper pipeline (the PDF ingestion code in
pdf_service.pyis mature; the other format parsers exist inrequirements.txtbut are not yet wired into a spider pipeline) - Add a document version history view showing how a GR or Act has been amended over time
- Implement a structured citation export (BibTeX / APA / Indian Legal Citation Style)
- Internationalise the admin panel beyond English
This project stands on the shoulders of several outstanding open-source projects and public data sources:
- Microsoft Phi-3.5-mini-instruct - the language model powering local AI inference (https://huggingface.co/microsoft/Phi-3.5-mini-instruct)
- sentence-transformers / paraphrase-multilingual-MiniLM-L12-v2 - multilingual embedding model enabling semantic search and similarity (https://huggingface.co/sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2)
- spaCy - industrial-strength NLP for entity extraction and keyword analysis (https://spacy.io/)
- Elasticsearch - the search engine at the heart of document retrieval (https://www.elastic.co/)
- Scrapy - the web scraping framework used to collect documents from government portals (https://scrapy.org/)
- Indian Kanoon - open legal database providing access to Indian court judgments (https://indiankanoon.org/)
- India Code - Ministry of Law and Justice's official database of Central Acts (https://indiacode.nic.in/)
- eGazette of India - official digital gazette for Central Government notifications (https://egazette.gov.in/)
- Gujarat General Administration Department - source for Gujarat Government Resolutions (https://gad.gujarat.gov.in/)
- ReportLab - PDF generation library (https://www.reportlab.com/)
- Flask and the entire Pallets ecosystem - web framework, forms, login, and compression (https://flask.palletsprojects.com/)
- llama-cpp-python - CPU-friendly GGUF inference binding (https://github.com/abetlen/llama-cpp-python)
- pywebpush - VAPID Web Push implementation (https://github.com/web-push-libs/pywebpush)
The project was created for a hackathon (repository name: ULAGIP-Hackathon). Author information was not explicitly included in the codebase. The domain referenced throughout the code is ulagip.in and the administrator contact email is admin@ulagip.in.
If you found ULAGIP useful - whether you are a developer wanting to extend it, a researcher wanting to use it, or someone curious about what is possible when you combine open-source AI with public legal data - reach out through GitHub Issues. The codebase is a genuine attempt to make India's legal infrastructure searchable and understandable for everyone, and contributions in any form are warmly welcome.