Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

AskTheLedger

Ask a database a question — out loud. A voice-driven Speech-to-SQL system that lets non-technical users query a relational database in plain English and get back interactive charts.

Python SQL Server Streamlit Whisper Gemini License

Final project for the Data Management (Mod. B) module of the MSc Data Science programme at the University of Naples Federico II. Awarded 30/30.


Demo

AskTheLedger dashboard
The Streamlit dashboard — speak a question, watch the SQL and chart appear.

Log-scaled choropleth map
Auto-generated log-scaled choropleth for "how many customers are in each country?"

Log-scaled choropleth map


What it does

You ask a question by voice, upload, or typing. The system:

  1. Transcribes the audio locally with OpenAI Whisper.
  2. Generates a T-SQL query with Google Gemini, which is given the live database schema.
  3. Validates the query with sqlglot — only read-only SELECTs can reach the database.
  4. Executes it against Microsoft SQL Server.
  5. Visualizes the result: it picks a chart type from the shape of the data (world map, bar, line, scatter, or metric).

Everything happens in seconds, and there's a CSV export plus a session history.


Architecture

 Voice / Upload / Text
         │
         ▼
   Whisper (base.en, local)          ── audio → text
         │
         ▼
   Google Gemini 2.5 Flash            ── text → T-SQL (schema-aware)
         │
         ▼
   sqlglot validation                 ── SELECT-only safety gate
         │
         ▼
   SQL Server 2022                    ── runs query, returns rows
         │
         ▼
   Streamlit dashboard                ── table + auto-chart + CSV

Each stage is a separate module (pipeline/), so components can be tested and swapped in isolation. The pipeline is orchestrated by a single PipelineResult object that carries the transcript, generated SQL, resulting DataFrame, timings, and any error up to the UI.


Tech stack

Layer Technology
Speech recognition OpenAI Whisper (base.en), local
Text-to-SQL Google Gemini 2.5 Flash (via google-genai)
Database Microsoft SQL Server 2022
DB access pyodbc + SQLAlchemy
SQL safety sqlglot (T-SQL dialect)
Dashboard Streamlit
Visualization Plotly (choropleth, bar, line, scatter)
Data processing pandas
Runtime Python 3.11 · Windows 11

Data

Built on the UCI Online Retail dataset (Chen, 2015) — a real record of ~540,000 transactions from a UK online retailer, December 2010 to December 2011. Chosen for being real, citable, and rich enough to support meaningful joins and aggregations.

The raw file is cleaned by a documented set of rules (null customer IDs, cancellations, non-positive quantities and prices) — retaining 397,884 rows (73.4%) — then normalized into a five-table schema in third normal form:

  • countries (37) · lookup with region
  • customers (4,338) · linked to a country, with purchase totals
  • products (3,665) · stock_code key, description, price, derived category
  • invoices (18,532) · one row per transaction
  • invoice_items (387,841) · line items linking invoices ↔ products

Cleaning and normalization live in db/loader.py; the schema and keys are defined in db/01_setup_database.sql.


Design highlights

Schema-aware prompting. The prompt is built at startup from INFORMATION_SCHEMA and the system catalog views (schema_introspector.py), so Gemini always sees the real tables, columns, keys, and foreign-key links. If the schema changes, the prompt updates — no code edits needed.

SELECT-only safety gate. Every generated query is parsed by sqlglot in the T-SQL dialect and rejected if it isn't a single SELECT (or a CTE / subquery / union of them). A keyword blocklist also catches INSERT, DROP, EXEC, MERGE, GRANT, and — deliberately — INTO, so SELECT ... INTO can't create tables through a SELECT. Enforced in Python; independent of database permissions.

Retry with model fallback. Gemini calls retry with exponential backoff (2s, 4s), then fall back down a chain of alternate models (gemini-2.5-flashgemini-2.5-flash-litegemini-flash-latest) before reporting failure. Handles free-tier 503 spikes transparently.

Graceful degradation. The optional AI extras (plain-English SQL explanation, follow-up suggestions in enrich.py) return a (value, status) pair so the UI can show why an extra was skipped (ok, no_key, busy, error). If they fail, the core flow is never affected.

Auto-charting from result shape. auto_chart.py classifies each result column as an identifier (like customer_id) or a measure, then picks a visualization: a country + measure becomes a choropleth (log-scaled, because the UK dominates the data), a date + measure becomes a line, a category + measure becomes a bar with tilted labels, two measures become a scatter, a single value becomes a highlighted metric.


Running it locally

Prerequisites

  • Windows 10/11 (or adapt paths for Linux/macOS)
  • Python 3.11
  • SQL Server 2022 (Developer edition is free; Windows Authentication)
  • ODBC Driver 18 for SQL Server
  • ffmpeg on PATH (for Whisper)
  • A Google Gemini API key (free tier is enough)

Setup

# 1. Clone
git clone https://github.com/anibihakeem/AskTheLedger.git
cd AskTheLedger

