Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .env.template
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
# Copy this file to .env and fill in the values.
#
MATCH_ID=[MATCH ID] # match-eligibility (optional — can also be passed via --match-id)
CLUB_REF=[CLUB REF] # member-summary, match-participation, match-eligibility
CLUB_NAME=[CLUB NAME] # member-summary, match-participation, match-eligibility
CLUB_REF=[CLUB REF] # member-summary, match-participation, match-eligibility, timeout-check
CLUB_NAME=[CLUB NAME] # member-summary, match-participation, match-eligibility, timeout-check
DATA_ANALYSIS_YEAR=[YEAR] # match-participation
LIST_OF_CLUBS=[COMMA SEPARATED LIST OF CLUBS] # prospects
EXCLUSION_CLUB=[CLUB REF] # prospects (optional — members of this club are excluded from the prospect list)
TIMEOUT_THRESHOLD_HOURS=[HOURS] # timeout-check (optional — hours remaining below which a game is flagged; default: 5)
TIMEOUT_MATCH_IDS=[COMMA SEPARATED IDS] # timeout-check (optional — match IDs to check, or "all" for all in-progress)
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ jobs:
run: ruff check .

- name: Run tests with coverage
run: pytest --cov=src --cov-report=term-missing --cov-report=xml
run: pytest --cov=src --cov-report=term-missing --cov-report=xml --cov-fail-under=90

- name: Upload coverage report
uses: actions/upload-artifact@v7
Expand Down
17 changes: 10 additions & 7 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@
## Project Overview

A Python CLI tool for Chess.com club administrators. It fetches data from the
Chess.com public API and generates Excel workbooks covering four report types:
Chess.com public API and generates Excel workbooks covering five report types:
club member summaries, match participation analysis, prospect identification,
and match eligibility checking. Club admins use it to manage rosters, track
member contributions, and identify new member candidates — delivering the kind
of structured reporting that the Chess.com UI does not provide out of the box.
match eligibility checking, and timeout monitoring. Club admins use it to
manage rosters, track member contributions, identify new member candidates,
and monitor in-progress matches for timeout risk — delivering the kind of
structured reporting that the Chess.com UI does not provide out of the box.

---

Expand Down Expand Up @@ -73,7 +74,7 @@ Dependencies point inward. This is non-negotiable.
```text
Presentation cli.py, __main__.py
Application reports/*.py (BaseReport ABC + 4 concrete reports)
Application reports/*.py (BaseReport ABC + 5 concrete reports)
Domain domain/models.py, domain/services.py
Expand Down Expand Up @@ -144,7 +145,8 @@ chesscom/
│ ├── match_eligibility.py
│ ├── match_participation.py
│ ├── member_summary.py
│ └── prospect.py
│ ├── prospect.py
│ └── timeout_check.py
├── tests/
│ ├── conftest.py
│ ├── integration/ # Live-API tests (require network access)
Expand Down Expand Up @@ -183,7 +185,8 @@ chesscom/
subcommand handler in `cli.py`.

- **Adding a config variable:** Add the field to `AppConfig` in `config.py` and
parse it in `from_env()`. Never read env vars anywhere else.
parse it in `from_env()`. Never read env vars anywhere else. Add a
corresponding CLI flag in `cli.py` and handle it in `_apply_cli_overrides()`.

- **Adding an API endpoint:** Add a method to `ChessComClient` in
`api/client.py`. Route all HTTP calls through `_get()`.
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ Assessment, review, planning, and refactoring playbooks are available as Claude
- **Plan mode for non-trivial tasks** — if a task has 3+ steps or architectural impact, write a plan to `tasks/todo.md` before implementing. See `.context/conventions/workflow.md`.
- **No type-checker or security-audit tool** — `mypy` and `bandit` are not configured. Do not assume they exist or add them without discussion.
- **Chess.com API domain knowledge** — the public API (`api.chess.com/pub`) is unauthenticated. All endpoint methods live in `ChessComClient`. The Chrome `User-Agent` header is required and must not be removed.
- **Four report types only** — `member-summary`, `match-participation`, `prospects`, `match-eligibility`. New report types must follow the `BaseReport` subclass pattern and be registered in `cli.py`.
- **Five report types** — `member-summary`, `match-participation`, `prospects`, `match-eligibility`, `timeout-check`. New report types must follow the `BaseReport` subclass pattern and be registered in `cli.py`.
- **Environment variables drive all configuration** — every setting is in `.env.template`. New settings must be added to `AppConfig` in `config.py` first; never use `os.getenv()` directly.
- **Verify tests pass before marking work complete** — run `pytest` after any behaviour change.
110 changes: 90 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# chesscom

Python tools for managing a Chess.com club. Generates Excel reports covering
member statistics, match participation, prospect identification, and match
eligibility.
member statistics, match participation, prospect identification, match
eligibility, and timeout monitoring.

[![CI](https://github.com/ldastey-dev/chesscom/actions/workflows/ci.yml/badge.svg)](https://github.com/ldastey-dev/chesscom/actions/workflows/ci.yml)

Expand All @@ -15,7 +15,8 @@ eligibility.
| `member-summary` | `Club Member Summary Report.xlsx` | Roster of all club members with ratings, Chess960 ratings, FIDE titles, join dates, and last-online dates. |
| `match-participation` | `<Club> Club Contribution Report <Year>.xlsx` | Two-sheet workbook: per-member win rate, timeout rate, and participation %; plus a match-by-match breakdown. |
| `prospects` | `Member Prospects.xlsx` | De-duplicated prospect list sourced from one or more target clubs, with current members excluded. |
| `match-eligibility` | `Match Eligibility <match name>.xlsx` | Club members eligible for a specific match (within its rating cap), showing sign-up status. |
| `match-eligibility` | `Match Eligibility.xlsx` | Club members eligible for a specific match (within its rating cap), showing sign-up status. |
| `timeout-check` | `Timeout Check Report.xlsx` | Two-sheet workbook: timeout alerts grouped by match; player timeout summary. Also prints a concise summary to stdout. |

---

Expand Down Expand Up @@ -66,13 +67,27 @@ Open `.env` and fill in your values — see [Environment Variables](#environment

## Usage

All subcommands read configuration from environment variables (or a `.env` file
in the project root).
All subcommands read configuration from environment variables (`.env` file) **and**
CLI arguments. CLI arguments take precedence over environment variables when both
are provided.

### Common flags (available on all subcommands)

| Flag | Overrides | Description |
|---|---|---|
| `--club-ref SLUG` | `CLUB_REF` | Club URL slug for API paths |
| `--club-name NAME` | `CLUB_NAME` | Club display name |

---

### Club Member Summary

```bash
# Using .env (CLUB_REF and CLUB_NAME set):
python -m chesscom member-summary

