AI-powered web application that makes Stouffville's municipal bylaws accessible and understandable to residents and municipal officers through natural conversation.
Started as a community learning experience about AI and how we can elevate residents in their knowledge on AI. Soon, it took a rebirth of learning by doing.
Traditional municipal bylaw repositories present significant accessibility barriers. The Stouffville document center, while functional, lacks semantic search capabilities, natural language understanding, and fails to clearly indicate bylaw status—whether active, expired, or revoked. Legal language remains opaque to residents, and navigating complex cross-references requires specialized knowledge.
The transformation of 9,000+ static PDF documents into an AI-accessible knowledge base involved a sophisticated multi-stage pipeline:
-
Structured Data Extraction: Leveraging the Google Gemini API through
BatchParseAndExtractBylawPDFs.py, each PDF undergoes semantic parsing to extract hierarchical metadata—bylaw numbers, years, types, legal topics, referenced legislation, entities, locations, and conditions—according to a predefined JSON schema. -
Status Intelligence Layer: A series of specialized analyzers determine bylaw validity:
bylaw_expiry_analyzer.pyidentifies time-limited bylaws that have naturally expiredbylaw_revocation_analysis.pydetects explicit revocation relationships between bylawsprepare_final_json.pyenriches the dataset with comprehensive status metadata
-
Vector Embedding and Storage: The system initializes ChromaDB using Voyage AI's
voyage-3-largeembedding model, creating high-dimensional vector representations that capture semantic relationships between bylaw concepts. This enables intelligent retrieval beyond keyword matching. -
Retrieval-Augmented Generation Architecture: The Flask backend implements a RAG pattern where:
- ChromaDB performs semantic search with real-time bylaw status filtering
- Retrieved bylaws pass through layered Gemini AI processing
- Legal language transforms into accessible explanations with proper citations
- XML formatting preserves hyperlinked references to source documents
This implementation transcends traditional search paradigms, enabling context-aware natural language interaction with complex legal documents while maintaining document provenance and status awareness.
This project uses Docker to create a consistent development environment. The setup includes a Python Flask backend that can be easily deployed and tested.
- Docker installed on your machine
- Docker Compose (included with Docker Desktop on Windows/Mac)
- Git for version control
- Google Gemini API key (for AI capability)
- Voyage AI API key (for vector embeddings)
-
Clone the repository:
git clone https://github.com/Cadence-GitHub/Stouffville-By-laws-AI.git cd Stouffville-By-laws-AI -
Set up environment variables: Create a
.envfile in the root directory with the following:GOOGLE_API_KEY=your_gemini_api_key_here VOYAGE_API_KEY=your_voyage_api_key_here -
Build the backend Docker image:
# Make the build script executable if needed chmod +x build-backend.sh # Build the backend image ./build-backend.sh
-
Start the backend service using Docker Compose:
docker-compose up
Or run it in detached mode:
docker-compose up -d
-
Access the backend API:
- The API will be available at http://localhost:5000
- Test the API with:
curl http://localhost:5000/api/hello - Access the demo web interface at http://localhost:5000/api/demo
- Access the public demo interface at http://localhost:5000/public-demo
- View specific bylaws at http://localhost:5000/static/bylawViewer.html?bylaw=BYLAW-NUMBER
- Record voice questions via the "Record Voice Question" button on the demo page (requires HTTPS and cert.pem/key.pem in the project root for microphone access)
In production, this application is deployed behind an NGINX reverse proxy which handles SSL termination and serves the application over HTTPS. While the Flask application has built-in SSL capability for local development and testing (using cert.pem and key.pem), the production environment uses NGINX for better performance and stability with high traffic loads.
-
Backend Files: The backend code is in the
backend/directorymain.py: Main Flask applicationapp/: Application package containing templates, prompts, and ChromaDB integrationrequirements.txt: Python dependenciesDockerfile: Container configuration
-
Database Files: The by-laws database is in the
database/directoryparking_related_by-laws.json: Contains by-laws related to parking regulationssearch_bylaws.py: Utility script for searching and extracting by-laws by keywordinit_chroma.py: Script to initialize ChromaDB with by-laws datachroma-data/: Directory containing ChromaDB vector database filesAI-generated_Q&A.json: Contains sample questions and answers for autocompleteingest_questions.py: Script to ingest questions into ChromaDB for autocomplete functionality
-
Static Assets: Frontend assets are in the
backend/app/static/directorydemo.cssanddemo.js: Styling and functionality for the demo interfacepublic_demo.css,public_demo.html, andpublic_demo.js: Simple public-facing demo interfacebylawViewer.html,bylawViewer.css, andbylawViewer.js: Bylaw viewer interface
-
Live Code Changes:
- The backend directory is mounted as a volume in the container
- The database directory is mounted as a volume in the container
- Changes to Python files will be reflected immediately when you refresh (Flask debug mode is enabled)
-
Managing Docker Containers:
- View running containers:
docker-compose ps - View logs:
docker-compose logs -f - Stop containers:
docker-compose down
- View running containers:
-
Rebuilding After Dependency Changes:
- If you modify
requirements.txt, you'll need to rebuild the image:./build-backend.sh docker-compose up -d
- If you modify
GET /api/hello: Returns a greeting message in JSON formatPOST /api/ask: Processes AI queries about bylaws- Requires JSON with a 'query' field
- Always uses 'gemini-mixed' model for optimal response generation
- Automatically performs enhanced search by transforming queries
GET /api/bylaw/<bylaw_number>: Retrieves full data for a specific bylaw- Intelligently handles different format variations of bylaw numbers
- Returns complete metadata and content for the requested bylaw
GET /api/demo: Returns a simple web interface for testing the AI functionalityPOST /api/demo: Processes form submissions from the demo interfacePOST /api/autocomplete: Returns autocomplete suggestions for partial queries- Requires JSON with a 'query' field containing the partial query text
- Returns semantically similar questions when the query is at least 3 characters long
POST /api/voice_query: Processes a base64-encoded audio recording for bylaw questions.- Request JSON:
{ "audio_data": "<base64-encoded audio>", "mime_type": "<audio MIME type>" } - Response:
{ "transcript": "<transcribed question or NO_BYLAW_QUESTION_DETECTED>" }or{ "error": "<error message>" }
- Request JSON:
GET/POST /tts-stream: Streams text-to-speech audio using Gemini Live API- GET: Uses
?text=query parameter for the text to convert to speech - POST: Uses JSON body with
textfield for the text to convert to speech - Streams raw PCM audio data (24kHz, 16-bit, mono) with JSON header containing format information
- Uses Google's Gemini Live API with "Iapetus" voice for natural-sounding speech
- GET: Uses
GET /public-demo: Serves a simplified public-facing demo interface
This project uses Google's Gemini AI models through the LangChain framework. Make sure your .env file contains a valid Google API key to enable AI functionality.
The application leverages ChromaDB (version 0.6.3) for vector search with Voyage AI embeddings to efficiently retrieve relevant by-laws based on semantic similarity. The system uses a direct similarity search with filtering for active bylaws only, and removes unnecessary metadata fields from results.
Available Gemini models (in developer tools):
- gemini-mixed (uses best model for each query stage, default for the public API)
- gemini-2.0-flash-lite (fastest, lowest cost)
- gemini-2.0-flash (balanced speed/quality)
- gemini-2.5-flash (best reasoning)
When using the gemini-mixed option:
- Query transformation uses gemini-2.0-flash
- First query (bylaws) uses gemini-2.5-flash
- Second query (layman's terms) uses gemini-2.0-flash
Each prompt type uses a specific temperature setting for optimal results:
- Bylaws prompt: 0.0 (consistent, deterministic outputs)
- Layman's terms prompt: 0.7 (more creative, natural language)
- Enhanced search prompt: 0.2 (slightly varied outputs while maintaining accuracy)
Dependencies for AI integration:
- langchain
- langchain-google-genai
- google-generativeai
- google-genai (for TTS streaming via Gemini Live API)
- python-dotenv
- chromadb
- langchain-chroma
- langchain-voyageai
- tiktoken
- Query Logging System: Comprehensive logging of all user queries including original query, transformed query, retrieved bylaws, timing metrics, and response data to a JSON file for analysis and improvement.
- Public Demo Interface: A simplified, user-friendly interface for the public to query bylaw information, with a clean design and dark mode support.
- Enhanced Search: Always enabled in the API, transforms user queries into formal, bylaw-oriented language to improve semantic search results, combining both original and transformed search results to maximize retrieval relevance.
- Token Counting and Cost Calculation: Tracks token usage for both input and output, calculating costs based on model-specific pricing to provide transparency about API usage.
- Bylaw Status Filtering: The system allows filtering bylaws by status (active or inactive) through a dropdown selection, improving user control over search results.
- Direct Vector Search Filtering: Active/inactive bylaw filtering is performed directly in the vector search query, improving efficiency.
- Inactive Bylaw Handling: Special prompt template for inactive bylaws that clearly communicates their non-active status and includes specific reasons why they are no longer in effect, using the "whyNotActive" metadata field.
- Layman's Terms Conversion: After retrieving relevant bylaws, a second prompt transforms the technical legal language into plain, everyday language without bylaw references, making information more accessible to residents.
- Comparison Mode: Option to display both versions of the answer (technical with bylaw references and layman's terms) for comparison.
- Model Selection: Users can select which Gemini model to use based on their requirements for speed, cost, and quality.
- Performance Metrics: The demo interface displays detailed timing information showing how long each step takes (by-law retrieval, first prompt execution, second prompt execution).
- Bylaw Limit Selection: In the demo interface, users can choose how many relevant bylaws to retrieve (5, 10, 15, or 20) for their queries.
- Interactive Bylaw Viewer: A dedicated interface to view complete bylaw information with rich formatting, dark mode support, and detailed metadata display.
- Direct Bylaw Linking: AI responses include hyperlinks to specific bylaws that open in the bylaw viewer.
- Intelligent Bylaw Number Matching: The system can match bylaw numbers even with different formatting variations (spacing, dashes, etc.).
- Visual UI Improvements: Enhanced demo interface with better layout and formatting options.
- Bug Reporting System: Each answer type includes a "Problem? Log a bug!" button that automatically captures query details, model information, retrieved bylaws, timing data, and the response content, formatting it as Markdown for clear reporting in GitHub Issues.
- Autocomplete Feature: The search interface provides intelligent autocomplete suggestions based on previously stored questions when the user types at least 3 characters, finding semantically similar questions using ChromaDB's vector search.
- Voice Query Recording: Allows users to record voice questions directly in the interface:
- Integrated microphone button in the search input field
- Popup interface for recording control with start/stop buttons
- Visible recording indicator to show when recording is active
- Automatic transcription of voice to text using Google's speech recognition
- Automatic population of the transcribed question in the search field
- Built-in timeout (30 seconds) to prevent excessively long recordings
- Requires HTTPS connection for browser security requirements
- Text-to-Speech (TTS) Streaming: Converts AI responses to natural-sounding speech audio using Google's Gemini Live API. See
backend/app/TTS_README.mdfor complete technical documentation and implementation details.
The application includes an interactive bylaw viewer that:
- Displays detailed information about specific bylaws in a user-friendly format
- Supports direct linking to bylaws from AI responses using hyperlinks
- Can open bylaws in a sidebar without leaving the main interface
- Features a dark mode toggle for better readability
- Intelligently formats:
- Tables and lists from bylaw content
- Location addresses with Google Maps links
- Links to original PDF documents
- Formatted text with proper spacing and line breaks
- Shows comprehensive metadata including:
- Bylaw number, type, and year
- Layman's explanation in simple terms
- Key dates and information
- Conditions and clauses
- Legal topics and related legislation
- Entity and designation information
- And many more fields when available
To initialize the ChromaDB vector database with by-laws data:
- Make sure the ChromaDB container is running (
docker-compose up -d) - Run the initialization script:
cd database python init_chroma.py
To initialize the questions database for autocomplete functionality:
- Make sure the ChromaDB container is running (
docker-compose up -d) - Run the questions ingestion script:
cd database python ingest_questions.py
- Create a feature branch from main
- Make your changes
- Submit a pull request
- Frontend development (React application)
- Expanded AI training on Stouffville bylaws
- Advanced query processing
The Stouffville By-laws AI application follows a modular architecture designed for scalability and maintainability. Below is a diagram illustrating the system components and their interactions:
graph TB
%% External services
GoogleAPI["Google Generative AI API"]
VoyageAPI["Voyage AI API"]
%% Docker containers
subgraph Docker["Docker Environment"]
Backend["Backend Container"]
ChromaDB["ChromaDB Container"]
end
%% Components in Backend
subgraph BackendComponents["Backend"]
FlaskApp["Backend App (main.py)"]
ChromaRetriever["ChromaDB Retriever (chroma_retriever.py)"]
GeminiHandler["Gemini Handler (gemini_handler.py)"]
PromptsModule["Prompts Module (prompts.py)"]
TokenCounter["Token Counter (token_counter.py)"]
Demo["Web Demo (templates/demo.html)"]
PublicDemo["Public Demo (static/public_demo.html)"]
BylawViewer["Bylaw Viewer (static/bylawViewer.html)"]
Autocomplete["Autocomplete (static/demo.js)"]
TTSHandler["TTS Handler (gemini_tts_handler.py)"]
%% Subgraph for API Calls
subgraph APICalls["API Endpoints"]
API1["/api/ask (POST)"]
API2["/api/demo (GET/POST)"]
API3["/api/hello (GET)"]
API4["/api/bylaw/ (GET)"]
API5["/api/autocomplete (POST)"]
API6["/public-demo (GET)"]
API7["/tts-stream (GET/POST)"]
end
end
%% Data Processing Components
subgraph DataProcessing["Data Processing"]
PrepareByLaws["Prepare JSON Bylaws (prepare_json_bylaws_for_db.py)"]
SearchBylaws["Search Bylaws (search_bylaws.py)"]
InitChroma["Initialize ChromaDB (init_chroma.py)"]
BylawExpiryAnalyzer["Bylaw Expiry Analyzer (bylaw_expiry_analyzer.py)"]
BylawRevocationAnalysis["Bylaw Revocation Analysis (bylaw_revocation_analysis.py)"]
IngestQuestions["Ingest Questions (ingest_questions.py)"]
end
%% Data Storage
ByLawsData[("By-laws JSON Data")]
ChromaDBData[("ChromaDB Vector Store")]
QuestionsData[("Questions JSON Data")]
%% Connections between components
PrepareByLaws --> ByLawsData
SearchBylaws --> ChromaDB
FlaskApp --> GeminiHandler
FlaskApp --> ChromaRetriever
FlaskApp --> Demo
FlaskApp --> PublicDemo
FlaskApp --> TokenCounter
ChromaRetriever --> ChromaDB
ChromaRetriever <--> BylawViewer
ChromaRetriever <--> Autocomplete
GeminiHandler --> PromptsModule
GeminiHandler --> GoogleAPI
TokenCounter --> GeminiHandler
InitChroma <--> VoyageAPI
InitChroma --> ByLawsData
InitChroma --> ChromaDB
IngestQuestions --> QuestionsData
IngestQuestions --> ChromaDB
IngestQuestions <--> VoyageAPI
API1 --> FlaskApp
API2 --> FlaskApp
API3 --> FlaskApp
API4 --> ChromaRetriever
API4 --> BylawViewer
API5 --> ChromaRetriever
API5 --> Autocomplete
API6 --> PublicDemo
Demo --> BylawViewer
Demo --> Autocomplete
PublicDemo --> Autocomplete
%% Container connections
Backend --> ChromaDB
Backend -.-> BackendComponents
ChromaDB -.-> ChromaDBData
%% Data flow for initialization
ByLawsData --> InitChroma
QuestionsData --> IngestQuestions
%% User interactions
User(("User")) --> API1
User --> API2
User --> API3
User --> API4
User --> API5
User --> API6
User --> Demo
User --> PublicDemo
User --> BylawViewer
User --> Autocomplete
style GoogleAPI fill:#0CC0DF
style VoyageAPI fill:#0CC0DF
The system implements a Retrieval-Augmented Generation (RAG) architecture with these key components:
-
Data Flow Pipeline:
- Raw by-law documents are processed and stored as JSON
prepare_json_bylaws_for_db.pycreates subsets of bylaws by keyword for further processingbylaw_expiry_analyzer.pyidentifies expired bylaws based on their contentbylaw_revocation_analysis.pyidentifies bylaws that revoke other bylaws- Voyage AI generates embeddings for by-law documents using the
voyage-3-largemodel - For the autocomplete questions collection, embeddings are generated using the
voyage-3-litemodel - ChromaDB indexes embeddings for efficient semantic retrieval using HNSW algorithm
- Sample questions and answers are ingested using
ingest_questions.pyto enable autocomplete functionality - When a query is received, relevant by-laws are retrieved and passed to Gemini AI
- The API always enables enhanced search, transforming the query into legal language and performing two searches
- Users can filter bylaws by status (active or inactive) through a dropdown selection
- Gemini generates two different responses:
- Complete response with bylaw references (includes XML tags that are converted to HTML links)
- Layman's terms response with simplified language and no bylaw references
-
Backend Core:
- Flask application handles HTTP routing and request processing
- ChromaDB Retriever manages vector database interactions and directly filters for active bylaws during retrieval
- Gemini Handler orchestrates AI model interactions with multiple model options
- Prompts Module contains templates that structure AI responses, with specialized templates for inactive bylaws
- Token Counter calculates token usage and associated costs
- Public Demo provides a simplified, user-friendly interface for public users
- Bylaw Viewer provides an interactive interface to explore complete bylaw information
- Autocomplete feature provides intelligent suggestions as users type their queries
-
Database Tools:
init_chroma.pyconverts JSON bylaws into vector embeddings stored in ChromaDBsearch_bylaws.pyallows semantic search, keyword search, or combinations of bothingest_questions.pyloads sample questions into a "questions" collection in ChromaDB- Vector search is optimized with configurable HNSW parameters (M, construction_ef, search_ef)
- Bylaw retrieval by number supports multiple format variations for flexible matching
-
External Services Integration:
- Google Generative AI provides LLM capabilities via multiple Gemini models (2.0-flash-lite, 2.0-flash, etc.)
- Voyage AI supplies high-quality text embeddings for semantic search
-
User Interface Options:
- Public demo interface with a clean, modern design and dark mode support
- Developer web demo interface with model selection and bylaw limit options
- Enhanced search option for improved semantic retrieval
- Intelligent autocomplete that suggests similar questions as users type
- Token usage and cost information
- Performance metrics showing timing information for each processing step
- Option to compare all three versions of the answer (complete, filtered active only, and layman's terms)
- Interactive bylaw viewer for exploring specific bylaws in detail
- Sidebar integration that allows viewing bylaws without leaving the main interface
- One-click bug reporting that generates formatted GitHub issues with complete context information
This architecture ensures that the system can accurately respond to user queries about Stouffville by-laws by finding the most semantically relevant information and presenting it in a natural, conversational format.