wrdler / specs /leaderboard_spec.md
Surn's picture
v0.2.5
082223e

A newer version of the Streamlit SDK is available: 1.52.1

Upgrade

ο»Ώ# Wrdler Leaderboard System Specification

Document Version: 1.4.0 Project Version: 0.2.0 Author: GitHub Copilot Last Updated: 2025-12-08 Status: βœ… Implemented and Documented


Table of Contents

  1. Executive Summary
  2. Goals and Objectives
  3. System Architecture
  4. Data Models
  5. New Python Modules
  6. Implementation Steps
  7. Version Changes
  8. File Changes Summary
  9. API Reference
  10. UI Components
  11. Testing Requirements
  12. Migration Notes
  13. Operational Considerations

1. Executive Summary

This specification documents the implemented Daily and Weekly Leaderboard System for Wrdler. The system:

  • βœ… Tracks top 25 scores for daily leaderboards (resets at UTC midnight)
  • βœ… Tracks top 25 scores for weekly leaderboards (resets at UTC Monday 00:00)
  • βœ… Creates separate leaderboards for each unique combination of game-affecting settings
  • βœ… Automatically adds qualifying scores from any game completion (including challenge mode)
  • βœ… Provides a dedicated leaderboard page with historical lookup capabilities
  • βœ… Stores leaderboard data in HuggingFace repository using existing storage infrastructure
  • βœ… Uses folder-based discovery (no index.json) with descriptive folder names
  • βœ… Uses a unified JSON format consistent with existing challenge settings.json files

Implementation Status: All features complete and deployed as of version 0.2.0


2. Goals and Objectives

Primary Goals

  1. Settings-Based Leaderboards: Each unique combination of game-affecting settings creates a separate leaderboard. Players using different settings compete on different leaderboards.

  2. Daily Leaderboards: Create and maintain daily leaderboards with top 25 entries displayed (can store more), organized by date folders (e.g., games/leaderboards/daily/2025-01-27/)

  3. Weekly Leaderboards: Create and maintain weekly leaderboards with top 25 entries displayed (can store more), organized by ISO week folders (e.g., games/leaderboards/weekly/2025-W04/)

  4. Automatic Qualification: Every game completion (challenge or solo) checks if score qualifies for the matching daily/weekly leaderboard based on current settings

  5. Leaderboard Page: New Streamlit page displaying:

    • Last 7 days of daily leaderboards (filtered by current settings)
    • Current weekly leaderboard (filtered by current settings)
    • Historical lookup via dropdown
  6. Folder-Based Discovery: No index.json file. Leaderboards are discovered by scanning folder names. Folder names include settings info for fast filtering.

  7. Unified File Format: Leaderboard files use the same structure as challenge settings.json with an entry_type field to distinguish between "daily", "weekly", and "challenge" entries

Secondary Goals

  • Maintain backward compatibility with existing challenge system
  • Minimize HuggingFace API calls through caching
  • Support sorting by score (descending), then time (ascending), then difficulty (descending)
  • Use challenge_id as the primary identifier across all entry types

Game-Affecting Settings

The following settings define a unique leaderboard:

Setting Type Description
game_mode string "classic", "easy", "too easy"
wordlist_source string Wordlist file (e.g., "classic.txt", "easy.txt")
show_incorrect_guesses bool Whether incorrect guesses are shown
enable_free_letters bool Whether free letters feature is enabled
puzzle_options object Puzzle configuration (spacer, may_overlap)

Example: A player using game_mode: "easy" with wordlist_source: "easy.txt" competes on a different leaderboard than a player using game_mode: "classic" with wordlist_source: "classic.txt".


3. System Architecture

3.1 Storage Structure

Each date/week has settings-based subfolders. The folder name (file_id) encodes the settings for fast discovery. All leaderboards use settings.json as the filename (consistent with challenges):

