A multi-agent AI chatbot over real European electricity market data — ask it something like "Was there a price spike in Germany in late January 2019?" and it doesn't just query the database, it runs statistical anomaly detection, checks correlated signals (wind, solar, demand) to explain why, and reports back with a verified, factual narrative plus a decision-support disclaimer.
Built as a third portfolio project specifically to demonstrate skills beyond text-to-SQL: real European data (relevant for EU/DACH-market job applications), a genuinely different ML technique (time-series anomaly detection, not just retrieval or querying), and EU-AI-Act-style transparency — every anomaly finding is deterministic and explainable, not an LLM guessing.
Built with LangGraph, the pipeline routes each question down one of two paths after a shared planning step:
- Planner — checks whether the question is in scope, classifies it as either a normal analytics question or an anomaly-detection question, and resolves conversational follow-ups using chat history.
- Analytics path (for "what/when/how much" questions) — generates SQL, runs it (validated to allow only
SELECT, blocking stacked queries and destructive keywords regardless of what the LLM produces), and verifies the result actually answers the question before answering — retrying up to 3 times on failure. - Anomaly path (for "was there anything unusual" questions) — this is the differentiator:
- Computes a rolling z-score per hour against a trailing 30-day baseline that only ever looks backward (never future data — the same leakage discipline used in the KitchenAid forecasting project), flagging hours more than 2.5 standard deviations from baseline.
- For every flagged hour, runs a deterministic rule-based check against correlated signals (e.g. was wind + solar generation unusually low relative to its own 30-day baseline at that hour? was demand unusually high?) to attach a plain-English likely cause — no LLM judgment involved in the detection or the reasoning, only in the final phrasing.
- An LLM writes a short narrative from the computed findings, which is then checked number-by-number against the real findings JSON; if it invents a figure, it's forced to regenerate.
- The response always includes a disclaimer: "This is an automated, decision-support signal, not financial or trading advice — verify before acting."
Every response includes a "Show details" panel — SQL, or the full anomaly report (flagged hours, z-scores, likely reasons) — so nothing is a black box.
| Layer | Tech |
|---|---|
| Backend | Python, FastAPI, LangGraph |
| Anomaly detection | pandas rolling statistics, rule-based root-cause classification |
| LLM | Qwen 3.6 35B (via OpenRouter) |
| Database | PostgreSQL (Railway) |
| Frontend | React + Vite |
| Backend hosting | Railway |
| Frontend hosting | Vercel |
Open Power System Data (sourced from ENTSO-E, the European transmission system operators' association) — hourly electricity load, day-ahead price, and generation (solar, onshore wind, offshore wind) for 13 European countries (Germany, France, Spain, Italy, Netherlands, Poland, Austria, UK, Sweden, Norway, Denmark, Ireland, Switzerland), 2018 onward, ~880K rows in a long/EAV schema (energy_readings(ts, country_code, metric, value)).
Not every country has every metric, and price data reflects a single representative bidding zone where the real market has several (e.g. Italy's price is the IT_NORD zone) — a real simplification in European market data, documented rather than hidden.
| Endpoint | Method | Description |
|---|---|---|
/ |
GET | Service info |
/health |
GET | Health check |
/chat |
POST | { question, history[] } → { answer, in_scope, query_type, sql?, verified?, anomaly_report? } |
Interactive docs available at /docs (FastAPI auto-generated Swagger UI).
python -m venv venv
source venv/Scripts/activate # Windows Git Bash; use venv\Scripts\activate on cmd
pip install -r requirements.txt
# create .env with:
# DATABASE_URL=postgresql://...
# OPENROUTER_API_KEY=your_key_here
uvicorn app.main:app --reload --port 8000cd frontend
npm install
npm run devSet VITE_API_URL in frontend/.env to point at your running backend (e.g. http://localhost:8000).
├── app/ # FastAPI application (routes)
├── src/ # Agent pipeline: db connection, text-to-SQL engine, anomaly detection, LangGraph agent
├── scripts/ # Data loading (create_tables, load_data) and manual test script
├── frontend/ # React + Vite chat UI
└── Procfile # Railway start command
MIT