Skip to content

Commit 64a3ee4

Browse files
ChuckBuildsChuckclaude
authored
feat(lacrosse): add NCAA men's and women's lacrosse scoreboard plugin (#91)
* feat(lacrosse): add NCAA men's and women's lacrosse scoreboard plugin New plugin providing live, recent, and upcoming NCAA Division I men's and women's lacrosse games via the ESPN public API, modeled on the hockey scoreboard plugin. Features: - NCAA Men's and Women's lacrosse (ESPN `mens-college-lacrosse` and `womens-college-lacrosse` endpoints) - Live games with quarter, clock, and score (Q1-Q4, OT1+) - Recent and upcoming game modes with records - Favorite-team filtering, with dynamic `NCAA_MENS_TOP_5/10/20` and `NCAA_WOMENS_TOP_5/10/25` shortcuts that resolve against the live Inside Lacrosse / IWLCA poll feeds - Poll rank overlays on live/recent/upcoming game cards - Per-mode switch vs. scroll display style - Live priority, configurable durations, update intervals, and game counts per league - Standalone smoke test (`test_lacrosse_plugin.py`) exercising imports, dynamic team resolution, and real-data extraction for both leagues Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(lacrosse): address PR review findings Bumps to 1.0.2 with fixes across the plugin based on PR #91 review: - dynamic_team_resolver: per-token TTL instead of a single shared timestamp, so fetching one poll doesn't extend the apparent freshness of unrelated cached tokens - game_renderer: use ImageFont.load (not ImageFont.truetype) for BDF bitmap fonts, with a warning + fallback on failure - logo_downloader: replace bare except on font load with except (OSError, IOError) + debug log; add explicit Optional[str] annotation on logo_url - ncaam/ncaaw managers: remove unused _processed_games_cache, _shared_data, _no_data_warning_logged and related class attrs that were dead cruft from the hockey template; guard len(result.data.get('events')) against None in the fetch callback - ncaam/ncaaw managers + manager adapter: use plain "live"/"recent" /"upcoming" display-mode keys consistently between the config schema, the config adapter, and the league managers (previously the adapter wrote "lacrosse_*" keys, which worked but was confusing) - lacrosse: extract _get_team_display_text helper to deduplicate the rank-vs-record logic; use game.get("odds") instead of the redundant membership+truthy guard; rename unused `situation` to `_situation`; explicit `return None` - scroll_display: collapse NCAAM/NCAAW lacrosse separator icon constants into a single NCAA_LACROSSE_SEPARATOR_ICON since ESPN ships one shared NCAA lacrosse mark - config_schema: unify update_intervals.base.maximum to 900 across both NCAA men's and women's blocks (was 600 vs 900) - test_lacrosse_plugin: compute the scoreboard date window from the current calendar year so the test doesn't go stale next season; skip (instead of fail) when the ESPN feed is unreachable by recognising requests.exceptions.RequestException and a sentinel _NetworkUnavailable exception Rejected findings (matching repo convention or not actual bugs): - LICENSE short notice: all 4 sibling sport plugins ship the same 17-line notice; fixing here would introduce inconsistency - User-Agent placeholder: identical across hockey/football/basketball - Pillow>=9.0.0: matches hockey; security bump should be repo-wide - README show_records/show_ranking example "conflict": the schema defaults are actually True, so the example already matches Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(lacrosse): address second round of PR review findings Bumps to 1.0.3 with correctness fixes and a refactor pass based on the second review: - game_renderer: mirror normalized status['clock'] into status['display_clock'] and fall back between the two keys in the live status renderer so flat payloads render the clock correctly; drop the external pytz dependency in the upcoming-game start-time fallback in favor of stdlib datetime.timezone.utc - lacrosse.py: wrap the shot-totals font load in a try/except that falls back to ImageFont.load_default(), matching the record-font pattern; make _test_mode_update resilient to empty/malformed clock strings via try/except around the MM:SS split - logo_downloader: download_missing_logo now returns False (not True) when it falls back to creating a placeholder, matching the docstring contract; the sole caller in sports.py discards the return value so this is behavior-compatible for existing callers - scroll_display: when loading separator icons, register the generic NCAA.png fallback under the lacrosse league keys so lookups for "ncaam_lacrosse" / "ncaaw_lacrosse" fall through to it when ncaa_lacrosse.png is missing; switch the registration loop to setdefault() so sport-specific icons always win over the generic fallback regardless of iteration order - test_lacrosse_plugin: detect partial network outages in the rankings resolver test — if either endpoint returns an empty list, raise _NetworkUnavailable with a per-endpoint message instead of failing the whole test - lacrosse.py + ncaam/ncaaw managers: extract the duplicated _fetch_ncaa_lacrosse_api_data body into a shared Lacrosse._fetch_season_schedule helper parameterized by sport key, cache prefix, URL, and season window; both manager modules now call it through thin wrappers - ncaam/ncaaw managers: replace the isinstance(self, *LiveManager) routing in _fetch_data with a polymorphic override on the Live subclass; base _fetch_data returns the season schedule, Live subclass overrides to call _fetch_todays_games() Rejected findings: - logo_downloader User-Agent placeholder (again): matches all four sibling sport plugins verbatim; this is repo convention, not a single-plugin issue - dynamic_team_resolver per-instance cache: the class-level cache is intentional to share fetched rankings across the six resolver instances in the plugin's lifecycle; moving it to __init__ would multiply ESPN API calls for no benefit. The per-token TTL bug that matters was already fixed in 1.0.2. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Chuck <chuck@example.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 411d968 commit 64a3ee4

18 files changed

Lines changed: 9911 additions & 1 deletion

plugins.json

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"version": "1.0.0",
3-
"last_updated": "2026-04-03",
3+
"last_updated": "2026-04-06",
44
"plugins": [
55
{
66
"id": "hello-world",
@@ -717,6 +717,29 @@
717717
"verified": true,
718718
"screenshot": "",
719719
"latest_version": "1.0.0"
720+
},
721+
{
722+
"id": "lacrosse-scoreboard",
723+
"name": "Lacrosse Scoreboard",
724+
"description": "Live, recent, and upcoming NCAA men's and women's lacrosse games with real-time scores and schedules",
725+
"author": "ChuckBuilds",
726+
"category": "sports",
727+
"tags": [
728+
"lacrosse",
729+
"ncaa",
730+
"sports",
731+
"scoreboard",
732+
"live-scores"
733+
],
734+
"repo": "https://github.com/ChuckBuilds/ledmatrix-plugins",
735+
"branch": "main",
736+
"plugin_path": "plugins/lacrosse-scoreboard",
737+
"stars": 0,
738+
"downloads": 0,
739+
"last_updated": "2026-04-06",
740+
"verified": true,
741+
"screenshot": "",
742+
"latest_version": "1.0.3"
720743
}
721744
]
722745
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
GNU GENERAL PUBLIC LICENSE
2+
Version 3, 29 June 2007
3+
4+
Copyright (C) 2025 LEDMatrix Team
5+
6+
This program is free software: you can redistribute it and/or modify
7+
it under the terms of the GNU General Public License as published by
8+
the Free Software Foundation, either version 3 of the License, or
9+
(at your option) any later version.
10+
11+
This program is distributed in the hope that it will be useful,
12+
but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
GNU General Public License for more details.
15+
16+
You should have received a copy of the GNU General Public License
17+
along with this program. If not, see <https://www.gnu.org/licenses/>.
Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
# Lacrosse Scoreboard Plugin
2+
3+
Live, recent, and upcoming NCAA Men's and Women's Lacrosse games on your LEDMatrix display. Real-time scores, schedules, favorite-team filtering, live-game priority, poll-rank badges, and both switch and scroll display modes — modeled on the existing hockey scoreboard plugin.
4+
5+
## Features
6+
7+
- **NCAA Men's Lacrosse** (Inside Lacrosse D1 Poll — top 20)
8+
- **NCAA Women's Lacrosse** (Inside Lacrosse / IWLCA Coaches Top 25 Poll)
9+
- **Live games** with quarter, clock, score, and optional shot totals
10+
- **Recent (completed) games** with final score and OT indicator
11+
- **Upcoming games** with start time, matchup, records, and rankings
12+
- **Favorite team filtering** — pin specific teams, or use the dynamic shortcuts `NCAA_MENS_TOP_20`, `NCAA_MENS_TOP_10`, `NCAA_MENS_TOP_5`, `NCAA_WOMENS_TOP_25`, `NCAA_WOMENS_TOP_10`, `NCAA_WOMENS_TOP_5` to auto-track whichever teams are currently in the poll
13+
- **Live priority** — force live favorite-team games to preempt the rotation
14+
- **Per-mode display style**`switch` (one game card rotating) or `scroll` (horizontal ticker), independently configurable for live, recent, and upcoming
15+
- **Poll rank badges**`#1`, `#2` overlays on team names, updated hourly from ESPN's public rankings feed
16+
- **Element customization** — toggle records, rankings, odds, shot totals; override layout offsets for logos, score, and status text
17+
- **Configurable durations, update intervals, and game counts** per league
18+
19+
## Requirements
20+
21+
- Python 3.9+
22+
- LEDMatrix core 2.0.0 or newer
23+
- A minimum display of 64×32 (128×32 recommended for full scroll and scoreboard layouts)
24+
- Internet access to reach the public ESPN API
25+
26+
No API key is required.
27+
28+
## Installation
29+
30+
The plugin is installable from the LEDMatrix plugin store — search for **Lacrosse Scoreboard** and enable it. On first launch, team logos for any teams appearing in the current scoreboard window will be downloaded to `assets/sports/ncaa_logos/` automatically.
31+
32+
To install manually from source:
33+
34+
```bash
35+
cd /path/to/LEDMatrix
36+
python -m pip install --user pillow requests pytz # see requirements.txt
37+
cp -r /path/to/ledmatrix-plugins/plugins/lacrosse-scoreboard plugins/
38+
```
39+
40+
Then add a `lacrosse-scoreboard` entry to your LEDMatrix `config.json` (see **Configuration** below) and restart the LEDMatrix service.
41+
42+
## Dependencies
43+
44+
From `requirements.txt`:
45+
46+
- `Pillow>=9.0.0` — image compositing and logo rendering
47+
- `requests>=2.28.0` — ESPN API calls
48+
- `pytz>=2022.1` — timezone conversion for game start times
49+
- `urllib3>=1.26.0` — HTTP retry logic
50+
51+
All dependencies are standard and already present in a typical LEDMatrix install.
52+
53+
## Configuration
54+
55+
The plugin config is split into per-league blocks. See `config_schema.json` for the authoritative list of fields and their defaults. Minimal working example:
56+
57+
```json
58+
{
59+
"enabled": true,
60+
"defaults": {
61+
"display_duration": 15,
62+
"show_records": true,
63+
"show_ranking": true,
64+
"show_odds": false
65+
},
66+
"ncaa_mens": {
67+
"enabled": true,
68+
"display_modes": {
69+
"live": true,
70+
"live_display_mode": "switch",
71+
"recent": true,
72+
"recent_display_mode": "scroll",
73+
"upcoming": true,
74+
"upcoming_display_mode": "scroll"
75+
},
76+
"teams": {
77+
"favorite_teams": ["NCAA_MENS_TOP_10", "JOHNS HOPKINS"],
78+
"favorite_teams_only": false,
79+
"show_all_live": true
80+
},
81+
"filtering": {
82+
"recent_games_to_show": 5,
83+
"upcoming_games_to_show": 10
84+
},
85+
"live_priority": true
86+
},
87+
"ncaa_womens": {
88+
"enabled": true,
89+
"display_modes": {
90+
"live": true,
91+
"live_display_mode": "switch",
92+
"recent": true,
93+
"recent_display_mode": "scroll",
94+
"upcoming": true,
95+
"upcoming_display_mode": "scroll"
96+
},
97+
"teams": {
98+
"favorite_teams": ["MARYLAND", "NORTH CAROLINA", "SYRACUSE"],
99+
"favorite_teams_only": false,
100+
"show_all_live": true
101+
}
102+
}
103+
}
104+
```
105+
106+
### Display modes per league
107+
108+
Each of live / recent / upcoming can be independently enabled and given its own display style:
109+
110+
- `switch` — one game card at a time, rotating on a timer
111+
- `scroll` — all matching games composited into a horizontal ticker that scrolls across the display
112+
113+
### Live priority
114+
115+
When `live_priority: true`, live games for configured favorite teams will interrupt the normal rotation whenever they are in progress.
116+
117+
## Team Abbreviations
118+
119+
**Important — NCAA lacrosse uses full-name abbreviations, not the short codes you may be used to from the football, basketball, or hockey plugins.** ESPN's lacrosse feed returns team abbreviations like `NORTH CAROLINA`, `JOHNS HOPKINS`, `SAINT JOSEPH'S`, not `UNC` / `JHU` / `SJU`. Use the full-name form in `favorite_teams` or the matching will fail silently.
120+
121+
A few recurring examples (use exactly as shown, uppercase, with spaces, apostrophes, and periods as they appear):
122+
123+
| Team | Abbreviation |
124+
|---|---|
125+
| Maryland | `MARYLAND` |
126+
| North Carolina | `NORTH CAROLINA` |
127+
| Syracuse | `SYRACUSE` |
128+
| Johns Hopkins | `JOHNS HOPKINS` |
129+
| Duke | `DUKE` |
130+
| Notre Dame | `NOTRE DAME` |
131+
| Princeton | `PRINCETON` |
132+
| Virginia | `VIRGINIA` |
133+
| Yale | `YALE` |
134+
| Harvard | `HARVARD` |
135+
| Cornell | `CORNELL` |
136+
| Penn State | `PENN STATE` |
137+
| Richmond | `RICHMOND` |
138+
| Saint Joseph's | `SAINT JOSEPH'S` |
139+
| Mount St. Mary's | `MOUNT ST. MARY'S` |
140+
| William & Mary | `WILLIAM & MARY` |
141+
| Long Island University | `LONG ISLAND UNIVERSI` *(ESPN truncates to 20 chars)* |
142+
143+
If you're unsure of a team's exact abbreviation, hit the ESPN scoreboard endpoint directly and look at `events[].competitions[].competitors[].team.abbreviation`:
144+
145+
```bash
146+
curl -s 'https://site.api.espn.com/apis/site/v2/sports/lacrosse/mens-college-lacrosse/scoreboard' \
147+
| python -m json.tool | grep -A1 abbreviation
148+
```
149+
150+
### Dynamic team shortcuts
151+
152+
Instead of listing abbreviations manually, use one of these tokens in `favorite_teams` to auto-expand to the current poll:
153+
154+
| Token | League | Expands to |
155+
|---|---|---|
156+
| `NCAA_MENS_TOP_5` | Men's | Top 5 of Inside Lacrosse D1 Men's Poll |
157+
| `NCAA_MENS_TOP_10` | Men's | Top 10 of Inside Lacrosse D1 Men's Poll |
158+
| `NCAA_MENS_TOP_20` | Men's | Full top 20 (the entire men's poll) |
159+
| `NCAA_WOMENS_TOP_5` | Women's | Top 5 of IWLCA Coaches Poll |
160+
| `NCAA_WOMENS_TOP_10` | Women's | Top 10 of IWLCA Coaches Poll |
161+
| `NCAA_WOMENS_TOP_25` | Women's | Full top 25 |
162+
163+
Tokens can be mixed with literal abbreviations: `["NCAA_MENS_TOP_10", "JOHNS HOPKINS", "PRINCETON"]` tracks the current top 10 *plus* any of those two teams that aren't already in it.
164+
165+
## Display Modes (plugin-level)
166+
167+
The plugin exposes six granular display modes the LEDMatrix host rotation can cycle through:
168+
169+
- `ncaa_mens_live`, `ncaa_mens_recent`, `ncaa_mens_upcoming`
170+
- `ncaa_womens_live`, `ncaa_womens_recent`, `ncaa_womens_upcoming`
171+
172+
## Data Source
173+
174+
Scores and schedules come from ESPN's public site API:
175+
176+
- Men's scoreboard: `https://site.api.espn.com/apis/site/v2/sports/lacrosse/mens-college-lacrosse/scoreboard`
177+
- Men's rankings: `https://site.api.espn.com/apis/site/v2/sports/lacrosse/mens-college-lacrosse/rankings`
178+
- Women's scoreboard: `https://site.api.espn.com/apis/site/v2/sports/lacrosse/womens-college-lacrosse/scoreboard`
179+
- Women's rankings: `https://site.api.espn.com/apis/site/v2/sports/lacrosse/womens-college-lacrosse/rankings`
180+
181+
Team logos are fetched from `https://a.espncdn.com/i/teamlogos/ncaa/500/{team_id}.png` and cached locally under `assets/sports/ncaa_logos/`.
182+
183+
## Troubleshooting
184+
185+
**My favorite team doesn't show up.** You're almost certainly using a short abbreviation like `UNC` or `JHU`. Lacrosse abbreviations are the full school name in uppercase — see **Team Abbreviations** above.
186+
187+
**No games appear at all.** NCAA lacrosse is a spring sport. Men's runs roughly January through late May; women's runs February through late May. Outside that window, the ESPN scoreboard endpoint returns an empty `events[]` array and the plugin has nothing to display.
188+
189+
**Rank badges (`#1`, `#2`) aren't appearing.** Ensure `display_options.show_ranking: true` (the default). Rankings are cached for 1 hour and are only populated for teams that appear in the current poll. Unranked teams show no badge, which is intentional.
190+
191+
**Shot totals are always 0.** ESPN's lacrosse feed does not currently expose per-team shot counts in the `competitors[].statistics` array the way hockey does for saves. The `show_shots` toggle is wired but will remain empty until ESPN publishes the stat. Leave it off for now.
192+
193+
**Tournament games show `TBD` placeholders.** ESPN uses team IDs `-1` and `-2` for bracket slots where the opponent hasn't been determined yet. The plugin renders these as text placeholders — they'll resolve to real logos once the bracket is set.
194+
195+
**A team's logo is missing or looks wrong.** Delete the cached logo at `assets/sports/ncaa_logos/{ABBR}.png` (use the exact file name, spaces and all) and the plugin will re-download it from ESPN on the next update.
196+
197+
## Testing
198+
199+
A standalone smoke test is included at `test_lacrosse_plugin.py`:
200+
201+
```bash
202+
cd plugins/lacrosse-scoreboard
203+
python test_lacrosse_plugin.py
204+
```
205+
206+
It stubs the LEDMatrix host modules, imports every plugin module, exercises the dynamic team resolver against live ESPN rankings, and runs a 50-event window of both men's and women's scoreboard data through `Lacrosse._extract_game_details`, asserting that required fields are populated. No external test framework is required.
207+
208+
## License
209+
210+
See `LICENSE` in this directory.

0 commit comments

Comments
 (0)