# Using CLI args only:
python -m chesscom member-summary --club-ref team-scotland --club-name "Team Scotland"
```

**Required:** `CLUB_REF`, `CLUB_NAME`
Expand All @@ -82,44 +97,91 @@ python -m chesscom member-summary
### Match Participation Report

```bash
# Using .env (CLUB_REF, CLUB_NAME, DATA_ANALYSIS_YEAR set):
python -m chesscom match-participation

# Using CLI args:
python -m chesscom match-participation --club-ref team-scotland --club-name "Team Scotland" --year 2025

# Mixed — override year only:
python -m chesscom match-participation --year 2025
```

**Required:** `CLUB_REF`, `CLUB_NAME`, `DATA_ANALYSIS_YEAR`
**Required:** `CLUB_REF`, `CLUB_NAME`, `DATA_ANALYSIS_YEAR` (or `--year`)

---

### Prospect Report

```bash
# Using .env (LIST_OF_CLUBS set):
python -m chesscom prospects

# Using CLI args:
python -m chesscom prospects --clubs team-ireland team-england --exclusion-club team-scotland

# Mixed — override exclusion club only:
python -m chesscom prospects --exclusion-club team-scotland
```

**Required:** `LIST_OF_CLUBS`
**Optional:** `EXCLUSION_CLUB`
**Required:** `LIST_OF_CLUBS` (or `--clubs`)
**Optional:** `EXCLUSION_CLUB` (or `--exclusion-club`)

---

### Match Eligibility Report

```bash
# Match ID from MATCH_ID env var:
# Using .env (MATCH_ID set):
python -m chesscom match-eligibility

# Or supply it directly:
# Using CLI args:
python -m chesscom match-eligibility --match-id 12345

# Mixed — override match ID only:
python -m chesscom match-eligibility --match-id 67890
```

**Required:** `CLUB_REF`, `CLUB_NAME`, `MATCH_ID` (or `--match-id`)

---

### Timeout Check

```bash
# Using .env (TIMEOUT_MATCH_IDS set):
python -m chesscom timeout-check

# Using CLI args — specific matches:
python -m chesscom timeout-check 12345 67890

# Check all in-progress matches:
python -m chesscom timeout-check all

# Custom threshold (flag overrides TIMEOUT_THRESHOLD_HOURS):
python -m chesscom timeout-check all --threshold 24

# Full CLI args (no .env needed):
python -m chesscom timeout-check all \
--club-ref team-scotland \
--club-name "Team Scotland" \
--threshold 10
```

**Required:** `CLUB_REF`, `CLUB_NAME`
**Optional:** `MATCH_ID` (can also be passed via `--match-id`)
**Required:** `CLUB_REF`, `CLUB_NAME`, plus match IDs via CLI or `TIMEOUT_MATCH_IDS`
**Optional:** `TIMEOUT_THRESHOLD_HOURS` (or `--threshold`; default: 5 hours)

**Output:**
- **stdout** — concise per-match summary with flagged player counts
- **xlsx** — Tab 1: alerts grouped by match; Tab 2: player timeout counts (descending)

---

### Help

```bash
python -m chesscom --help
python -m chesscom match-eligibility --help
python -m chesscom timeout-check --help
```

---
Expand All @@ -130,12 +192,17 @@ Copy `.env.template` to `.env` and populate:

| Variable | Required by | Description |
|---|---|---|
| `CLUB_REF` | `member-summary`, `match-participation`, `match-eligibility` | Club URL slug, e.g. `team-scotland` |
| `CLUB_NAME` | `member-summary`, `match-participation`, `match-eligibility` | Display name, e.g. `Team Scotland` |
| `CLUB_REF` | `member-summary`, `match-participation`, `match-eligibility`, `timeout-check` | Club URL slug, e.g. `team-scotland` |
| `CLUB_NAME` | `member-summary`, `match-participation`, `match-eligibility`, `timeout-check` | Display name, e.g. `Team Scotland` |
| `DATA_ANALYSIS_YEAR` | `match-participation` | Four-digit year to analyse, e.g. `2025` |
| `MATCH_ID` | `match-eligibility` | Chess.com match ID (optional — also accepted via `--match-id`) |
| `MATCH_ID` | `match-eligibility` | Chess.com match ID (also accepted via `--match-id`) |
| `LIST_OF_CLUBS` | `prospects` | Comma-separated club slugs to source prospects from |
| `EXCLUSION_CLUB` | `prospects` | Optional club slug whose members are excluded from the prospect list |
| `EXCLUSION_CLUB` | `prospects` | Club slug whose members are excluded from the prospect list |
| `TIMEOUT_MATCH_IDS` | `timeout-check` | Comma-separated match IDs, or `all` for every in-progress match |
| `TIMEOUT_THRESHOLD_HOURS` | `timeout-check` | Hours remaining below which a game is flagged as at risk (default: `5`) |

All variables can also be supplied (or overridden) via CLI flags. Run any subcommand
with `--help` to see available options.

---

Expand Down Expand Up @@ -165,7 +232,7 @@ chesscom/
│ ├── api/
│ │ └── client.py # ChessComClient — all HTTP calls
│ ├── domain/
│ │ ├── models.py # Member, Match, MatchResult, MemberParticipation
│ │ ├── models.py # Member, Match, MatchResult, MemberParticipation, TimeoutAlert
│ │ └── services.py # Pure calculation and filtering functions
│ ├── export/
│ │ └── excel.py # ExcelReportWriter + SheetConfig
Expand All @@ -174,7 +241,8 @@ chesscom/
│ ├── match_eligibility.py
│ ├── match_participation.py
│ ├── member_summary.py
│ └── prospect.py
│ ├── prospect.py
│ └── timeout_check.py
├── tests/
│ ├── integration/ # Live-API tests (require network)
│ └── unit/ # Fast, dependency-free unit tests
Expand Down Expand Up @@ -225,7 +293,7 @@ ruff format .

The GitHub Actions workflow (`.github/workflows/ci.yml`) runs on every push and
pull request to `master`. It runs `ruff check` and `pytest --cov` against
Python 3.11 and 3.12.
Python 3.11 and 3.12, enforcing a minimum 90% test coverage threshold.

---

Expand All @@ -235,3 +303,5 @@ Python 3.11 and 3.12.
with exponential back-off on transient failures.
- A Chrome browser `User-Agent` header is used to ensure compatibility with
the Chess.com API.
- The `timeout-check` command will warn if any boards could not be checked due
to API errors.
16 changes: 16 additions & 0 deletions src/chesscom/api/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,3 +184,19 @@ def get_match(self, match_id_or_url: str) -> dict:
return self._get(
match_id_or_url if match_id_or_url.startswith("http") else f"match/{match_id_or_url}"
)

def get_match_board(self, board_url: str) -> dict:
"""Return board detail for a team match board.

The board endpoint returns the individual games on a specific board,
including ``move_by`` timestamps for in-progress daily games.

Args:
board_url: Fully-qualified board URL as returned in the player's
``board`` field from a match detail response (e.g.
``"https://api.chess.com/pub/match/12345/1"``).

Returns:
Board detail dict containing ``board_scores`` and ``games``.
"""
return self._get(board_url)
Loading