Target Audience: AI Agents (LLMs) working on this codebase.
You are working on TyperBot, a Discord bot for football prediction leagues.
- Core Function: Users predict scores -> Admins enter results -> Bot calculates points.
- Vibe: Functional, simple, reliable. No bloat.
- Tech: Python 3.10+, discord.py, aiosqlite, portable container hosting.
- Persistence: The database defaults to
./data/typer.dblocally. On production, setDATA_DIR=/app/dataso the live DB stays on the mounted data volume. - Transaction Safety: Critical operations use atomic transactions (BEGIN/COMMIT/ROLLBACK) to ensure data consistency. Never modify transaction logic without understanding rollback implications.
- Prediction Contract: Fixture threads are the public source of truth.
/predictis a structured composer that posts publicly into the selected fixture thread. - Configuration: All data paths configurable via env vars in
utils/config.py:DATA_DIR: Base directory (default:./data)DB_PATH: Full database path (default:{DATA_DIR}/typer.db)BACKUP_DIR: Backup storage (default:{DATA_DIR}/backups)
- Modal Workflow: Complex inputs (fixture creation, results entry,
/predict) use Discord modals instead of DM sessions. - Thread Predictions: Users can post predictions in public threads under fixture announcements.
- Rate Limiting: Thread predictions are rate-limited to 1 per second per user. Cooldown entries auto-expire after 1 hour.
- Async: All database ops must be async (
aiosqlite). - Parsing: Use
utils.prediction_parser.parse_line_predictionsfor all score parsing. Do NOT write ad-hoc regex. - Logging: Use
typer_bot.utils.logger.setup_logging()early. Do not useprint(). - Timezones: All datetime operations use timezone-aware objects. Use
utils.timezone.now()instead ofdatetime.now(). Configure viaTZenv var (default:UTC). - Permissions: Bot requires
Send Messages,Read Message History,Add Reactions, andCreate Public Threads.
SQLite. Tables are initialized in typer_bot/database/connection.py.
fixtures (
id INTEGER PK,
week_number INTEGER,
games TEXT, -- Newline separated: "Team A - Team B\nTeam C - Team D"
deadline DATETIME,
status TEXT DEFAULT 'open', -- 'open' or 'closed'
message_id TEXT -- Discord message ID (thread shares same snowflake ID)
)
predictions (
id INTEGER PK,
fixture_id INTEGER FK,
user_id TEXT, -- Discord ID
predictions TEXT, -- Newline separated: "2-1\n1-1"
is_late BOOLEAN
)
results (
id INTEGER PK,
fixture_id INTEGER FK,
results TEXT -- Newline separated actual scores
)
scores (
id INTEGER PK,
fixture_id INTEGER FK,
user_id TEXT,
points INTEGER, -- 3 (exact), 1 (outcome), 0 (miss)
exact_scores INTEGER,
correct_results INTEGER
)typer_bot/bot.py: Entry point and setup hook.typer_bot/commands/user_commands.py: Public slash commands, including modal-driven/predictflow.typer_bot/commands/admin_panel/: Admin panel UI views, selects, and modals split out ofadmin_commands.py.typer_bot/handlers/thread_prediction_handler.py: Thread-based prediction processing (on_message) plus thread prediction cooldown state.typer_bot/commands/admin_commands.py:/admincommand surface and orchestration for admin workflows, including admin calculation cooldown state.typer_bot/utils/config.py: Centralized configuration (data paths via env vars).typer_bot/utils/prediction_parser.py: Central logic for parsing "2-1" or "2:1" strings.typer_bot/utils/scoring.py: Point calculation rules.typer_bot/utils/logger.py: structured logging configuration for local and deployed environments.typer_bot/utils/db_backup.py: Automatic database backup after successful score calculation.scripts/restore_db.py: Manual database restore from a host or container shell.
- Fixing Parsing: Edit
prediction_parser.py. - Admin Panel UI: Edit
commands/admin_panel/. - Workflow/Cooldown State: Thread prediction cooldowns live in
handlers/thread_prediction_handler.py; admin calculate cooldowns live incommands/admin_commands.py. Keep them process-local instead of introducing module-level globals. - New Commands: Add Cog to
commands/folder, load inbot.py. - Database Changes: Edit
typer_bot/database/connection.pyinitialize()and the focused repositories intyper_bot/database/(handle migrations manually if needed). - Debugging: Check
utils/logger.pyfor config. SetLOG_LEVEL=DEBUGin env. - Database Restore: Use
scripts/restore_db.pyfrom the host or container shell for manual database restoration from backups. The script restores into a temporary SQLite file first, then atomically replaces the live DB only after success.
When modifying code, ensure tests pass and add tests for new functionality.
Test Organization:
tests/test_*.py- Unit tests mirroring source structuretests/test_integration.py- End-to-end workflows (fixture → predictions → results → scores)tests/conftest.py- Shared fixtures (mock Discord objects, temp database)
Key Patterns:
- Async tests: Use
@pytest.mark.asynciodecorator - Discord mocking: Use fixtures from
conftest.py(mock_interaction,mock_user,mock_thread, etc.) - Database: The
databasefixture provides isolated temp database per test - Time: Use
freezegunfor time-sensitive tests
Adding Tests:
- Mirror the source file structure (e.g.,
commands/admin_commands.py→tests/test_admin_commands.py) - Use descriptive test names:
test_rejects_non_admin_usersnottest_admin_1 - Group related tests in classes (e.g.,
class TestAdminOnlyDecorator) - Mock external dependencies (Discord API, time) - never hit real services
- Prefer behavior and contract assertions over exact UI layout, log wording, mock-call choreography, or source-string checks.
- Delete low-value tests that only verify Python/library behavior or fixture setup.
Running Tests:
uv run pytest # All tests
uv run pytest -x # Stop on first failure
uv run pytest -v -k "admin" # Run tests matching "admin"
uv run pytest --tb=short # Shorter traceback output- Double Digits: Scores like
10-0are allowed. - Format: Users provide flexible separators (
-,:,–). - Rate Limiting: Thread predictions limited to 1/second per user.
- Workflow State Ownership: Process-local thread/admin cooldowns live with their owning handlers/commands; they are not persisted and reset on process restart.
- Token Safety: Bot validates DISCORD_TOKEN at startup (rejects placeholders like "your_bot_token_here"). Token values are never logged.
Setup (one-time):
# Install dependencies (includes ty as dev dependency)
uv sync --group dev
# Install prek (Rust-based pre-commit hooks, 10-100x faster than pre-commit)
uv tool install prek
# Install the git hooks
prek install
# Verify hooks are active
ls .git/hooks/pre-commit # Should exist (not .sample)Pre-commit Hooks:
Configured in .pre-commit-config.yaml:
- ruff check --fix - Linting with auto-fix
- ruff format - Code formatting
- ty check - Type checking (blocking in CI)
Running manually:
prek run --all-files # Run all hooks on all files
prek run ruff # Run specific hookType Checking:
- Tool:
ty - Run:
ty check typer_bot
- Configuration: The
ENVIRONMENTvariable controls bot behavior:production: Production deployment label- Not set/
development: Non-production environment label
- Runtime: The bot still connects to Discord in non-production environments; use a separate token for manual testing and previews
- Token Safety: Any deployment with a valid token will connect; never run multiple environments against the same live token
- Production: Set
ENVIRONMENT=productionin deployment variables for production deployments - Portability: Works on any platform (Coolify, Railway, local, etc.) - just set the variable accordingly