HF_REPO_ID/
β”œβ”€β”€ games/                          # All game-related storage
β”‚   β”œβ”€β”€ {challenge_id}/             # Existing challenge storage
β”‚   β”‚   └── settings.json           # entry_type: "challenge"
β”‚   └── leaderboards/
β”‚       β”œβ”€β”€ daily/
β”‚       β”‚   β”œβ”€β”€ 2025-01-27/
β”‚       β”‚   β”‚   β”œβ”€β”€ classic-classic-0/
β”‚       β”‚   β”‚   β”‚   └── settings.json
β”‚       β”‚   β”‚   └── easy-easy-0/
β”‚       β”‚   β”‚       └── settings.json
β”‚       β”‚   └── 2025-01-26/
β”‚       β”‚       └── classic-classic-0/
β”‚       β”‚           └── settings.json
β”‚       └── weekly/
β”‚           β”œβ”€β”€ 2025-W04/
β”‚           β”‚   β”œβ”€β”€ classic-classic-0/
β”‚           β”‚   β”‚   └── settings.json
β”‚           β”‚   └── easy-too_easy-0/
β”‚           β”‚       └── settings.json
β”‚           └── 2025-W03/
β”‚               └── classic-classic-0/
β”‚                   └── settings.json
└── shortener.json                  # Existing URL shortener

3.2 File ID Format

The file_id (folder name) encodes settings for discovery without an index:

{wordlist_source}-{game_mode}-{sequence}

Examples:

  • classic-classic-0 - Classic wordlist, classic mode, first instance
  • easy-easy-0 - Easy wordlist, easy mode, first instance
  • classic-too_easy-1 - Classic wordlist, "too easy" mode, second instance

Sanitization Rules:

  • .txt extension is removed from wordlist_source
  • Spaces are replaced with underscores
  • All lowercase

3.3 Folder-Based Discovery

Instead of maintaining an index.json file, leaderboards are discovered by:

  1. List period folders: Scan games/leaderboards/daily/ or games/leaderboards/weekly/ for date/week folders
  2. List file_id folders: For each period, scan for settings folders
  3. Filter by prefix: Match file_ids that start with {wordlist_source}-{game_mode}-
  4. Load and verify: Load settings.json to verify full settings match

Benefits:

  • No index synchronization issues
  • Self-documenting folder structure
  • Can browse folders directly
  • Reduced write operations (no index updates)

3.4 Data Flow

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Game Completion   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
          β”‚
          β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Get current game   β”‚
β”‚ settings           β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
          β”‚
          β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Build file_id      β”‚
β”‚ prefix from        β”‚
β”‚ settings           β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
          β”‚
          β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Scan folders for   β”‚
β”‚ matching file_id   β”‚
β”‚ or create new      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
          β”‚
    β”Œβ”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”
    β”‚           β”‚
    β–Ό           β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Daily β”‚ β”‚ Weekly    β”‚
β”‚ LB    β”‚ β”‚ LB        |
β””β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
    β”‚          β”‚
    β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜
         β”‚
         β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Check if score      β”‚
β”‚ qualifies (top      β”‚
β”‚ 25 displayed)       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
          β”‚
          β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Update & Upload     β”‚
β”‚ to HF repo          β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

4. Data Models

4.1 Entry Type Definition

The entry_type field distinguishes between different types of game entries:

entry_type Description Storage Location
"challenge" Player-created challenge for others to compete games/{challenge_id}/settings.json
"daily" Daily leaderboard entry games/leaderboards/daily/{date}/{file_id}/settings.json
"weekly" Weekly leaderboard entry games/leaderboards/weekly/{week}/{file_id}/settings.json

4.2 Unified File Schema (Consistent with Challenge settings.json)

Both leaderboard files and challenge files use the same base structure. The settings in the file define what makes this leaderboard unique:

{
  "challenge_id": "2025-01-27/classic-classic-0",
  "entry_type": "daily",
  "game_mode": "classic",
  "grid_size": 8,
  "puzzle_options": {
    "spacer": 0,
    "may_overlap": false
  },
  "users": [
    {
      "uid": "20251130T190249Z-0XLG5O",
      "username": "Charles",
      "word_list": ["CHEER", "PLENTY", "REAL", "ARRIVE", "DEAF", "DAILY"],
      "word_list_difficulty": 117.48,
      "score": 39,
      "time": 132,
      "timestamp": "2025-11-30T19:02:49.544933+00:00",
      "source_challenge_id": null
    }
  ],
  "created_at": "2025-11-30T19:02:49.544933+00:00",
  "version": "0.2.0",
  "show_incorrect_guesses": true,
  "enable_free_letters": true,
  "wordlist_source": "classic.txt",
  "game_title": "Wrdler Gradio AI",
  "max_display_entries": 25
}

4.3 Field Descriptions

Field Type Description
challenge_id string Unique identifier. For daily: "2025-01-27/classic-classic-0", weekly: "2025-W04/easy-easy-0", challenge: "20251130T190249Z-ABCDEF"
entry_type string One of: "daily", "weekly", "challenge"
game_mode string Game difficulty: "classic", "easy", "too easy"
grid_size int Grid width (8 for Wrdler)
puzzle_options object Puzzle configuration (defines leaderboard uniqueness)
users array Array of user entries (sorted by score desc, time asc, difficulty desc)
created_at string ISO 8601 timestamp when entry was created
version string Schema version
show_incorrect_guesses bool Display setting (defines leaderboard uniqueness)
enable_free_letters bool Free letters feature toggle (defines leaderboard uniqueness)
wordlist_source string Source wordlist file (defines leaderboard uniqueness)
game_title string Game title for display
max_display_entries int Maximum entries to display (default 25, configurable via MAX_DISPLAY_ENTRIES env var)

4.4 User Entry Schema

Each user entry in the users array:

{
  "uid": "20251130T190249Z-0XLG5O",
  "username": "Charles",
  "word_list": ["CHEER", "PLENTY", "REAL", "ARRIVE", "DEAF", "DAILY"],
  "word_list_difficulty": 117.48,
  "score": 39,
  "time": 132,
  "timestamp": "2025-11-30T19:02:49.544933+00:00",
  "source_challenge_id": null
}
Field Type Description
uid string Unique user entry ID
username string Player display name
word_list array 6 words played
word_list_difficulty float Calculated difficulty score
score int Final score
time int Time in seconds
timestamp string ISO 8601 when entry was recorded
source_challenge_id string|null If from a challenge, the original challenge_id

4.5 Settings Matching

Two leaderboards are considered the same if ALL of the following match:

  • game_mode
  • wordlist_source (after sanitization - .txt removed, lowercase)
  • show_incorrect_guesses
  • enable_free_letters
  • puzzle_options.spacer
  • puzzle_options.may_overlap

4.6 Weekly Leaderboard Naming

Uses ISO 8601 week numbering:

  • Format: YYYY-Www (e.g., 2025-W04)
  • Week starts on Monday
  • Week 1 is the week containing the first Thursday of the year

5. New Python Modules

5.1 wrdler/leaderboard.py (NEW FILE)

Purpose: Core leaderboard logic for managing daily and weekly leaderboards with settings-based separation and folder-based discovery.

Key classes:

  • GameSettings - Settings that define a unique leaderboard
  • UserEntry - Single user entry in a leaderboard
  • LeaderboardSettings - Unified leaderboard/challenge settings format

Key functions:

  • _sanitize_wordlist_source() - Remove .txt extension and normalize
  • _build_file_id() - Create file_id from settings
  • _parse_file_id() - Parse file_id into components
  • find_matching_leaderboard() - Find leaderboard by scanning folders
  • create_or_get_leaderboard() - Get or create a leaderboard
  • submit_score_to_all_leaderboards() - Main entry point for submissions

5.2 wrdler/modules/storage.py (UPDATED)

Added functions:

  • _list_repo_folders() - List folder names under a path in HuggingFace repo
  • _list_repo_files_in_folder() - List files in a folder

6. Implementation Steps

Phase 1: Core Leaderboard Module (v0.2.0-alpha) βœ… COMPLETE

