Coverage for src/usls/domain/models.py: 100%
105 statements
« prev ^ index » next coverage.py v7.15.3, created at 2026-08-05 10:11 +0000
« prev ^ index » next coverage.py v7.15.3, created at 2026-08-05 10:11 +0000
1"""Domain models for the USL Super League MCP server.
3Pure Python dataclasses with zero framework dependencies. Adapters are
4responsible for translating to/from these types from the ESPN API wire format.
5"""
7from dataclasses import dataclass, field
10@dataclass
11class Team:
12 """An USL Super League franchise.
14 id and abbreviation are the stable identifiers used by the ESPN API.
15 """
17 id: str
18 name: str
19 abbreviation: str
20 location: str
21 display_name: str
22 logo_url: str | None = None
25@dataclass
26class MatchCompetitor:
27 """One side of a match — home or away team with its score."""
29 team: Team
30 home_away: str
31 score: str | None = None
32 winner: bool | None = None
35@dataclass
36class Match:
37 """A single USL Super League match (scheduled, in-progress, or completed).
39 status_type values from the ESPN API:
40 "pre" — scheduled, not yet started
41 "in" — in progress
42 "post" — final
43 """
45 id: str
46 date: str
47 name: str
48 short_name: str
49 status_type: str
50 status_detail: str
51 competitors: list[MatchCompetitor] = field(default_factory=list)
54@dataclass
55class NewsArticle:
56 """A single USL Super League news article from the ESPN news feed."""
58 id: str
59 headline: str
60 description: str
61 published: str
62 link: str | None = None
65@dataclass
66class Player:
67 """An USL Super League player on a team's roster.
69 Optional fields (jersey, position, citizenship, age) may be missing for
70 unsigned, recently traded, or international players whose data ESPN has
71 not fully populated.
72 """
74 id: str
75 full_name: str
76 jersey: str | None = None
77 position: str | None = None
78 position_abbr: str | None = None
79 citizenship: str | None = None
80 age: int | None = None
83@dataclass
84class MatchEvent:
85 """A single key event within a match (goal, substitution, card, etc.).
87 Mirrors ESPN's keyEvents entries: type is the raw event tag
88 (e.g. "goal", "goal---header", "yellow-card", "substitution"); scoring
89 is a convenience flag for goal events.
90 """
92 clock: str
93 period: int
94 type: str
95 scoring: bool
96 text: str | None = None
97 team_name: str | None = None
100@dataclass
101class MatchDetails:
102 """Detailed information about a single USL Super League match.
104 Combines header data (teams, score, status) with venue, attendance, and
105 a chronological list of key in-game events.
106 """
108 id: str
109 date: str
110 status_detail: str
111 home_team: str
112 away_team: str
113 home_score: str | None = None
114 away_score: str | None = None
115 venue: str | None = None
116 venue_city: str | None = None
117 attendance: int | None = None
118 key_events: list[MatchEvent] = field(default_factory=list)
121@dataclass
122class Standing:
123 """A team's position in the USL Super League table."""
125 team: Team
126 wins: int
127 losses: int
128 ties: int
129 points: int
130 goals_for: int
131 goals_against: int
132 goal_difference: int
135@dataclass
136class OpponentPPG:
137 """One opponent a team has played, paired with that opponent's current PPG.
139 ``points_per_game`` is computed from the opponent's full league record (no
140 self-exclusion), so it reflects current standings position rather than a
141 strict RPI-style adjustment.
142 """
144 team: Team
145 matches_played: int
146 points: int
147 points_per_game: float
150@dataclass
151class StrengthOfSchedule:
152 """Opponent-quality summary for a single team.
154 Aggregates the current points-per-game of every opponent the team has
155 actually faced (completed matches only). Useful for "who has played the
156 tougher schedule so far?" questions early in the season.
157 """
159 team: Team
160 matches_played: int
161 opponents: list[OpponentPPG]
162 average_opponent_ppg: float
165@dataclass
166class TierRecord:
167 """A team's W-L-T record against opponents in one current-standings tier."""
169 label: str
170 rank_low: int
171 rank_high: int
172 wins: int
173 losses: int
174 ties: int
177@dataclass
178class ResultsByOpponentTier:
179 """A team's results split by the current-standings tier of each opponent.
181 Tiers are derived from the live league table at call time, not the table
182 at the time each match was played — interpret as "how have you done
183 against teams that are currently strong/middle/weak?"
184 """
186 team: Team
187 tier_size: int
188 tiers: list[TierRecord]
191@dataclass
192class AdjustedPointsPerGame:
193 """Raw vs. opponent-quality-adjusted PPG for a single team.
195 ``adjusted_ppg`` scales raw PPG by ``average_opponent_ppg / league_average_ppg``,
196 so values above raw PPG mean the team has earned points against a tougher
197 schedule than league average.
198 """
200 team: Team
201 matches_played: int
202 points: int
203 raw_ppg: float
204 average_opponent_ppg: float
205 league_average_ppg: float
206 adjusted_ppg: float