Complete Project Documentation

Everything you need to install, configure, and use the NBA Shot Strategy application.

Table of Contents

Project Overview

Interactive web application applying probabilistic methods from financial mathematics to basketball analytics. Users can select NBA matchups, adjust shot strategies interactively, and run Monte Carlo simulations to find optimal 2PT/3PT ratios.

Key Features

Quick Start

Local (30 seconds)

cd /opt/predicrionai
docker compose up --build

# Open http://localhost:8000

Production (with HTTPS)

# Configure .env
cp .env.example .env
nano .env  # Add CF_API_EMAIL, CF_API_KEY, DOMAIN

# Launch
docker compose up -d --build

# Access:
# https://monte.kz-saas.com          → Web App
# https://jupyter.monte.kz-saas.com  → Jupyter
# https://research.monte.kz-saas.com → Research Paper
# https://traefik.monte.kz-saas.com  → Dashboard

Installation

Option 1: Docker Compose (Recommended)

# Clone repository
git clone <repo-url>
cd predicrionai

# Launch
docker compose up -d --build

Automatically handles:

Option 2: Local Development

# Backend
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
uvicorn webapp.main:app --reload --port 8000

# Frontend (separate terminal)
cd frontend
npm install
npm run dev

# Open http://localhost:5173

Production Deployment

Requirements

Step 1: DNS Setup (Cloudflare)

Add 4 A records:

Name Content Proxy
monteYOUR_SERVER_IP✅ Proxied
jupyter.monteYOUR_SERVER_IP✅ Proxied
research.monteYOUR_SERVER_IP✅ Proxied
traefik.monteYOUR_SERVER_IP❌ DNS only

Step 2: Cloudflare API Token

  1. Cloudflare Dashboard → My ProfileAPI Tokens
  2. Create Token → template Edit zone DNS
  3. Permissions: Zone → DNS → Edit
  4. Zone: your domain
  5. Copy the token

Step 3: Configure .env

cp .env.example .env
nano .env

Fill in:

# Cloudflare API Token
CF_API_EMAIL=your-email@cloudflare.com
CF_API_KEY=your_cloudflare_api_token

# Domain
DOMAIN=monte.yourdomain.com

# Application
NBA_API_RATE_LIMIT=0.6
LOG_LEVEL=INFO

# Match cache (Redis, TTL in seconds, default 12 hours)
MATCH_CACHE_TTL_SECONDS=43200

Step 4: Launch

# Start all services
docker compose up -d --build

# Check status
docker ps

# Follow logs (certificate issuance)
docker logs traefik -f

# Look for: "Server responded with a certificate"

Step 5: Verify

curl -I https://monte.yourdomain.com           # 200
curl -I https://research.monte.yourdomain.com  # 200
curl -I https://jupyter.monte.yourdomain.com   # 302

Usage Guide

Web Interface

  1. Select Match — Choose two teams or pick from Synthetic / Historical / Upcoming tabs
  2. Match Sources — Badges on matches: ESPN, nba.com or Synthetic (fallback)
  3. Loading State — Historical/Upcoming show a "Запрос к NBA API…" (Requesting NBA API…) spinner while fetching
  4. View Profiles — Current strategies and Four Factors
  5. Adjust Parameters:
    • 3-Point ratio (0-95%)
    • 3-Point FG% (20-50%)
    • Hot hand (ON/OFF)
    • Iterations (1K-20K)
  6. Run Simulation — Get results
  7. Analyze — Histograms, probabilities, recommendations

Match Data Sources

Matches are fetched from the first available source (in priority order):

PrioritySourceProvidessource
1ESPN Scoreboard APIReal games: last 7 days + next 7 daysespn
2stats.nba.com (nba_api)Historical games from season 2023-24nba_api
3SyntheticProviderDeterministic synthetic pairings (seed 42) — always workssynthetic

Responses are cached in Redis (key matches:{mode}:{count}, TTL 12 hours by default). If Redis is unavailable an in-memory cache is used — behavior stays the same.

Jupyter Notebooks

# Start Jupyter
docker compose up jupyter

# Open https://jupyter.monte.kz-saas.com
# Password in CREDENTIALS.txt

Example code:

from src.simulation import MonteCarloSimulation

