Every ball. Three AI agents. One live broadcast.
π 1st place at GDG Raipur β Agentic Premier League Hackathon, May 24 2026
CricketPulse turns a Cricsheet ball-by-ball JSON into a live AI broadcast. The moment an event arrives off the Redis wire, three agents fire in parallel:
| Agent | Type | What it does |
|---|---|---|
| Win Probability | XGBoost ML | Predicts P(batting team wins) from 12 match-state features |
| Commentary | Groq LLM | Writes one Harsha Bhogle-style sentence per delivery |
| Alerts | Rules + LLM | Fires on wickets, fifties, centuries, sixes β LLM called only when needed |
All three results are fused into a single JSON payload and broadcast to every WebSocket client in real time. PostgreSQL gets a background log. The frontend updates without a page refresh.
Cricsheet JSON
β
βΌ
emitter.py ββpublishβββΆ Redis: match:events
β
[FastAPI subscriber]
β
event_processor.py
β
βββββasyncio.gather()βββββ
β β
to_thread(XGBoost) check_and_alert()
Win Probability Rules + Groq LLM
β β
βββββββββββββ¬βββββββββββββ
β
generate_commentary()
Groq LLM
β
WebSocketPayload
β
βββββββββββββββΌββββββββββββββ
β β β
WS broadcast Next.js UI create_task(Postgres)
(all clients) React app (fire-and-forget)
Critical design decisions:
asyncio.gather()β ML inference and alert check run concurrently, not sequentiallyasyncio.to_thread()β XGBoost inference offloaded to thread pool, event loop stays unblocked- Per-client
try/exceptinbroadcast()β one dead WebSocket never kills others - LLM fallback
"Great delivery!"β Groq rate limits or timeouts never crash the pipeline - DB logging is always
create_task()β never adds to broadcast latency
CricketPulse/
βββ Backend/
β βββ main.py # FastAPI app, lifespan, router registration
β βββ config.py # pydantic-settings, reads .env
β βββ database.py # async SQLModel engine + session factory
β βββ agents/
β β βββ win_probability.py # XGBoost inference, 12-feature match state
β β βββ commentary.py # Groq LLM, one sentence per ball
β β βββ alerts.py # Rule engine + conditional Groq call
β βββ core/
β β βββ event_processor.py # Orchestrator β gather, broadcast, log
β β βββ redis.py # Async pub/sub singleton
β β βββ ws_manager.py # WebSocket fan-out with fault isolation
β β βββ llm.py # Groq async wrapper + fallback
β βββ routes/
β β βββ health.py # GET /health
β β βββ match.py # state, history, inject, demo/wicket
β β βββ websocket.py # WS /ws
β βββ schemas/
β β βββ event.py # BallEvent (Pydantic v2)
β β βββ payload.py # WinProbResult, WebSocketPayload
β β βββ response.py # HealthResponse, MatchStateResponse, etc.
β βββ db/
β β βββ models.py # BallEventLog, LLMResponseLog (SQLModel)
β βββ emitter/
β βββ emitter.py # Cricsheet parser + Redis publisher
β
βββ Frontend/cricketpulse-frontend/
β βββ app/ # Next.js 16 App Router (React Server Components)
β β βββ layout.tsx # Root layout with metadata
β β βββ page.tsx # Home page
β β βββ globals.css # Global styles
β β βββ docs/ # Documentation pages
β βββ components/ # React components
β β βββ animations/ # Framer Motion animations (six, wicket, boundary, milestone)
β β βββ dashboard/ # Dashboard widgets (ScoreBoard, CommentaryFeed, WinProbBar, etc.)
β β βββ docs/ # Documentation components (API reference, architecture, etc.)
β β βββ ui/ # Reusable UI components (Badge, CodeBlock, StatCard, etc.)
β βββ hooks/ # Custom React hooks (useWebSocket, useMatchState, useAnimationTrigger)
β βββ lib/ # Utilities (API examples, constants, utils)
β βββ public/ # Static assets
β βββ types/ # TypeScript type definitions (cricket.ts)
β βββ package.json
β βββ tsconfig.json
β βββ next.config.ts
β βββ postcss.config.mjs
β βββ tailwind.config.js
β βββ eslint.config.mjs
β βββ next-env.d.ts
β
βββ models/
β βββ win_prob.pkl
β βββ team_encoder.pkl
β βββ features.pkl
β
βββ docker-compose.yml
βββ requirements.txt
βββ PRD.md
- Python 3.11+
- Docker Desktop
- A Groq API key (free tier works)
git clone https://github.com/BackStacked/CricketPulse.git
cd CricketPulse
pip install -r requirements.txt# .env is already created β just add your key
GROQ_API_KEY=gsk_your_key_here
REDIS_URL=redis://localhost:6379
DATABASE_URL=postgresql+asyncpg://postgres:postgres@localhost:5432/cricketpulse
REDIS_CHANNEL=match:eventsdocker compose up -d# Terminal 1
uvicorn Backend.main:app --reloadExpected startup output:
[CricketPulse] Starting up...
[WinProbAgent] Loaded β 81.7% acc, 0.91 AUC
[CricketPulse] Redis connected
[CricketPulse] Subscribed to Redis channel: match:events
[CricketPulse] Ready β waiting for ball events...
# Terminal 2
python -m Backend.emitter.emitter --file 1529309.json --speed demo# Terminal 3
cd Frontend/cricketpulse-frontend
npm install
npm run devThe Next.js dashboard will start on http://localhost:3000
| Flag | Delay per ball | Use case |
|---|---|---|
--speed slow |
8s | Watch carefully |
--speed normal |
4s | Default |
--speed demo |
2s | Hackathon demo |
--speed fast |
1s | Quick integration test |
Docs: http://localhost:8000/docs (Swagger UI auto-generated)
| Method | Path | Description |
|---|---|---|
GET |
/health |
Redis, DB, model status |
GET |
/match/state |
Current in-memory match state |
GET |
/match/history?limit=20 |
Last N balls from Postgres |
POST |
/match/event |
Inject any BallEvent into the pipeline |
POST |
/demo/wicket |
Force a Rohit Sharma caught dismissal |
WS |
/ws |
Live WebSocketPayload stream |
Every ball emits this JSON to all connected clients:
{
"ball": {
"match_id": "1529309",
"inning": 1,
"batting_team": "Gujarat Titans",
"bowling_team": "Chennai Super Kings",
"over": 7,
"ball": 3,
"batter": "Shubman Gill",
"bowler": "Mukesh Choudhary",
"non_striker": "B Sai Sudharsan",
"batsman_runs": 1,
"extra_runs": 0,
"total_runs": 1,
"is_wicket": false,
"player_dismissed": null,
"dismissal_kind": null,
"is_powerplay": false
},
"win_prob": {
"batting_team": "Gujarat Titans",
"bowling_team": "Chennai Super Kings",
"batting_prob": 64.2,
"bowling_prob": 35.8
},
"commentary": "Shubman Gill clips it fine for a cheeky single as Gujarat continue to build steadily.",
"alert": null,
"timestamp": "2026-05-24T14:22:01.123456Z"
}The win probability model was trained on IPL historical ball-by-ball data from Cricsheet.
| Attribute | Value |
|---|---|
| Algorithm | XGBoost (XGBClassifier) |
| Accuracy | 81.7% |
| AUC-ROC | 0.91 |
| Features | 12 (inning, over, ball, runs, wickets, balls_remaining, CRR, RRR, runs_remaining, target, batting_team_enc, bowling_team_enc) |
| Target | winner == batting_team (binary) |
| Serialization | joblib |
Run ML Model/train_win_prob.ipynb to retrain on updated data.
# 1. Start infrastructure + backend (see Quickstart above)
# 2. Open Frontend/index.html in browser
# β dot shows "Live", dashboard is waiting
# 3. Start emitter
python -m Backend.emitter.emitter --file 1529309.json --speed demo
# 4. Watch win probability bar shift every 2 seconds
# 5. Watch commentary appear for every ball
# 6. Wait for a wicket β red alert banner fires
# 7. Force an instant wicket anytime (works mid-match):
curl -X POST http://localhost:8000/demo/wicket
# 8. Show persistent DB history:
curl http://localhost:8000/match/history
# 9. Show Swagger UI:
# http://localhost:8000/docs
# 10. Use --demo-wicket flag to force wicket at specific ball:
python -m Backend.emitter.emitter --file 1529309.json --speed demo --demo-wicket 10Next.js 16 + React 19 full-stack TypeScript application
Built with modern React patterns, Server Components, and TypeScript for type safety.
Key UI features:
- Win probability bar β animated two-sided bar, purple (batting) vs teal (bowling), transitions smoothly on every ball (Framer Motion)
- Commentary feed β last 10 deliveries, newest on top, fade-in animation
- Alert banner β flashes red for wickets/milestones, auto-dismisses after 4 seconds
- Ball log chips β last 20 balls: gray dot, blue 1-3, green 4, amber 6, red W
- Connection indicator β pulsing green when live, auto-reconnects on disconnect
- Responsive design β Tailwind CSS v4 for mobile-first, accessible UI
- Smooth animations β Framer Motion for card and boundary animations
| Layer | Technology |
|---|---|
| API framework | FastAPI + uvicorn |
| Real-time transport | WebSocket (native FastAPI) |
| Message bus | Redis pub/sub |
| Database | PostgreSQL 15 via asyncpg + SQLModel |
| ORM / validation | SQLModel + Pydantic v2 |
| ML model | Next.js 16 + React 19 + TypeScript |
| Styling | Tailwind CSS v4 + PostCSS |
| Animations | Framer Motion |
| Icons | Lucide React |
| Linting | ESLint |
| LLM | Groq Cloud β llama-3.1-8b-instant |
| Config | pydantic-settings |
| Serialization | joblib (model), json (Redis) |
| Infra | Docker Compose |
| Frontend | Vanilla JS + CSS custom properties |
| Failure scenario | Behaviour |
|---|---|
| Groq API down / rate limited | Returns "Great delivery!" β pipeline never crashes |
| PostgreSQL unreachable | DB log silently skipped β broadcast continues |
| Dead WebSocket client | Client pruned silently β other clients unaffected |
| Unknown team name in ML encoder | Falls back to index 0 β no exception |
| Model file missing | Returns 50/50 β /health reports model_loaded: false |
- Fork the repo
- Add your agent under
Backend/agents/ - Wire it into
event_processor.pyinsideasyncio.gather() - Add its output field to
WebSocketPayloadinschemas/payload.py - Open a PR
Agent pattern is deliberately isolated β adding a new agent does not touch routes, WebSocket, or DB layers.
MIT β see LICENSE
Built with Python, XGBoost, Groq, Redis, FastAPI, and a love for cricket.
GDG Raipur β Agentic Premier League, May 24 2026