A Streamlit analytics dashboard with an agentic RAG chat assistant, built for the RBC × BCCAI × SFU Beedie Agribusiness Analytics Hackathon 2026. It's built around GreenLeaf CEA, a hypothetical B.C. controlled-environment agriculture operator.
The audience is a mix of farmers, RBC lender staff, and technical judges, so every answer leads with numbers and cites where they came from.
python -m venv .venv
source .venv/bin/activate
pip install -e .The chat assistant calls the DeepSeek chat API for routing and answer synthesis.
Put your key in a .env file at the repo root (gitignored):
DEEPSEEK_API_KEY=sk-...Without a key the dashboard's data tabs still work; only the chat is disabled.
# 1. Build the derived data (parquet) the app reads from
python build_derived.py
# 2. Build the RAG index once (re-run whenever docs/greenleaf_corpus/ changes)
python app/rag/build_index.py
# 3. Launch the dashboard
streamlit run app/streamlit_app/main.pyThe app has four tabs: Overview, Operations, Financials, and Ask GreenLeaf (the chat assistant).
pytest tests/data/raw/— source CSVs and case PDF (read-only, never modified).data/external/— external BC benchmarks (YAML, one per benchmark).data/derived/— joined/feature-engineered parquet, built bybuild_derived.py(gitignored).docs/greenleaf_corpus/— the curated markdown knowledge base the RAG searches.
build_derived.py is the single source of truth for derived data.
The chat is agentic RAG: instead of always retrieving documents and stuffing them into one prompt, an LLM agent is given a set of tools and decides — turn by turn — which to call, looks at each result, and adjusts until it can answer. Document retrieval is just one of those tools.
question
│
▼
app/streamlit_app/chat_page.py the Streamlit chat UI (render_chat)
│
▼
app/chat/answer.py public entry point; runs the agent, then cleans
│ the final text (strips "Let me…"/"you could ask…")
▼
app/chat/agent.py ◀── THE AGENTIC LOOP ──▶ loops up to max_iterations:
│ DeepSeek picks tool(s) → run them → feed results back →
│ repeat or write the final cited answer
├── search_documents ──▶ app/rag/retrieve.py (semantic search = the "R" in RAG)
├── 10 analytics tools ─▶ app/chat/data_queries.py (pandas over the derived data)
└── query_data ────────▶ app/chat/agent.py (safe, flexible pandas escape hatch)
So the loop can: search the corpus, see the chunks, then call a data tool, look at the numbers, retrieve again with a better query if needed, and only then answer — all chosen by the model, not hard-coded. That autonomy is what makes it agentic RAG rather than plain RAG.
RAG itself is the classic two steps — index offline, retrieve at query time:
-
Index —
app/rag/build_index.pyReadsdocs/greenleaf_corpus/*.md, strips YAML frontmatter, chunks each doc into ~300-word passages on paragraph boundaries, embeds them with theall-MiniLM-L6-v2sentence-transformer (L2-normalized), and writesapp/rag/index_data/embeddings.npy+chunks.json. Run it once (and again after editing the corpus). -
Retrieve —
app/rag/retrieve.pyLoads that index and the embedding model once, embeds the query with the same model, and returns the top-k chunks by cosine similarity (a numpy dot product, since both sides are normalized)._search_topk()is the single swap point if you ever move to a vector DB (FAISS, Chroma, pgvector).
app/rag/ is only the retrieval half of RAG — build_index.py and retrieve.py.
The agentic work lives in app/chat/:
| File | Role |
|---|---|
app/chat/agent.py |
The agent loop — tool definitions + the gather→look→adjust loop that drives DeepSeek. This is the agentic RAG core. |
app/chat/data_queries.py |
The 10 analytics tools (pure pandas over the derived parquet). |
app/chat/answer.py |
Public entry point used by the UI; post-processes the final answer text. |
app/chat/router.py |
LLM key/.env loading + a legacy single-shot classifier (kept for reference; the agent loop in agent.py superseded it). |
app/rag/build_index.py |
Builds the corpus embedding index. |
app/rag/retrieve.py |
Semantic top-k retrieval over the index. |
The agent reaches retrieval via the search_documents tool in agent.py, which calls
retrieve(). So "index + retrieve" are the ingredients; agent.py is the cook.
app/streamlit_app/— Streamlit pages (main.pyshell + one module per tab).app/chat/— agentic chat: agent loop, data tools, answer entry point.app/rag/— corpus index builder + retrieval.analysis/,models/— exploratory analyses and trained model artifacts.docs/— supporting docs;docs/greenleaf_corpus/is the RAG source.tests/— pytest suite.build_derived.py— single source of truth for derived data.