# 2. Create a virtual environment
python -m venv .venv
.venv\Scripts\activate       # Windows
# source .venv/bin/activate   # macOS/Linux

# 3. Install dependencies
pip install -r requirements.txt

Configure

Create a .env file in the project root (copy from .env.example):

DB_SERVER=localhost
DB_NAME=Voice2Query
DB_DRIVER=ODBC Driver 18 for SQL Server
GEMINI_API_KEY=your_key_here

Build the database

In SSMS, run in order:

  1. db/01_setup_database.sql — creates the database, tables, primary keys, foreign keys.
  2. db/02_create_users.sql — creates the read-only application role.

Then populate it:

python db/loader.py

This downloads the UCI dataset (~22 MB), cleans it, and bulk-loads all 397,884 rows via fast_executemany in FK-safe order (~1–2 minutes).

Run

# Optional: sanity-check the backend
python pipeline/test_pipeline.py

# Launch the dashboard
streamlit run pipeline/app.py

Opens at http://localhost:8501.


Repository layout

AskTheLedger/
├── db/
│   ├── 01_setup_database.sql       # schema, keys, indexes
│   ├── 02_create_users.sql         # read-only role
│   ├── loader.py                   # download, clean, normalize, load
│   └── test_connection.py
├── pipeline/
│   ├── app.py                      # Streamlit dashboard
│   ├── config.py                   # single source of truth for settings
│   ├── schema_introspector.py      # live schema → prompt
│   ├── text_to_sql.py              # Gemini call + retry/fallback
│   ├── sql_executor.py             # sqlglot validation + pd.read_sql
│   ├── transcriber.py              # Whisper wrapper
│   ├── auto_chart.py               # shape-aware chart selection
│   ├── enrich.py                   # optional AI extras (fail-silent)
│   ├── pipeline.py                 # orchestrator + PipelineResult
│   └── test_pipeline.py            # seven representative test queries
├── docs/
│   ├── report.pdf                  # full project report
│   └── screenshots/                # dashboard images
├── .streamlit/config.toml          # theme
├── .env.example
├── requirements.txt
└── README.md

Example questions

The system is tested against a fixed set of representative questions (see pipeline/test_pipeline.py), each exercising a different SQL feature:

Question SQL features
Top 5 best-selling products by total quantity JOIN, SUM, GROUP BY, ORDER BY, TOP
How many customers are in each country? JOIN, COUNT, GROUP BY
Average order value by country JOIN, AVG, GROUP BY
The 10 customers who spent the most JOIN, SUM, GROUP BY, ORDER BY, TOP
Which product categories generate the most revenue? JOIN, SUM, GROUP BY, ORDER BY
How many invoices were created in December 2011? WHERE (date range), COUNT
Products that have never been sold LEFT JOIN / NOT IN subquery

Trade-offs and design choices

A few decisions worth calling out, since they shaped the project:

Pipeline architecture over end-to-end. End-to-end speech-to-SQL models exist (SpeechSQLNet, Wav2SQL) but require training data that isn't practical to gather for a course project. A modular pipeline is testable stage-by-stage and more interpretable, at the cost of potential error cascades between stages — which the schema-aware prompt mitigates by anchoring Gemini to real table and column names.

Whisper over Speechmatics. Whisper runs locally: free, offline-capable, and predictable latency. Speechmatics is a strong commercial cloud service with features like speaker diarization, but the cloud dependency and per-minute cost weren't justified for a single-speaker demo.

Gemini + schema-aware prompting over a dedicated text-to-SQL platform (e.g. WrenAI). A hosted general model with a good prompt reaches the required quality with far less infrastructure than standing up a dedicated platform, and integrates in a few lines of code.

SQL Server was a fit, not a superiority claim. Any mature relational database would have worked. SQL Server was chosen for native integration with the SSMS/T-SQL toolchain the course uses, plus the bulk-load and identity features the loader relies on. PostgreSQL would have been equally valid.


Limitations

  • Depends on the hosted Gemini API for text-to-SQL. Retry and fallback mitigate this, but don't eliminate the dependency.
  • English-only, to match the dataset. Multilingual support would require a translation layer.
  • Ambiguous questions can produce a reasonable but unintended reading — inherent to any natural-language interface.
  • Product categories are derived from descriptions by keyword matching, so occasional miscategorization is possible.
  • pd.read_sql loads the full result into memory. Fine for the analytical queries here (which aggregate down), but not a streaming solution for very large result sets.

Roadmap

  • Database-aware correction of transcription errors, to reduce cascade risk
  • Multilingual support via a translation front-end
  • Semantic caching of similar questions using embeddings
  • Cloud deployment for browser-only access
  • Self-hosted open-model fallback (Llama, Mistral) to remove the hosted-API dependency

Acknowledgements


License

MIT — see LICENSE.


Author

Hakeem Anibi LinkedIn · Email

About

Voice-driven Speech-to-SQL system that converts spoken questions into safe, read-only SQL queries and returns interactive charts. Built with Whisper, Gemini, SQL Server, and Streamlit.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages