League Defaults¶
The page at /admin/league-defaults edits in-game and Match question defaults on
each League row. Matches without overrides inherit these values.
Data source¶
The admin page is a thin HTML shell. Alpine.js loads leagues via
GET /api/leagues and saves with PATCH /api/leagues/{league_id}.
This is database configuration — changes apply immediately as the new
baseline for matches that use league defaults (no match override_*).
Fields¶
| Field | Column | Notes |
|---|---|---|
| Max questions | default_question_max_count |
Hard ceiling on in-game issuances per match |
| Max pivotal moments | default_question_pivotal_max_count |
Hard ceiling on pivotal-moment issuances per match (default 4) |
| Min interval after resolution (seconds) | default_question_min_interval |
Match-wide floor; 0 lets definitions pace |
| Question points | default_question_points |
Absolute points for a correct answer |
| Answer window (seconds) | default_answer_window_seconds |
No per-match override |
| Period distribution (JSON) | default_question_period_distribution |
Period/inning/half → [normal, pivotal] (or bare int ⇒ pivotal 0); {} = Issue ASAP; [] / {"dist": "even"} = even auto-fill at listener start (default pivotal 1); unlisted: block treats omitted keys as 0 |
| Question definitions | default_question_definition_ids |
Folder tree of definition ids. When allowlist is null (All), checkboxes reflect Include in league defaults (is_active) on each non-archived definition; Save persists those flags and keeps null. None writes []. Explicit lists still store flat id subsets. Matches that inherit (override_* is null) pick up later edits; a pinned match list does not. |
| Match Questions max | default_match_question_max_count |
Cap on pre-game Match questions (default 1). Independent of in-game max questions. |
| Match question definitions | default_match_question_definition_ids |
Same folder tree and null / [] / subset semantics as in-game. |
| Match question resolution grace (seconds) | default_match_question_resolution_grace_seconds |
Wait after complete/closed before stale/award-all (default 900). Postpone/cancel/unnecessary skip this wait and stamp those statuses as the close reason. |
| Match question earliest issue (seconds before start) | default_match_question_earliest_issue_seconds |
Match questions appear no earlier than this many seconds before scheduled start (default 900 / 15 minutes). 0 issues as soon as the match exists. Not issued after lock or once in-game questions exist. |
| Resolve success copy | default_resolve_success_copies |
Phrases stamped as resolve_copy when the player is right |
| Resolve failed copy | default_resolve_failed_copies |
Phrases stamped as resolve_copy when the player is wrong |
| Resolve copy template | default_resolve_copy |
{{resolve_copy}} - {{outcome_text}} by default. Play and push notifications compose this line. Blank outcome_text uses the phrase only. |
Each field label has a ? help modal. Period distribution must be a JSON
object with string keys and integer / [normal, pivotal] values, {} (Issue
ASAP), [], or {"dist": "even", …} — same rules as match overrides. dist
is reserved for strategies; today only "even" (others may be added later).
For registered leagues (MLB), even sentinels are replaced at listener start
with an even split of remaining max as [n, 1] pairs; see
Auto even period distribution.
{} never auto-fills. Extend another league by adding keys in
AUTO_PERIOD_DISTRIBUTION_KEYS.
Workflow¶
- Open Manage → League Defaults.
- Select a league (list comes from the API, not hardcoded slugs).
- Edit defaults and Save defaults. Switching leagues with unsaved changes prompts to discard.
Per-match overrides remain on Matches. To see how layers combine without editing, use Question Inheritance. To reconstruct why a published question issued, use Issuance Explorer. See Question definitions for in-game league / match / definition layers, and Match question issuance & resolution for pre-game Match questions.
Code map¶
| Concern | Location |
|---|---|
| REST list / get / patch | GET/PATCH /api/leagues, src/api/routes/leagues.py |
| Ops + schemas | src/lib/leagues/ |
| Period-distribution validation | src/lib/period_distribution.py |
| Admin HTML shell | src/api/admin/routes/league_defaults.py |
| Template | src/api/admin/templates/league_defaults/index.jinja2 |
| Alpine app | src/api/admin/static/js/league_defaults_app.js |
| CSS | src/api/admin/static/css/league_defaults.css |
| Tests | src/api/tests/admin/test_league_defaults.py, src/api/tests/test_leagues_api.py |
Common failures¶
- Invalid JSON: period distribution must be a JSON object /
[]with string period keys, integer or[normal, pivotal]values, and optionaldist(today only"even"). - League not found: the internal league id no longer exists.
- Unexpected match behavior: check match
override_*first — a match override still wins over the league baseline.