A reproducible CLI-first pipeline for analyzing chess playing styles through time-control preferences and performance volatility patterns. This project builds per-user feature vectors and population baselines from Chess.com public API data, examining associations between time control preferences and decision-making proxies.
This tool enables systematic analysis of how chess players allocate their games across different time controls (bullet, blitz, rapid) and whether these preferences are associated with differences in performance volatility. The analysis framework is designed for reproducibility, transparency, and professional data engineering practices.
Important: This analysis examines behavioral patterns and performance proxies only. No personality inference or causal claims are made. All results are descriptive associations.
Individual chess players show consistent time-control preferences (as measured by long-run proportions in the available archive history), and these preferences are associated with systematic differences in performance volatility, consistent with different decision-making proxies under time constraints.
Note: v1 does not perform a formal temporal stability test (e.g., split-half or time-window correlations). “Consistent” refers to aggregate proportions over the downloaded history.
Time-trouble analysis using per-move clock data from PGN annotations. This module is disabled by default and only activates if clock data is available in the Chess.com API responses. In practice, per-move clock times are not consistently available in the public API.
This project uses the Chess.com Public API, which provides:
- Available: Player statistics, game archives (monthly), game outcomes, ratings, time controls, PGN text, ECO codes, opening names
- Sometimes Available: Accuracy scores (only for analyzed games), basic game metadata
- Not Consistently Available: Per-move clock times, detailed move-by-move timestamps
- Known API edge case: Some monthly archive URLs returned by
/games/archivesmay return 404 when fetched. The pipeline logs and skips missing months rather than failing the full user.
API endpoints used:
https://api.chess.com/pub/player/{username}/statshttps://api.chess.com/pub/player/{username}/games/archives- Monthly game archives (JSON format)
The API is rate-limited but does not require authentication. This pipeline respects rate limits with retry logic and exponential backoff.
chess-decision-style/
├─ README.md # This file
├─ pyproject.toml # Project dependencies (uv-compatible)
├─ configs/
│ └─ config.yaml # Pipeline configuration
├─ data/
│ ├─ raw/ # Cached API responses (gitignored)
│ └─ processed/ # Generated CSV outputs
├─ reports/
│ ├─ figures/ # Generated PNG plots
│ └─ summaries/ # Per-user markdown reports
├─ src/chess_style/ # Core pipeline modules
│ ├─ api.py # Chess.com API client
│ ├─ cache.py # Local caching utilities
│ ├─ normalize.py # Raw JSON → tidy CSV
│ ├─ validate.py # Schema validation
│ ├─ features.py # Feature engineering
│ ├─ population.py # Population baseline
│ ├─ plots.py # Matplotlib visualizations
│ ├─ report.py # Markdown report generation
│ └─ cli.py # Typer CLI interface
└─ tests/ # Pytest test suite
├─ test_schema.py
└─ test_features.py
This project uses uv for fast, reliable dependency management.
- Python 3.10+
- uv (install via
curl -LsSf https://astral.sh/uv/install.sh | sh)
# Clone repository
git clone <repository-url>
cd chess-decision-style
# Install dependencies
uv sync
# Optional: Install dev dependencies for testing
uv sync --extra dev# 1. Create a users.txt file with one username per line
echo "magnuscarlsen" > users.txt
# 2. Run full pipeline for a single user
uv run chess-style build --user magnuscarlsen
# Output files:
# - data/processed/games_magnuscarlsen.csv
# - data/processed/user_features_magnuscarlsen.csv
# - reports/figures/time_control_preference_magnuscarlsen.png
# - reports/figures/win_rate_by_timeclass_magnuscarlsen.png
# - reports/figures/volatility_by_timeclass_magnuscarlsen.png
# - reports/figures/rating_diff_distribution_magnuscarlsen.png
# - reports/summaries/summary_magnuscarlsen.md# 1. Create users.txt with multiple usernames
cat > users.txt << EOF
hikaru
magnuscarlsen
chessbrah
gothamchess
EOF
# 2. Run batch processing
uv run chess-style batch --users users.txt
# 3. Compute population baseline
uv run chess-style population --users users.txt
# Output:
# - data/processed/population_features.csv
# - Individual outputs for each userBy default, the pipeline uses cached API responses. To force a refresh:
# Single user
uv run chess-style build --user hikaru --refresh
# Batch
uv run chess-style batch --users users.txt --refreshThe chess-style CLI provides fine-grained control over each pipeline stage:
-
fetch- Download raw data from Chess.com APIuv run chess-style fetch --user <username> [--refresh]
-
normalize- Convert raw JSON to tidy CSV (one row per game)uv run chess-style normalize --user <username>
-
validate- Run schema validation checksuv run chess-style validate --user <username>
-
features- Compute per-user feature vectoruv run chess-style features --user <username>
-
plots- Generate matplotlib PNG figuresuv run chess-style plots --user <username>
-
report- Generate markdown analysis reportuv run chess-style report --user <username>
-
build- Run full pipeline (fetch → normalize → validate → features → plots → report)uv run chess-style build --user <username> [--refresh]
-
batch- Run pipeline for multiple users from fileuv run chess-style batch --users users.txt [--refresh]
-
population- Compute population-level statistics from all processed usersuv run chess-style population --users users.txt
-
games_<user>.csv- Normalized game data (one row per game)- Columns: game_id, username, opponent, time_class, result, ratings, outcome_numeric, etc.
- Deduplicated by game_id
- Includes both rated and unrated games
-
user_features_<user>.csv- Per-user feature vector (one row)- Overall metrics: total games, win rate, volatility
- Per time-class metrics: game counts, percentages, win rates, volatility
- Flags for minimum game thresholds
-
population_features.csv- Population-level statistics- Mean, median, p25, p75 for each feature
- Counts of valid users per feature
- Distribution of dominant time classes
All plots saved as PNG (150 DPI) in reports/figures/:
time_control_preference_<user>.png- Bar chart of game distribution across time classeswin_rate_by_timeclass_<user>.png- Win rates for bullet/blitz/rapidvolatility_by_timeclass_<user>.png- Performance volatility (SD of outcomes)rating_diff_distribution_<user>.png- Histogram of rating differencesuser_vs_population_timeclass_<user>.png- User preferences vs population mean (if available)
summary_<user>.md- Comprehensive markdown report including:- Hypothesis statement
- Data summary and quality notes
- Key results (descriptive statistics)
- Population comparisons (if available)
- Limitations and caveats
- Reproducibility commands
Edit configs/config.yaml to customize:
- API settings: Timeout, retry count, backoff
- Data filtering: Minimum games per time class, time classes to analyze
- Time-trouble module: Enable/disable (default: false)
- Output paths: Customize directory structure
Run the test suite:
# Install dev dependencies
uv sync --extra dev
# Run all tests
uv run pytest
# Run with coverage
uv run pytest --cov=chess_style --cov-report=term-missingTests cover:
- Schema validation (required columns, uniqueness, data types)
- Feature engineering (game thresholds, win rate calculations, volatility)
- Reproducible caching with explicit refresh control
- Schema validation with fail-fast error handling
- Deduplication by stable game IDs
- Handles missing optional fields gracefully
- Selection Bias: Only users with public profiles and sufficient game history
- Rating Confounds: Rating differences across time controls may reflect skill rather than style
- Missing Clock Data: Per-move timestamps not consistently available (H2 cannot be fully tested)
- Accuracy Scores: Only available for analyzed games (often <10% of total)
- Temporal Aggregation: Analysis combines all historical games without time weighting
- No Causal Inference: All results are descriptive associations only
This repository follows professional data engineering practices:
- CLI-first: No notebook dependencies; all operations via command line
- Reproducibility: Deterministic outputs, explicit caching, version-controlled configs
- Modularity: Clear separation of concerns (API, cache, normalize, validate, features, plots, report)
- Type Safety: Type hints throughout codebase
- Error Handling: Clear error messages, fail-fast validation, retry logic
- Restraint: No overclaiming; associations and proxies only
This analysis examines behavioral patterns (time control preferences) and performance proxies (volatility, win rates). It does not infer personality traits, cognitive abilities, or decision-making quality. All language in reports and outputs is carefully constrained to descriptive associations.
MIT License (or your preferred license)
Contributions welcome. Please ensure:
- New features include tests
- Code passes existing test suite
- Documentation is updated
- No personality inference language introduced
For questions or issues, please open a GitHub issue.