An unofficial, community-run chatbot for ElectionData.MY — an Agno agent that answers questions about Malaysian election data, backed by the public ElectionData.MY REST API and its data lake (candidate demographics, polling-station level results, voter rolls, and full historical trends going back to GE-01).
This is not maintained or endorsed by the ElectionData.MY team — it's a separate project built on their public API and data, kept here so anyone can self-host it. Bring your own API keys.
Try it live at http://localhost:8001/ after running it (see below) — a single-page chat widget
is included, no separate frontend needed.
- FastAPI backend, one Agno
Agent, no external database (in-process session history only — history lives as long as the server process runs) POST /chat— AG-UI-compatible streaming endpoint (text/event-stream)GET /— a self-contained, dependency-free HTML/JS chat widget that talks to/chat- Async tools against the public ElectionData.MY REST API (candidates, seats, parties, elections, by-elections, results)
query_data_lake— a read-only DuckDB SQL tool against the public Parquet data lake (lake.electiondata.my), for anything the REST API alone can't answer: candidate age/sex/ ethnicity, saluran (polling station) level results, voter rolls, cross-election analysis- Optional controlled UI components (
show_ui): cards, tables, alerts, and a candidate profile card colour-coded by coalition
Requires uv and Python 3.12.
make setup # uv sync + creates .env from .env.example if missingThen fill in .env:
OPENAI_API_KEY— your own OpenAI key (any account with API access; the agent defaults togpt-4o-mini, configurable viaOPENAI_MODEL)ELECTIONDATA_API_KEY— get one free at electiondata.my (an API key is required for the REST endpoints; the data lake itself needs no key)
make dev # runs at http://localhost:8001Open http://localhost:8001/ in a browser to chat. No separate frontend, no CORS setup needed —
the widget is served by this same app.
make docker-upRuns on port 8001 by default (see docker-compose.yml). Environment variables come from .env.
make test
make lintIf you'd rather build your own UI instead of using the bundled widget, POST /chat accepts a
minimal AG-UI-style body and streams AG-UI events back:
{
"threadId": "local-thread",
"messages": [{ "role": "user", "content": "Who won P.001 Padang Besar in GE-15?" }]
}Any AG-UI compatible client (e.g. @ag-ui/client's
HttpAgent) works directly against this endpoint.
query_data_lake runs whatever read-only SQL the agent writes. It's gated to SELECT/WITH
statements only (no DDL, no raw URLs — only the documented dataset names), voter-roll queries are
forced to LIMIT 10000 or less, and results are hard-capped at 200 rows server-side regardless of
the query. See src/electiondata_chatbot_api/services/datalake.py for the exact rules.