Skip to content

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

  1. Open Manage → League Defaults.
  2. Select a league (list comes from the API, not hardcoded slugs).
  3. 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 optional dist (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.