Step Task Files Status
1.1 Create wrdler/leaderboard.py with GameSettings and data models NEW βœ… Complete
1.2 Implement folder listing in storage.py storage.py βœ… Complete (_list_repo_folders)
1.3 Implement find_matching_leaderboard() with folder scanning leaderboard.py βœ… Complete
1.4 Implement check_qualification() and sorting leaderboard.py βœ… Complete (_sort_users)
1.5 Implement submit_to_leaderboard() and submit_score_to_all_leaderboards() leaderboard.py βœ… Complete
1.6 Write unit tests for leaderboard logic including settings matching tests/test_leaderboard.py ⏳ Recommended

Phase 2: UI Integration (v0.2.0-beta) βœ… COMPLETE

Step Task Files Status
2.1 Create wrdler/leaderboard_page.py with settings filtering NEW βœ… Complete
2.2 Add leaderboard navigation to ui.py sidebar ui.py βœ… Complete (footer menu)
2.3 Integrate score submission in _game_over_content() with current settings ui.py βœ… Complete
2.4 Add leaderboard results display in game over dialog ui.py βœ… Complete
2.5 Style leaderboard tables to match ocean theme leaderboard_page.py βœ… Complete (pandas dataframe styling)

Phase 3: Challenge Format Migration (v0.2.0-beta) βœ… COMPLETE

Step Task Files Status
3.1 Add entry_type field to existing challenge saves game_storage.py βœ… Complete
3.2 Update challenge loading to handle entry_type game_storage.py βœ… Complete (defaults to "challenge")
3.3 Test backward compatibility with old challenges Manual βœ… Verified

Phase 4: Testing & Polish (v0.2.0-rc) βœ… COMPLETE

Step Task Files Status
4.1 Integration testing with HuggingFace Manual βœ… Verified
4.2 Add caching for leaderboard data leaderboard.py ⏳ Future optimization
4.3 Add error handling and retry logic leaderboard.py βœ… Complete (logging)
4.4 Update documentation README.md, specs/, CLAUDE.md βœ… Complete
4.5 Version bump and release notes pyproject.toml, init.py βœ… Complete (v0.2.0)

7. Version Changes

pyproject.toml

[project]
name = "wrdler"
version = "0.2.0"  # Updated from 0.1.0
description = "Wrdler vocabulary puzzle game with daily/weekly leaderboards"

wrdler/init.py

__version__ = "0.2.0"  # Updated from existing version

wrdler/game_storage.py

__version__ = "0.2.0"  # Updated from 0.1.5

wrdler/leaderboard.py (NEW)

__version__ = "0.2.0"

wrdler/leaderboard_page.py (NEW)

__version__ = "0.2.0"

wrdler/modules/storage.py

__version__ = "0.1.6"  # Updated to add folder listing functions

8. File Changes Summary

New Files

File Purpose
wrdler/leaderboard.py Core leaderboard logic with folder-based discovery
wrdler/leaderboard_page.py Streamlit leaderboard page
tests/test_leaderboard.py Unit tests for leaderboard
specs/leaderboard_spec.md This specification

Modified Files

File Changes
pyproject.toml Version bump to 0.2.0
wrdler/__init__.py Version bump, add leaderboard exports
wrdler/modules/storage.py Add _list_repo_folders() and _list_repo_files_in_folder()
wrdler/game_storage.py Version bump, add entry_type field, integrate leaderboard submission
wrdler/ui.py Add leaderboard nav, integrate submission in game over
wrdler/modules/__init__.py Export new functions if needed

9. API Reference

Public Functions in wrdler/leaderboard.py

def submit_score_to_all_leaderboards(
    username: str,
    score: int,
    time_seconds: int,
    word_list: List[str],
    settings: GameSettings,
    word_list_difficulty: Optional[float] = None,
    source_challenge_id: Optional[str] = None,
    repo_id: Optional[str] = None
) -> Dict[str, Any]:
    """Main entry point for submitting scores after game completion."""

