The SQL Alchemist is a local Business Intelligence assistant that turns natural language questions into DuckDB SQL over flight operations data.
It currently supports two interfaces:
- a terminal experience through
src/main.py(ormain.pyat the project root) - a Streamlit web app through
src/app.py
Both interfaces share the same analytics engine in src/core.py.
The project uses local LLMs via Ollama to generate SQL, applies fallback logic when model output fails, and provides interactive analytics for airline performance, disruption cost, and operational quality.
| Dashboard | Natural-language chat | Airline comparison |
|---|---|---|
![]() |
![]() |
![]() |
To refresh screenshots after UI changes:
pip install playwright && playwright install chromium
python3 scripts/capture_screenshots.pysql_alchemist/
├── data/
│ └── flights.csv
├── docs/
│ ├── images/ # README screenshots (PNG)
│ ├── DEPENDENCIES.md
│ ├── DEPLOYMENT.md
│ └── GITHUB_SETUP.md
├── notebooks/
│ └── main.ipynb
├── scripts/
│ ├── capture_screenshots.py
│ ├── enrich_flights_dates.py
│ └── docker-entrypoint.sh
├── src/
│ ├── core.py # shared BI engine (ChatBI, analytics, explanations)
│ ├── health.py # deployment health checks
│ ├── i18n.py # EN/PT UI strings
│ ├── main.py # CLI interface
│ └── app.py # Streamlit interface
├── tests/
│ ├── conftest.py
│ ├── test_core.py
│ ├── test_config.py
│ ├── test_health.py
│ └── test_i18n.py
├── .github/
│ └── workflows/
│ └── ci.yml
├── Dockerfile
├── docker-compose.yml
├── docker-compose.prod.yml
├── config.py
├── main.py # convenience CLI entry point
├── .env.example
├── .env.production.example
├── .gitignore
├── LICENSE
├── pyproject.toml
├── README.md
├── requirements.txt
└── requirements-dev.txt
| Module | Responsibility |
|---|---|
config.py |
Environment-based configuration (OLLAMA_HOST, DATA_PATH, model chain, costs) |
src/core.py |
DuckDB loading, Ollama SQL generation, fallback logic, watchdog, airline comparison, KPI helpers |
src/main.py |
Terminal UI with Rich (/dashboard, /compare, /suggest, chat) |
src/app.py |
Streamlit dashboards, charts, chat, CSV export |
src/health.py |
Startup and HTTP health checks for deployment |
src/i18n.py |
EN/PT localization for CLI and Streamlit |
notebooks/main.ipynb |
Lightweight experimentation that imports from src/core.py |
tests/ |
Automated tests for core, config, health, and i18n |
The engine loads data/flights.csv into an in-memory DuckDB table named flights, then:
- accepts a natural-language question
- asks a local Ollama model to generate SQL
- sanitizes and validates the SQL
- executes the query against DuckDB
- returns results in either CLI or Streamlit UI
- falls back to keyword-based SQL when model generation fails
This makes the project usable even when a model is unavailable, returns invalid SQL, or cannot be reached locally.
The CLI version in src/main.py provides a lightweight local chat workflow for asking flight-related questions directly in the terminal.
The Streamlit app in src/app.py extends the project with:
- model fallback chain support
- KPI and chart-based analytics
- chat-based natural language exploration
- business impact estimation
- watchdog quality checks
- airline-vs-airline comparison
- result explanation logic
- suggested prompts for faster interaction
- CSV export for dashboard and chat results
The main dataset is stored in:
data/flights.csv
The flights table is loaded from CSV and enriched at query time with derived analytics fields.
Source columns (from flights.csv):
flight_idairlineorigindestinationdeparture_date,arrival_date(YYYY-MM-DD)departure_time,arrival_time(HH:MM)latency_minutesstatus
Derived columns (computed when DuckDB loads the dataset):
route—origin → destination(e.g.CDG → JFK)departure_hour,departure_minute,arrival_hour,arrival_minute— parsed from time stringsdeparture_time_of_day—Morning,Afternoon,Evening, orNightscheduled_duration_minutes— scheduled block time between departure and arrival (overnight-aware)day_of_week,departure_month,departure_year— calendar analytics fromdeparture_date
Supported status values include:
On-TimeDelayedCancelled
The dataset has been expanded with more realistic records to improve aggregate analysis, filtering, routing comparisons, and seasonal patterns.
Regenerate calendar dates after replacing the CSV with:
python3 scripts/enrich_flights_dates.pyUsers can ask questions in plain English, and the app uses a local Ollama model to convert the question into a DuckDB SELECT query.
To improve reliability, generated SQL is sanitized and validated before execution. If generation fails or produces invalid output, the app falls back to intent-based SQL patterns such as average latency, cancellations, delays, or counts by status.
The web app includes:
- total flights, average latency, delayed flight KPIs
- latency visualizations
- estimated disruption cost by airline
- watchdog quality distribution
- filtered results table
- auto-charting for numeric chat results
The Streamlit version estimates disruption cost using configurable business rules:
- delay cost =
latency_minutes × delay_cost_per_minute - cancellation cost = fixed
cancellation_cost - total cost = delay cost + cancellation cost
These values can be changed in the sidebar of the app.
The app classifies records into operational quality groups:
ReliableReviewHigh Risk
This is based on airline-relative latency behavior using configurable sensitivity (relaxed, normal, strict) with percentile and standard-deviation thresholds.
The airline comparison view compares two airlines on a selected destination using:
- average latency
- on-time rate
- cancellation rate
- total disruption cost
- ranking metrics
This provides a direct route-level rivalry view for operational benchmarking.
Before running the project, make sure you have:
- Python 3.10 or later
- Ollama installed locally
- at least one local Ollama model pulled
- a valid
config.pyin the project root - the dataset available at the configured path
From the project root:
git clone https://github.com/PedroCarneiroMarques/the-sql-alchemist.git
cd the-sql-alchemist
python3 -m pip install -r requirements.txtFor development and tests:
python3 -m pip install -r requirements-dev.txtStart the Ollama server locally:
ollama servePull at least one model. Example:
ollama pull mistral:7bYou can also pull additional models used in the fallback chain, such as:
ollama pull phi4:14b
ollama pull qwen2.5-coder:14b
ollama pull deepseek-r1:8bConfiguration lives in config.py at the project root. Values can be overridden with environment variables:
| Variable | Default | Description |
|---|---|---|
DATA_PATH |
data/flights.csv |
Path to the flights dataset |
OLLAMA_HOST |
http://localhost:11434 |
Ollama API endpoint |
OLLAMA_TIMEOUT |
180 |
Request timeout in seconds |
DEFAULT_MODEL_CHAIN |
comma-separated model list | Balanced profile fallback order |
DEFAULT_MODEL_PROFILE |
balanced |
Default profile: fast, balanced, or accurate |
MODEL_PROFILE_FAST |
mistral:7b |
Model chain for the fast profile |
MODEL_PROFILE_ACCURATE |
larger models first | Model chain for the accurate profile |
DEFAULT_WATCHDOG_SENSITIVITY |
normal |
Watchdog level: relaxed, normal, or strict |
LOG_LEVEL |
INFO |
Logging verbosity: DEBUG, INFO, WARNING, ERROR |
LOG_TO_FILE |
true |
Write logs to logs/sql_alchemist.log |
UI_LOCALE |
en |
UI language: en or pt (Streamlit sidebar can override per session) |
APP_ENV |
development |
development or production (stricter validation in production) |
DEPLOYMENT_SECRETS_READY |
unset | Set to true in production after secrets are injected |
OLLAMA_API_KEY |
unset | Optional API key for authenticated Ollama endpoints |
DEFAULT_DELAY_COST_PER_MINUTE |
50 |
Delay cost per minute (€) |
DEFAULT_CANCELLATION_COST |
200 |
Fixed cancellation cost (€) |
Example:
cp .env.example .env
# edit .env as neededOr export variables directly:
export DATA_PATH="data/flights.csv"
export OLLAMA_HOST="http://localhost:11434"
export DEFAULT_MODEL_CHAIN="mistral:7b,phi4:14b,qwen2.5-coder:14b"Run from the project root:
python3 src/main.pyOr use the root entry point:
python3 main.pySet UI language with UI_LOCALE=pt or python3 main.py --lang pt.
In the terminal:
- ask questions in plain English
- use
/filter,/profile,/dashboard,/compare,/export,/suggest,/models, or/help - switch model profile:
/profile fast,/profile balanced,/profile accurate - adjust watchdog sensitivity:
/watchdog relaxed,/watchdog normal,/watchdog strict - quick comparison:
/compare AirlineA AirlineB Destination(alias:/wars) - type
quit,exit, orqto leave
Exported CSV files are saved under exports/ at the project root.
Run from the project root:
streamlit run src/app.pyDefault language comes from UI_LOCALE; users can also switch language in the Streamlit sidebar.
This matters because config.py is stored in the project root and the app expects the repository root to be part of the Python path.
The app will usually open at:
http://localhost:8501
Run the Streamlit app and Ollama together with Docker Compose:
docker compose up --build -dOpen:
http://localhost:8501
Pull at least one model into the Ollama container (first run only):
docker compose exec ollama ollama pull mistral:7bOptional models for balanced/accurate profiles:
docker compose exec ollama ollama pull phi4:14b
docker compose exec ollama ollama pull qwen2.5-coder:14bView logs:
docker compose logs -f app
docker compose logs -f ollamaStop the stack:
docker compose downExported CSV files and application logs are written to exports/ and logs/ on the host via volume mounts.
Before the Streamlit container starts, the entrypoint runs:
python -m src.health --startupThis validates configuration and confirms that DATA_PATH exists.
While the stack is running:
| Check | Command / URL |
|---|---|
| Streamlit HTTP health | http://localhost:8501/_stcore/health |
| Config + dataset | python -m src.health --startup |
| Streamlit + optional Ollama | python -m src.health --http --ollama |
Docker Compose configures an app healthcheck against the Streamlit endpoint. Override runtime settings by copying .env.example to .env and adjusting values to match the environment block in docker-compose.yml.
For production, see docs/DEPLOYMENT.md and docker-compose.prod.yml.
CI runs on every push/PR to main. If git push fails with a workflow scope error, follow docs/GITHUB_SETUP.md.
The Streamlit app currently includes:
- installed-model detection from local Ollama
- model profiles: fast, balanced, accurate (with optional custom chain)
- fallback model execution chain
- generated SQL preview
- results table rendering
- automatic charting for numeric chat results (bar, line, or pie based on data shape)
- suggested prompts
- business impact controls in the sidebar
- global airline filters
- route-level airline comparison
- in-session chat history (metadata-only storage; results reloaded from SQL on demand)
- CSV download buttons for filtered data, cost summaries, airline comparison, and chat results
Run the automated test suite from the project root:
python3 -m pytest tests/ -vThe tests cover SQL safety (including sqlparse guardrails), keyword fallback, few-shot prompts, watchdog logic, airline comparison, explanations, and CSV export helpers. They do not require a running Ollama instance.
CI runs automatically on GitHub Actions for Python 3.11 and 3.12 on every push and pull request to main.
Structured logs are written to stderr and, by default, to:
logs/sql_alchemist.log
Useful events include dataset loading, Ollama model detection failures, rejected SQL, model attempts, query timings, and keyword fallback usage. Control verbosity with:
export UI_LOCALE=pt
export LOG_LEVEL=DEBUG
export LOG_TO_FILE=falseScreenshots live in docs/images/ and are embedded at the top of this README.
| File | Description |
|---|---|
dashboard-overview.png |
KPIs, charts, watchdog, and cost analytics |
chat-analysis.png |
Natural-language query with SQL, table, and auto-chart |
airline-comparison.png |
Head-to-head airline comparison on a route |
Regenerate with python3 scripts/capture_screenshots.py (requires Playwright).
Examples:
- Which airlines have the highest average latency?
- How many flights were cancelled by airline?
- Show delayed flights ordered by latency.
- What is the distribution of flight statuses?
- Which routes have the highest average delay?
- What is the estimated total cost by airline?
- Which destinations have the most delayed flights?
- Which airlines have the best on-time performance?
To improve stability:
- model output is sanitized before execution
- only
SELECT/WITH ... SELECTqueries are accepted - table whitelist (
flightsonly) and forbidden keywords (UNION,DROP, etc.) - non-safe SQL is rejected
- invalid model output triggers keyword-based fallback SQL
- execution errors are surfaced in the UI
- result explanations attempt to summarize returned data safely
Recent improvements include:
- shared analytics engine in
src/core.py - removal of duplicated notebook logic
- safer explanation logic for result metrics
- CSV export from the Streamlit UI
- automated
pytestcoverage for core behavior
Make sure:
config.pyexists in the project root- you run the app from the repository root, not from inside
src/
Correct:
cd the-sql-alchemist
streamlit run src/app.pyCheck that DATA_PATH in config.py points to a real CSV file, for example:
"DATA_PATH": "data/flights.csv"Make sure Ollama is running:
ollama serveAlso verify the host in config.py:
"OLLAMA_HOST": "http://localhost:11434"List installed models:
ollama listIf needed, pull one:
ollama pull mistral:7bThis usually means:
- Ollama is not running
- the selected model is not installed
- the model returned invalid SQL
- the prompt was too ambiguous
The app will try the configured fallback chain and then use keyword-based SQL if needed.
Runtime dependencies are listed in requirements.txt and pyproject.toml. Development extras (including pytest) are in requirements-dev.txt (pip install -e ".[dev]").
Additional dependency notes are documented in docs/DEPENDENCIES.md.
Core runtime libraries: DuckDB, Ollama, Streamlit, Plotly, Pandas, Rich, and sqlparse. Development: pytest.
The notebook imports the production modules instead of duplicating them:
notebooks/main.ipynb
Use it to validate dataset loading, test fallback queries, and explore watchdog/cost outputs without launching the full CLI or Streamlit app.
The project currently includes:
- shared engine in
src/core.py - CLI interface in
src/main.py - Streamlit interface in
src/app.py - local LLM integration via Ollama
- DuckDB-powered local analytics
- fallback SQL behavior
- business impact estimation
- watchdog anomaly scoring
- airline rivalry comparison
- CSV export from the web UI
- automated tests in
tests/ - Docker Compose stack for Streamlit + Ollama
- Python
- DuckDB
- Streamlit
- Ollama
- Plotly
- Pandas
- Rich
- pytest
Possible next improvements:
- automated screenshot refresh in CI
- additional airline datasets
See the LICENSE file for project licensing details.