sim = MonteCarloSimulation(
    two_point_fg_pct=0.52,
    three_point_fg_pct=0.36,
    num_iterations=10000
)

best, all = sim.optimize_shot_distribution()

import matplotlib.pyplot as plt
plt.plot([r.three_pt_ratio for r in all], 
         [r.mean_score for r in all])
plt.show()

Architecture

Project Structure

predicrionai/
├── webapp/              # FastAPI backend
│   ├── main.py         # Entry point (+ cache warm-up on startup)
│   ├── api.py          # REST routes
│   ├── providers.py    # Data sources: ESPN → nba_api → synthetic
│   ├── cache.py        # Redis match cache (in-memory fallback)
│   ├── services.py     # Business logic
│   └── static/         # Built frontend
├── frontend/           # React + Vite
│   ├── src/
│   │   ├── pages/      # MatchSelect, Analysis
│   │   └── components/ # TeamCard, Charts
│   └── vite.config.js
├── src/                # Simulation engine
│   ├── simulation/     # MonteCarlo
│   ├── analysis/       # FourFactors, HotHand
│   └── data/           # NBAClient
├── research-site/      # Static site
└── docker-compose.yml  # Orchestration (traefik, web, redis, jupyter, research)

Tech Stack

ComponentTechnologyVersion
BackendFastAPI + Uvicorn0.115+
FrontendReact + Vite18 / 5
ChartsChart.js4.4
SimulationNumPy + SciPy1.24+
CacheRedis7.x
ContainersDocker + Composev2
ProxyTraefik + Let's Encrypt3.7.10

API Documentation

Endpoints

GET /api/teams

Get list of all NBA teams.

curl http://localhost:8000/api/teams

GET /api/teams/{team_id}

Get team profile (shooting stats, Four Factors).

curl http://localhost:8000/api/teams/1610612747

POST /api/simulate

Run Monte Carlo simulation.

{
  "home_team_id": 1610612747,
  "away_team_id": 1610612744,
  "home_three_ratio": 0.38,
  "away_three_ratio": 0.42,
  "home_fg3_pct": 0.36,
  "away_fg3_pct": 0.38,
  "hot_hand": false,
  "iterations": 5000
}

GET /api/health

Health check endpoint.

curl http://localhost:8000/api/health

GET /api/matches?mode=<upcoming|history|synthetic>&count=N

List of matches. For upcoming/history data is fetched from ESPN (fallback: stats.nba.com, then synthetic) and cached in Redis.

curl "http://localhost:8000/api/matches?mode=history&count=5"

Each match has a source field — espn, nba_api or synthetic:

[{
  "match_id": "ESP-401585000",
  "date": "2026-08-04",
  "home_team_id": 1610612747,
  "away_team_id": 1610612738,
  "home_score": 112,
  "away_score": 98,
  "status": "final",
  "source": "espn",
  "label": "Los Angeles Lakers vs Boston Celtics"
}]

Full API docs: https://monte.kz-saas.com/docs

Troubleshooting

Port 8000 in use

# Find process
lsof -i :8000

# Stop
kill -9 <PID>

# Or change port in docker-compose.yml
ports:
  - "8001:8000"

Frontend not loading

# Rebuild
cd frontend
npm run build

# Check files
ls -la ../webapp/static/

Jupyter 500 error

Check password hash in docker-compose.yml — all $ must be doubled:

--NotebookApp.password='argon2:$$argon2id$$v=19$$...'

Traefik Dashboard 401

  1. Check password in CREDENTIALS.txt
  2. Ensure traefik.monte.yourdomain.com is in DNS-only mode (not proxied)

Certificates not issued

# Check logs
docker logs traefik | grep -i error

# Common causes:
# - Invalid CF_API_KEY
# - Token lacks "Zone DNS Edit" permission
# - DNS records missing

NBA API not responding

Not a problem! Match data is fetched through the ESPN → stats.nba.com → Synthetic chain. If all real sources are down (or it's off-season with no games), the synthetic fallback kicks in automatically — you'll see the "Synthetic" badge. If stats.nba.com blocks requests, ESPN keeps working, and vice versa.

Redis is not running

The app works without Redis too: the cache automatically switches to in-memory mode (single process). To restore Redis: docker compose up -d redis.

Additional Resources

Research Concentration under Professor Hesam Oveys
New York University | August 2026