Sleeper Manager is a personal, read-only fantasy basketball assistant for Sleeper Lock-In leagues. It combines Sleeper league data with NBA schedules, availability, and projections to recommend weekly lineup and Lock-In decisions. It can send ntfy or Discord notifications, but it never submits roster changes to Sleeper.
- Sleeper provides league settings, rosters, matchups, transactions, and fantasy scoring.
- ESPN and official NBA injury reports provide current schedules, results, and availability.
- Historical SportsDataverse data supports projection experiments and backtesting.
- Local commands persist state in SQLite; the deployed Cloudflare Worker uses D1.
- The five-minute scheduled wake captures pre-tipoff starters, watches ESPN finals, and recommends Lock or Pass until acknowledgement or deadline.
See LOCK_IN_MODE.md for the league rules enforced by the decision engine.
Requirements:
- Python 3.12 or newer
- uv
Install the locked development environment:
uv sync --locked --all-groupsCreate an ignored .env with the Sleeper IDs used for live commands:
SLEEPER_LEAGUE_ID=your-league-id
SLEEPER_USER_ID=your-user-idDo not commit .env, notification topics, access tokens, or webhook URLs. The default manager
policy works without a file; to customize it, copy the documented example into the ignored
local directory:
mkdir -p .local
cp manager-policy.example.toml .local/policy.tomlVerify configuration, synchronize the league profile, and inspect NBA data health:
uv run sleeper-manager check-config
uv run sleeper-manager bootstrap
uv run sleeper-manager check-nba-databootstrap and check-nba-data read from external providers and write only to the local SQLite
database at .local/state.db by default.
Settings load from environment variables or the ignored .env file.
| Variable | Purpose | Default |
|---|---|---|
SLEEPER_LEAGUE_ID |
League used by live commands | Required for live commands |
SLEEPER_USER_ID |
Manager account used to identify the roster | Required for live commands |
TIMEZONE |
Local display and scheduling timezone | America/Chicago |
MANAGER_POLICY_PATH |
Manager policy TOML file | .local/policy.toml |
STATE_BACKEND |
Local persistence backend | sqlite |
SQLITE_PATH |
Local state and NBA cache database | .local/state.db |
NTFY_TOPIC |
Enables ntfy delivery | Empty |
NTFY_BASE_URL |
ntfy service root | https://ntfy.sh |
NTFY_ACCESS_TOKEN |
Optional ntfy authentication | Empty |
DISCORD_WEBHOOK_URL |
Enables Discord delivery | Empty |
ACKNOWLEDGEMENT_BASE_URL |
Base URL for notification action callbacks | Empty |
At least one of NTFY_TOPIC or DISCORD_WEBHOOK_URL is required for notification commands.
ACKNOWLEDGEMENT_BASE_URL is also required for interactive notifications and scheduled work.
Local commands persist in SQLite; only STATE_BACKEND=sqlite is supported. The Cloudflare
Worker uses D1 and does not read this variable.
Manager policy TOML is the local source of manager intent: decision preset and confidence,
notification quiet hours, and mapping overrides. sync-cloudflare-runtime-data
translates that intent into the deployed runtime policy envelope in D1, including a content
hash stamped on live plans as manager_policy_version. Version-one policy files reject removed
keys such as use_matchup_context, protect_elite_upside, daily_summary, injury_alerts,
and protected_sleeper_ids. Quiet-hour fields are accepted and stored, but quiet-hour
suppression is deferred. Live Lock-In advice uses the resolved minimum-confidence threshold.
Changing .local/policy.toml does not affect the Worker until the next runtime-data sync.
Run uv run sleeper-manager --help for the complete argument reference.
| Command | Purpose |
|---|---|
check-config |
Report sanitized configuration readiness |
bootstrap |
Validate and summarize the configured Sleeper league |
check-nba-data |
Report NBA provider health and player-mapping coverage |
test-notification |
Send one idempotent local notification diagnostic |
run-scheduled |
Run one local due-work wake against SQLite |
validate-model-features |
Run the frozen historical feature experiment |
evaluate-projections |
Evaluate the frozen projection models |
python -m sleeper_manager.backtesting.replay.team_week_bundle |
Build one immutable historical team-week replay-input bundle |
python -m sleeper_manager.backtesting.experiments.lock_in_diagnostic |
Run one diagnostic-only Lock-In model/oracle comparison for an existing team-week artifact |
validate-lock-in-policy |
Replay historical leagues against the Lock-In policy |
sync-cloudflare-runtime-data |
Prepare or apply projection history and runtime policy in D1 |
Historical ingestion commands need the optional dependencies:
uv sync --locked --all-groups --extra historicalBuild a single historical team-week bundle with cached NBA data and a minimal Sleeper archive (acquired only when absent):
uv run --extra historical python -m sleeper_manager.backtesting.replay.team_week_bundle \
--league-id <league-id> --roster-id <roster-id> --week <week> --monday <YYYY-MM-DD>The command persists source-fingerprinted artifacts under
.local/model-validation/team-week-inputs/. It labels late-captured player
eligibility as best-known rather than exact and leaves unavailable evidence
visible in the bundle. The NBA cache directory is resolved from Sleeper's
season metadata using SportsDataverse's ending-year convention, including
October--December weeks. When source finalization timestamps are unavailable,
the bundle labels a next-local-day 6:00 AM Eastern completion bound as
approximate; only observed starters are treated as best-known eligible to lock.
Run a diagnostic-only Lock-In comparison against one existing team-week artifact (no acquisition, refresh, or network access):
uv run python -m sleeper_manager.backtesting.experiments.lock_in_diagnostic \
--team-week-path .local/model-validation/team-week-inputs/<manifest-id>/team-weeks/<league-id>/week-<week>/roster-<roster-id>.json \
--output-root .local/model-validation/reportsThis writes configuration-keyed JSON and Markdown under
.local/model-validation/reports/lock-in-diagnostics/. The output is
diagnostic_only evidence for one historical team-week; it does not validate
release readiness and must not be treated as Phase 7 promotion evidence.
Run the same checks enforced by CI:
uv run ruff check .
uv run ruff format --check .
uv run mypy
uv run pytest --cov=sleeper_manager --cov-report=term-missingThe opt-in live provider smoke test is excluded from the default suite. Run it only with
FANTASY_MANAGER_LIVE_SMOKE=1 and configured Sleeper IDs.
The production path is a Python Worker with D1 persistence and a five-minute Cron Trigger. Projection history and the active runtime policy are synchronized separately from deployment.
See docs/cloudflare-runtime.md for operator commands and dry-run behavior, and infra/cloudflare/README.md for first-deployment and migration steps.
src/sleeper_manager/domain: fantasy, scoring, scheduling, and planning modelssrc/sleeper_manager/integrations: Sleeper and NBA provider adapterssrc/sleeper_manager/decisions: lineup and Lock-In decision policiessrc/sleeper_manager/workflows: planning, notification, and diagnostic workflowssrc/sleeper_manager/persistence: SQLite and D1 repositoriessrc/sleeper_manager/projections: live and historical projection modelssrc/sleeper_manager/backtesting: replay, experiment, and validation toolingsrc/sleeper_manager/cloudflare: Worker entry point and runtime adaptersinfra/cloudflare: D1 migrations and deployment notestests: deterministic unit tests and provider fixtures