An agent that searches, explains, compares, and saves NBA playoff shot quality insights using MongoDB Atlas as its memory and semantic retrieval layer.
Hackathon track: MongoDB. Built with Gemini + Google Cloud Agent Builder
- MongoDB Atlas (Vector Search + MCP Server).
License: MIT — see LICENSE.
Live demo: coming soon — replace with deployed Vercel URL.
Demo video (≈3 min): coming soon — replace with YouTube link.
Run it locally in replay mode (no MongoDB, no agent credentials required — the canned response ships with the repo so judges can see the full demo without any setup):
cd frontend
npm install
npm run devOpen: http://localhost:3000/?replay=brunson-toughest
The agent panel auto-populates immediately:
- The user prompt is pre-filled in the editorial display-input
- The agent answer is rendered in body type
- The actual MongoDB aggregation pipeline that ran is visible in mono
- 5 evidence shots (Brunson's toughest playoff threes by xFG)
- 3 similar shots from Atlas Vector Search (Curry, Edwards, SGA)
- The saved scouting report appears in the saved-reports list
- A
▶ replay · brunson-toughestchip marks the page as a canned response
This replay path is the demo we record on. Live mode (no ?replay=) calls
the real Mongo + Agent Builder stack.
Type something like:
find Brunson's toughest made threes in the playoffs and save a scouting report
The agent:
- Calls a MongoDB aggregation pipeline that filters made 3-pt shots by player and sorts ascending by xFG (lowest xFG = hardest shot).
- Returns the top 5 with the actual pipeline JSON shown to the user.
- Generates a Gemini-written scouting report.
- Inserts the report into the
reportscollection. - Surfaces 3 semantically similar shots via Atlas Vector Search over shot summary embeddings.
Everything is real: real shot data, real xFG model, real aggregations, real embeddings, real Mongo writes. The frontend renders the executed query so judges can verify the agent is acting on the database, not narrating.
┌───────────────────┐ ┌──────────────────────┐ ┌────────────────┐
│ Next.js (Vercel) │ ───▶ │ /api/agent (BFF) │ ───▶ │ Gemini agent │
│ AgentPanel.tsx │ ◀─── │ captures tool_calls │ ◀─── │ Agent Builder │
└───────────────────┘ └──────────────────────┘ └────────┬───────┘
│
tools call ▼
┌──────────────────────────────┐
│ MongoDB Atlas │
│ - shots, players, reports │
│ - vector index on summaries │
└──────────────────────────────┘
The BFF route is the single integration point. It captures tool-call traces from the agent response and forwards them to the frontend so the UI can render the actual MongoDB pipeline that ran.
frontend/lib/agent-tools.ts is the single source of truth for the four
agent tools — queryShots, runAggregation, vectorSearchShots, and
insertReport. It exports both an OpenAPI schema for Agent Builder
registration and executable handlers for the BFF route. No drift possible.
The BFF supports ?replay=<session> mode that returns a frozen response
captured from a previously executed live run, so a 3-minute demo video is
recordable without any live-API risk.
The repo ships with make targets so the whole flow is five commands. The
order matters — don't skip the smoke test, it catches 95% of setup mistakes
before they cost you twenty minutes of import time.
- Create a free Atlas cluster. Project name
ShotSense Scout, M0 free shared cluster. Copy the connection string when prompted. - Create database + collections named exactly:
(You can skip this — the import will create them on first write.)
Database: shotsense Collections: shots, players, reports, agent_memory - Add a database user with read+write on the
shotsenseDB. Whitelist your IP (or0.0.0.0/0for hackathon dev). - Fill
.env:Then editcp .env.example .env
.env:MONGODB_URI=mongodb+srv://<user>:<pw>@<cluster>.mongodb.net/?retryWrites=true MONGODB_DB=shotsense GEMINI_API_KEY=<from https://aistudio.google.com/app/apikey> - Create the Vector Search index (Atlas UI → Atlas Search → Create
Search Index → JSON Editor). Do this AFTER
make importso the field exists:TheIndex name: shot_summary_vector_index Collection: shotsense.shots Type: Vector Search Field: summary_embedding Dimensions: 768 Similarity: cosinevectorSearchShotsagent tool reads from this exact index name.
make install # 1. Python deps into .venv (one-time)
make smoke # 2. Validate URI + auth + IP allowlist
make import # 3. Insert 10,503 shots + 217 players into Atlas
make smoke # 4. Confirm shots collection populated
# --- now create the Vector Search index in Atlas UI (step 5 above) ---
make embeddings # 5. Generate Gemini embeddings (can stop early — see below)
make smoke # 6. Confirm partial or full embedding coverage
make dev # 7. Run Next.js dev server
make replay # 8. Open the replay-mode demo URLEvery make smoke is read-only and safe to run as often as you want. Each
script also has friendly errors if MONGODB_URI, MONGODB_DB, or
GEMINI_API_KEY is missing — the message tells you exactly what to do next.
make embeddings reads EMBEDDING_PROVIDER from .env:
| Provider | Pros | Cons |
|---|---|---|
gemini (default) |
Best embedding quality; no local resource use. | Free-tier quota (~100 RPM, daily token caps) often caps a full run partway through 10,503 shots. |
local_sentence_transformers |
No API quotas, no key, no internet after first download. Embeds all 10,503 shots in ~10-15 min on a modern CPU. | One-time ~420MB model download (sentence-transformers/all-mpnet-base-v2); larger pip install (torch). Different vector space from Gemini. |
Both produce 768-dim vectors so the same Atlas Vector Search index works.
Switching providers requires a full re-embed because the two vector spaces don't align — mixing them silently breaks similarity search:
# Switch in .env first:
# EMBEDDING_PROVIDER=local_sentence_transformers
# EMBEDDING_OVERWRITE=true
make embeddings # rebuilds all 10,503 from scratch with the local model
make smoke
# Then unset EMBEDDING_OVERWRITE=false so future runs only fill gaps.The BFF route detects which provider produced the corpus (each shot is
tagged with embedding_provider at write time). If the corpus provider
doesn't match the runtime provider (currently always gemini), the agent
skips Vector Search and uses the structured Mongo heuristic. So:
| Corpus provider | Runtime | Similar-shot beat uses |
|---|---|---|
gemini |
gemini |
Atlas Vector Search (real semantic similarity) |
local_sentence_transformers |
gemini |
Heuristic only (real Mongo aggregation over distance / zone / xfg) |
Both render real, readable Mongo pipelines in the agent panel. To enable true vector search with the local provider, the BFF would need a matching local embedding service — out of scope for the hackathon.
Gemini free-tier quotas (100 RPM, daily token caps) often make embedding all 10,503 shots impractical. The demo is designed to work with whatever coverage you get:
| Embedded shots | Similar-shot beat |
|---|---|
0 to 99 |
Heuristic mode — Mongo aggregation ranks candidates by shot_distance, shot_zone, action_type, is_three_point, and xfg similarity to the seed shot. No embeddings needed. Real pipeline JSON still renders. |
100 to total-1 |
Partial vector mode — Atlas Vector Search over whatever's been embedded. Candidate count auto-scales. |
total |
Full vector mode — vanilla Atlas Vector Search across the corpus. |
Both modes return real shots from real MongoDB aggregations, and the agent
panel renders the actual pipeline that ran. Stop make embeddings whenever
you hit a quota wall — the existing checkpoint resumes cleanly later, and
the demo works in the meantime.
The live prompt find the toughest clutch makes and save a scouting report
works against any of the three modes because the primary runAggregation
insertReportflow doesn't depend on embeddings at all.
Placeholder — wiring up to live Gemini Agent Builder is the next milestone.
The integration seam is a single function:
callAgentBuilder() in frontend/app/api/agent/route.ts.
Until AGENT_BUILDER_ENDPOINT is set in the environment, the BFF falls back
to a deterministic stub that produces a plausible tool-call plan for any
prompt mentioning a player and the word "tough/hard/difficult/clutch".
To go live:
- Register the four tools from
toolsAsOpenAPI()(infrontend/lib/agent-tools.ts) with your Agent Builder agent. The handlers in the same file are what the BFF executes on the agent's behalf — same parameters on both sides, so drift is impossible. - Decide MCP transport. Three options were spike'd in plan review:
- A — Self-host the official MongoDB MCP Server on Cloud Run with HTTP transport. Cleanest "uses MCP server" story.
- B — Thin FastAPI shim with OpenAPI tools (skips MCP protocol).
- C — Atlas Data API directly (skips a server entirely). The right choice depends on the partner-track rules wording for MongoDB MCP Server compliance.
- Set
AGENT_BUILDER_ENDPOINTin.envto the resulting agent URL.
nba_shot_quality/ XGBoost xFG model, features, evaluation
scripts/
import_to_mongodb.py one-shot import of shots → Atlas
build_embeddings.py batched Gemini embeddings → vectorSearch index
export_for_frontend.py static JSON for the existing dashboard
frontend/
app/page.tsx Hero → AgentPanel → existing sections
app/api/agent/route.ts BFF (captures tool_calls, replay mode)
components/AgentPanel.tsx the agent UI section (locked design spec)
lib/agent-tools.ts single source of truth for agent tools
lib/replay-samples/ checked-in demo sessions for ?replay=
tests/ Python tests for the model + data layer
- Frontend: Next.js 15 (App Router), React 19, TypeScript, Tailwind v4, Motion, Three Fiber for 3D shot replays, Zod for tool schemas.
- Agent: Google Cloud Agent Builder + Gemini.
- Data layer: MongoDB Atlas with Vector Search.
- Embeddings: Gemini
gemini-embedding-001(768 dims, cosine, viagoogle-genai). - Model: XGBoost xFG model trained on playoff shot tracking data
(pre-existing artifact from the
nba_shot_quality/Python package).
MIT — see LICENSE. Open source, hackathon-track compliant.