Basketball league stats at your fingertips — standings, schedules & AI-powered predictions.
korbPuls is a lightweight FastAPI web app that wraps the korb CLI to serve German basketball league data (from basketball-bund.net) as clean, browsable HTML pages — optimised for both desktop and mobile.
- Standings — full league table with rank, wins, losses, points scored/allowed, differentials, and per-game averages
- Team detail — game-by-game results, win streaks, computed quality metrics (win rate, average margins, blowouts, close games), and upcoming fixtures
- Schedule — complete season schedule sorted by date, with cancelled-game indicators
- Ergebnisse — all completed game results with scores and differentials
- Predictions — forecasted results for remaining games and a projected final standings table (available once at least half the season is played)
When configured with an OpenAI-compatible LLM endpoint, korbPuls adds four AI agents:
| Agent | What it generates | Trigger |
|---|---|---|
| Commentator | Standings narrative — a quick league overview | Auto on data change |
| Oracle | Prediction narrative — qualitative season projection | Auto on data change |
| Analyst | Team analysis — form, strengths, weaknesses | Manual (button on team page) |
| Scout | Matchup preview — head-to-head comparison | Manual (button on matchup page) |
Auto-generated analyses (Commentator, Oracle) run in the background whenever league data changes. If the server restarts before they complete, a startup recovery hook automatically re-triggers them. AI features are fully optional — the app works without them.
- Eager caching — all league data fetched once via korb and served from disk; one-hour TTL with manual refresh
- Background fetching — data downloads run asynchronously; users see a loading spinner with auto-refresh
- AI recovery — startup hook scans cached leagues and re-triggers any missing or failed AI analyses
- Mobile-first responsive design — optimised layouts for phones (360px+), tablets, and desktops
- API endpoints — JSON API with token-based access control for programmatic access
- Health check —
/healthzendpoint for monitoring and Docker healthchecks - Dark theme — "Court Night" design with custom typography (Syne + Source Serif 4)
KORBPULS_API_KEY is a secret used to protect the /api/ endpoints. Generate a secure random key:
# Python
python -c "import secrets; print(secrets.token_urlsafe(32))"
# or OpenSSL
openssl rand -base64 32Copy the output and use it as the value for KORBPULS_API_KEY.
# install dependencies
uv sync
# create a .env file (see Configuration below)
cp .env.example .env # or create manually
# run with live reload
RELOAD=true ./run.shThe app starts at http://localhost:8000. Enter a Liga-ID from basketball-bund.net to get started.
docker compose up --buildThe container exposes port 8000, persists cached data in a named Docker volume,
and includes a built-in health check on /healthz.
-
Clone the repository:
git clone https://github.com/malvavisc0/korbpuls.git cd korbpuls -
Create a
.envfile with your configuration:# Required KORBPULS_API_KEY=your-secret-api-key # Optional — AI features (leave empty to disable) OPENAILIKE_API_BASE=https://api.example.com/v1 OPENAILIKE_API_KEY=sk-... OPENAILIKE_LLM=gpt-4o-mini # Optional — Langfuse tracing (leave empty to disable) LANGFUSE_SECRET_KEY=sk-lf-... LANGFUSE_PUBLIC_KEY=pk-lf-... LANGFUSE_BASE_URL=https://cloud.langfuse.com
-
Start the service:
docker compose up -d --build
-
The app is now running at
http://localhost:8000.
The container includes a health check that pings /healthz every 30 seconds.
Check status with docker compose ps — the STATUS column shows healthy
once the app is ready.
korbPuls serves plain HTTP on the configured PORT. Put it behind a reverse proxy for TLS:
# Nginx example
server {
listen 443 ssl;
server_name korbpuls.de;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}# Caddyfile example
korbpuls.de {
reverse_proxy localhost:8000
}
# Install dependencies
uv sync --no-dev
# Set environment variables
export KORB_CMD="uv run korb"
export CACHE_DIR="./files"
export KORBPULS_API_KEY="your-secret-api-key"
# Run with production settings
WORKERS=4 ./run.sh| Variable | Default | Description |
|---|---|---|
PORT |
8000 |
HTTP listen port |
HOST |
0.0.0.0 |
Bind address |
WORKERS |
2 |
Uvicorn worker count (use 1 per CPU core) |
RELOAD |
false |
Enable live reload (development only) |
CACHE_DIR |
files (local) / /data (Docker) |
Directory for cached league data |
KORBPULS_API_KEY |
— | API key for protected /api/ endpoints |
KORB_CMD |
uv run korb (local) / korb (Docker) |
Command to invoke the korb CLI |
| Variable | Default | Description |
|---|---|---|
OPENAILIKE_API_BASE |
— | Base URL for an OpenAI-compatible API |
OPENAILIKE_API_KEY |
— | API key for the LLM provider |
OPENAILIKE_LLM |
— | Model name (e.g., gpt-4o-mini, qwen/qwen-turbo) |
Set all three to enable AI features. Leave any empty to disable.
| Variable | Default | Description |
|---|---|---|
LANGFUSE_SECRET_KEY |
— | Langfuse secret key (enables tracing when set) |
LANGFUSE_PUBLIC_KEY |
— | Langfuse public key |
LANGFUSE_BASE_URL |
Langfuse cloud | Langfuse host URL (self-hosted or cloud) |
When LANGFUSE_SECRET_KEY is set, every AI agent call (Analyst, Oracle,
Commentator, Scout) is traced to Langfuse with its input, output and any
errors. Tracing is fully opt-in: with the secret key unset there is zero
overhead. Get the keys from your Langfuse project settings. See
.env.example for a full configuration template.
Protected endpoints require the X-API-Key header matching KORBPULS_API_KEY.
| Method | Endpoint | Description |
|---|---|---|
GET |
/healthz |
Health check (public) |
GET |
/api/liga/{ligaid}/standings |
League standings as JSON |
GET |
/api/liga/{ligaid}/spielplan |
Schedule as JSON |
GET |
/api/liga/{ligaid}/ergebnisse |
Game results as JSON |
GET |
/api/liga/{ligaid}/prognose |
Predictions as JSON |
GET |
/api/liga/{ligaid}/team/{slug} |
Team results as JSON |
src/korbpuls/
├── __init__.py # version
├── main.py # FastAPI routes (HTML + API), lifespan, AI recovery
├── auth.py # API key validation, .env loading
├── cache.py # disk-based league data cache
├── korb_client.py # korb CLI wrapper
├── presenters.py # data → view model transformation
├── slugify.py # URL-safe slug generation
├── ai/
│ ├── config.py # AI env-var configuration
│ ├── agents.py # LlamaIndex AI agents (Analyst, Oracle, Commentator, Scout)
│ ├── tools.py # korb tool bindings for agents
│ └── skills/ # AI prompt templates (markdown)
├── templates/ # Jinja2 HTML templates
└── static/ # CSS and static assets
# lint
uv run ruff check src/
# type check
uv run mypy
# test
uv run pytestMade with ❤️ · Powered by korb