def load_leaderboard(
    entry_type: EntryType,
    period_id: str,
    file_id: str,
    repo_id: Optional[str] = None
) -> Optional[LeaderboardSettings]:
    """Load a specific leaderboard by file ID."""

def find_matching_leaderboard(
    entry_type: EntryType,
    period_id: str,
    settings: GameSettings,
    repo_id: Optional[str] = None
) -> Tuple[Optional[str], Optional[LeaderboardSettings]]:
    """Find a leaderboard matching given settings."""

def get_last_n_daily_leaderboards(
    n: int = 7,
    settings: Optional[GameSettings] = None,
    repo_id: Optional[str] = None
) -> List[Tuple[str, Optional[LeaderboardSettings]]]:
    """Get recent daily leaderboards for display."""

def list_available_periods(
    entry_type: EntryType,
    limit: int = 30,
    repo_id: Optional[str] = None
) -> List[str]:
    """List available period IDs from folder structure."""

def list_settings_for_period(
    entry_type: EntryType,
    period_id: str,
    repo_id: Optional[str] = None
) -> List[Dict[str, Any]]:
    """List all settings combinations for a period."""

def get_current_daily_id() -> str:
    """Get today's period ID."""

def get_current_weekly_id() -> str:
    """Get this week's period ID."""

10. UI Components

10.1 Sidebar Navigation

Add to _render_sidebar() in ui.py:

st.header("Navigation")
if st.button("πŸ† Leaderboards", use_container_width=True):
    st.session_state["show_leaderboard_page"] = True
    st.rerun()

10.2 Game Over Integration

Modify _game_over_content() in ui.py to:

  1. Call submit_score_to_all_leaderboards() after generating share link
  2. Display qualification results:
# After score submission
if results["daily"]["qualified"]:
    st.success(f"πŸ† You ranked #{results['daily']['rank']} on today's leaderboard!")
if results["weekly"]["qualified"]:
    st.success(f"πŸ† You ranked #{results['weekly']['rank']} on this week's leaderboard!")

10.3 Leaderboard Page Routing

In run_app():

# Check if leaderboard page should be shown
if st.session_state.get("show_leaderboard_page", False):
    from wrdler.leaderboard_page import render_leaderboard_page
    render_leaderboard_page()
    if st.button("← Back to Game"):
        st.session_state["show_leaderboard_page"] = False
        st.rerun()
    return  # Don't render game UI

11. Testing Requirements

Unit Tests (tests/test_leaderboard.py)

class TestUserEntry:
    def test_create_entry(self): ...
    def test_to_dict_roundtrip(self): ...
    def test_from_legacy_time_seconds_field(self): ...

class TestLeaderboardSettings:
    def test_create_leaderboard(self): ...
    def test_entry_type_values(self): ...
    def test_get_display_users_limit(self): ...
    def test_format_matches_challenge(self): ...

class TestGameSettings:
    def test_settings_matching_same(self): ...
    def test_settings_matching_different_mode(self): ...
    def test_settings_matching_txt_extension_ignored(self): ...
    def test_get_file_id_prefix(self): ...

class TestFileIdFunctions:
    def test_sanitize_wordlist_source_removes_txt(self): ...
    def test_build_file_id(self): ...
    def test_parse_file_id(self): ...

class TestQualification:
    def test_qualify_empty_leaderboard(self): ...
    def test_qualify_not_full(self): ...
    def test_qualify_by_score(self): ...
    def test_qualify_by_time_tiebreaker(self): ...
    def test_qualify_by_difficulty_tiebreaker(self): ...
    def test_not_qualify_lower_score(self): ...

class TestDateIds:
    def test_daily_id_format(self): ...
    def test_weekly_id_format(self): ...
    def test_daily_path(self): ...  # Tests new folder structure
    def test_weekly_path(self): ...  # Tests new folder structure

Integration Tests

  • Test full flow: game completion β†’ leaderboard submission β†’ retrieval
  • Test with mock HuggingFace repository
  • Test folder-based discovery logic
  • Test concurrent submissions (edge case)
  • Test backward compatibility with legacy challenge files (no entry_type)

12. Migration Notes

Backward Compatibility

  • Existing challenges continue to work unchanged (entry_type defaults to "challenge")
  • No changes to shortener.json format
  • Challenge settings.json format is extended (new fields are optional)
  • No index.json migration needed - folder-based discovery is self-contained

Schema Evolution

Version Changes
0.1.x Original challenge format
0.2.0 Added entry_type, max_display_entries, source_challenge_id fields
0.2.0 Changed to folder-based discovery (no index.json)
0.2.0 New folder structure: games/leaderboards/{type}/{period}/{file_id}/settings.json

Data Migration

  • No migration required for existing challenges
  • New leaderboard files use folder-based storage from start
  • Legacy challenges without entry_type default to "challenge"

Rollback Plan

  1. Remove leaderboard imports from ui.py
  2. Remove sidebar navigation button
  3. Remove game over submission calls
  4. Optionally: delete games/leaderboards/ directory from HF repo

13. Implementation Notes

13.1 Actual Implementation Details

The following represents the actual implementation as of v0.2.0:

Core Modules Implemented

wrdler/leaderboard.py (v0.2.0):

  • βœ… GameSettings dataclass with settings matching logic
  • βœ… UserEntry dataclass for individual scores
  • βœ… LeaderboardSettings dataclass (unified format)
  • βœ… File ID sanitization and parsing functions
  • βœ… Folder-based discovery with _list_period_folders() and _list_file_ids_for_period()
  • βœ… find_matching_leaderboard() with prefix filtering and full verification
  • βœ… create_or_get_leaderboard() with automatic sequence management
  • βœ… submit_score_to_all_leaderboards() as main entry point
  • βœ… check_qualification() for top 25 filtering
  • βœ… Period ID generators: get_current_daily_id(), get_current_weekly_id()
  • βœ… Historical lookup functions: list_available_periods(), list_settings_for_period()
  • βœ… URL generation with get_leaderboard_url()

wrdler/leaderboard_page.py (v0.2.0):

  • βœ… Four-tab navigation system (Today, Daily, Weekly, History)
  • βœ… Query parameter routing (?page=, ?gidd=, ?gidw=)
  • βœ… Settings badge display with full configuration info
  • βœ… Styled pandas dataframes with rank emojis (πŸ₯‡πŸ₯ˆπŸ₯‰)
  • βœ… Challenge indicator badges (🎯)
  • βœ… Expandable leaderboard groups per settings combination
  • βœ… Last updated timestamps and entry counts

wrdler/modules/storage.py (Updated):

  • βœ… _list_repo_folders() for folder discovery
  • βœ… Seamless integration with existing HF dataset functions

wrdler/ui.py (Updated):

  • βœ… Footer menu integration for leaderboard page navigation
  • βœ… Automatic submission call in game over flow
  • βœ… Current settings extraction via GameSettings
  • βœ… Display qualification results with rank notifications

Storage Implementation

Folder Structure (as built):

HF_REPO_ID/games/
β”œβ”€β”€ leaderboards/
β”‚   β”œβ”€β”€ daily/
β”‚   β”‚   └── {YYYY-MM-DD}/
β”‚   β”‚       └── {wordlist_source}-{game_mode}-{sequence}/
β”‚   β”‚           └── settings.json
β”‚   └── weekly/
β”‚       └── {YYYY-Www}/
β”‚           └── {wordlist_source}-{game_mode}-{sequence}/
β”‚               └── settings.json
└── {challenge_id}/
    └── settings.json

File ID Sanitization:

  • .txt extension removed from wordlist_source
  • Spaces replaced with underscores
  • All lowercase
  • Regex: r'[^\w\-]' replaced with _

UI/UX Features Implemented

Today Tab:

  • Displays current daily and weekly leaderboards side-by-side in two columns
  • Query parameter filtering: ?gidd={file_id} and ?gidw={file_id} show specific leaderboards
  • Expandable settings groups with full configuration captions

Daily Tab:

  • Shows last 7 days of daily leaderboards
  • One expander per date with all settings combinations nested
  • Today's date auto-expanded by default

Weekly Tab:

  • Shows current ISO week leaderboard
  • All settings combinations displayed in expandable groups

History Tab:

  • Two-column layout: Daily (left) and Weekly (right)
  • Dropdown selectors for period and settings combination
  • "Load Daily" and "Load Weekly" buttons for explicit loading

Table Styling:

  • Pandas DataFrame with custom CSS styling
  • Rank column: large bold text with emojis
  • Score column: green color (#20d46c) with bold font
  • Challenge indicator: 🎯 badge appended to username
  • Last updated timestamp and entry count displayed below table

Key Differences from Spec

  1. Navigation: Implemented as 'Leaderboard' link in the footer menu instead of sidebar button
  2. Caching: Not implemented in v0.2.0 (deferred to v0.3.0 for optimization)
  3. Tab Implementation: Used query parameters with custom nav links instead of Streamlit native tabs for better URL support
  4. Table Rendering: Used pandas DataFrames with styling instead of custom HTML tables

Known Limitations (as of v0.2.0)

  1. No caching: Each page load fetches from HF repository (can be slow)
  2. No pagination: Displays top 25 only (additional entries stored but not shown)
  3. Limited error handling: Basic logging, could benefit from retry logic
  4. No rate limiting: Submission frequency not constrained
  5. No archival: Old leaderboards remain indefinitely (no cleanup script)

Future Enhancements (Planned for v0.3.0+)

  • ⏳ In-memory caching with TTL (60s for periods, 15s for leaderboards)
  • ⏳ Pagination for >25 entries
  • ⏳ Retry logic with exponential backoff
  • ⏳ Rate limiting per IP/session
  • ⏳ Archival script for old periods (>365 days daily, >156 weeks weekly)
  • ⏳ Manual refresh button in UI

14. Operational Considerations

14.1 Concurrency and Consistency

  • Write model:
    • Use optimistic concurrency: read settings.json, merge new users entry, write back with a unique commit message including challenge_id and uid.
    • Implement retry with exponential backoff on HTTP 409/5xx or checksum mismatch.
    • Ensure atomicity per file write: do not split updates across multiple files per leaderboard.
  • Simultaneous file creation:
    • When no matching file_id folder exists, first attempt creation; if a concurrent process creates it, fallback to loading and merging.
    • Always re-verify settings match by reading settings.json after folder discovery.
  • Sequence collisions:
    • If {wordlist}-{mode}-{sequence} collides but settings differ, increment sequence until a unique folder is found; verify match via file content, not only prefix.

14.2 Caching and Discovery Performance

  • Cache tiers:
    • In-memory (per app session):
      • Period listings for games/leaderboards/{type}/ (TTL 60s).
      • file_id listings inside a period (TTL 30s).
      • Loaded settings.json for leaderboards (TTL 15s or invalidated on write).
  • Invalidation:
    • On successful submission, invalidate the specific leaderboard cache (file content and directory listing for that period).
    • Provide a manual refresh option in UI (leaderboard page).
  • Discovery limits:
    • Cap directory scans to the most recent N periods (configurable; default 30). UI uses explicit period selection for older data.
    • Prefer prefix filtering client-side before loading file content.

14.3 Error Handling and Observability

  • Error taxonomy:
    • Storage errors: HF_STORAGE_UNAVAILABLE, HF_WRITE_CONFLICT, HF_NOT_FOUND.
    • Validation errors: LB_INVALID_INPUT, LB_SETTINGS_MISMATCH.
    • Operational errors: LB_TIMEOUT, LB_RETRY_EXCEEDED.
  • User feedback:
    • On non-critical failure (e.g., leaderboard write conflict), show non-blocking warning and retry silently up to 3 times.
  • Logging:
    • Log submission events with fields: entry_type, period_id, file_id, uid, score, time, rank_result, repo_path, latency_ms.
    • Log error events with code, message, attempt, backoff_ms.
  • Telemetry (optional):
    • Count successful submissions per period and per settings combination for basic monitoring.

14.4 Security and Abuse Controls

  • Input validation:
    • username: max 40 chars, strip control chars, allow alphanumerics, spaces, basic punctuation; reject offensive content if possible.
    • word_list: array of 6 uppercase A–Z strings, length 3–10; drop invalid entries.
    • score: 0–999; time: 1–36000 (10 hours); word_list_difficulty: float if provided, clamp to 0–10000.
  • Spam and duplicates:
    • Rate limit per IP/session (e.g., max 10 submissions per hour).
    • Detect duplicate entries by same uid + timestamp within 10 seconds window; deduplicate silently.
  • Repository permissions:
    • Submissions require HF write permissions for the space; ensure credentials are scoped to the specific repo.
    • Do not expose write tokens in client logs; keep server-side commit operations.

14.5 Data Lifecycle and Retention

  • Retention:
    • Keep daily leaderboards for 365 days; weekly leaderboards for 156 weeks (3 years).
    • Optional archival: move older periods to games/leaderboards_archive/{type}/ or leave as-is with documented retention.
  • Cleanup:
    • Provide a maintenance script to prune old periods and reindex cache.
  • Privacy:
    • Store only display names and gameplay metrics; avoid PII.
    • Users must enter a name (Anonymous not allowed); do not display IP or identifiers publicly.

14.6 Time and Period Boundaries

  • Timezone:
    • All operations use UTC. The periods roll over at 00:00:00 UTC for daily, Monday 00:00:00 UTC for weekly.
  • ISO week:
    • Use Python’s isocalendar() to derive YYYY-Www and handle year transitions (weeks spanning year boundaries).
  • Clock source:
    • Use server-side timestamp for submissions; do not trust client clock. If unavailable, fall back to Python datetime.utcnow().

14.7 UI Reliability and UX

  • Loading states:
    • Show skeleton/loading indicators while scanning folders or reading leaderboard JSON.
  • Empty states:
    • Display β€œNo entries yet” when a leaderboard exists without users or has not been created.
  • Accessibility:
    • Ensure sufficient color contrast, keyboard navigation for tabs/period selectors, and alt text for icons.
  • Internationalization (future):
    • Keep date/time ISO formatting and English labels; design UI to allow future localization.

14.8 Ranking and Tie-Breaks (Operational Clarification)

  • Sort order:
    • Primary: score desc; secondary: time asc; tertiary: word_list_difficulty desc; quaternary: stable by timestamp asc.
  • Display limit:
    • Always store full users list; apply max_display_entries at render time only.
  • Rank reporting:
    • Return rank based on full sorted list even if not displayed; if outside display limit, mark qualified=False.

14.9 Commit and Retry Strategy (HF)

  • Commit messages:
    • Format: leaderboard:{entry_type}:{period_id}:{file_id} add uid={uid} score={score} time={time}.
  • Retries:
    • Backoff sequence: 0.25s, 0.5s, 1s; max 3 attempts; abort on LB_SETTINGS_MISMATCH.
  • Partial failures:
    • If daily succeeds and weekly fails (or vice versa), return both statuses independently; UI reports partial success.

14.10 Timezone Handling

  • Daily leaderboard files use UTC for period boundaries.
  • When displaying, show the UTC period as a PST date range: For daily leaderboards, display the period as: "YYYY-MM-DD 00:00:00 UTC to YYYY-MM-DD 23:59:59 UTC" and "YYYY-MM-DD HH:MM:SS PST to YYYY-MM-DD HH:MM:SS PST" (PST is UTC-8; adjust for daylight saving as needed) For example, a UTC file date of 2025-12-08 covers 2025-12-08 00:00:00 UTC to 2025-12-08 23:59:59 UTC, which is displayed as 2025-12-07 16:00:00 PST to 2025-12-08 15:59:59 PST. The leaderboard expander label should show: Mon, Dec 08, 2025 4:00 PM PST – Tue, Dec 09, 2025 3:59:59 PM PST [settings badge]