Seasonal Competition Standings
Problem
A competition operator records results throughout one season and may later receive score corrections. Design SeasonStandings to keep registered teams, current match results, rankings, and team histories consistent under fixed scoring and tie-break rules. Calls are sequential on one instance.
Requirements
- Fixed rules. Construction fixes the win, draw, and loss awards and selects either
score-differenceorwinsas the tie-break policy. - Registered teams. A valid team ID is registered once before use. Registration order is permanent and is the final ranking tie-break.
- Single contribution. A match ID keeps one ordered home-and-away pairing and contributes exactly once to both teams' current aggregates.
- Safe corrections. Repeating the same pairing and scores returns
unchanged. New scores for the stored pairing returncorrected, atomically replace both old contributions, and keep the match's history position. - Configured aggregates. Current results determine all counters, scores, and configured points exactly. For each team,
played = wins + draws + lossesandscoreDifference = scoreFor - scoreAgainst. Across the season, total played is twice the match count and total score for equals total score against; every aggregate except score difference is nonnegative. - Deterministic ranking. The selected policy and registration fallback produce one strict order with global one-based ranks.
- Detached pages. Queries do not change state, and each standings page is stable while season state is unchanged. Changing a returned collection or record where the language permits it cannot change stored state or another snapshot.
- Ordered history. A team's history shows its current perspective on each match in first-submission order. A correction replaces the existing entry without moving it.
- Atomic failures. Every rejected call reports the specified error code and changes no state, registration position, or first-submission position.
API
integer is a signed 64-bit integer. Identifiers are case-sensitive ASCII strings matching [A-Za-z0-9][A-Za-z0-9_-]{0,31}. No public argument may be null.
| Signature | Returns | Behavior |
|---|---|---|
SeasonStandings(winPoints: integer, drawPoints: integer, lossPoints: integer, tieBreakPolicy: string) | Not applicable | Creates an empty season. Each point value must be from 0 through 100. The policy must be exactly score-difference or wins. |
registerTeam(teamId: string) | void | Registers a new team at the next permanent registration position. |
recordMatch(matchId: string, homeTeamId: string, awayTeamId: string, homeScore: integer, awayScore: integer) | string | Creates or updates a match and returns created, corrected, or unchanged. Scores must be from 0 through 1,000,000. |
getStandings(offset: integer, limit: integer) | Standing[] | Returns at most limit records from zero-based offset. Offset must be from 0 through 10,000 and limit from 1 through 100. An offset at or beyond the team count returns an empty array. |
getTeamHistory(teamId: string) | MatchHistoryEntry[] | Returns the registered team's current history, or an empty array when it has no matches. |
Standing has fields in this order:
| Field | Type | Meaning |
|---|---|---|
rank | integer | One-based position in the complete current ordering. |
teamId | string | Registered team ID. |
played | integer | Number of current contributing matches. |
wins | integer | Current wins. |
draws | integer | Current draws. |
losses | integer | Current losses. |
scoreFor | integer | Sum of this team's current scores. |
scoreAgainst | integer | Sum of opponents' current scores. |
scoreDifference | integer | scoreFor - scoreAgainst. |
points | integer | Sum of configured awards for current outcomes. |
MatchHistoryEntry has fields in this order:
| Field | Type | Meaning |
|---|---|---|
matchId | string | Match ID. |
opponentTeamId | string | The other team. |
venue | string | home or away from this team's perspective. |
scoreFor | integer | This team's corrected current score. |
scoreAgainst | integer | The opponent's corrected current score. |
outcome | string | win, draw, or loss. |
pointsAwarded | integer | This match's current contribution to this team's points. |
For a new match ID, the ordered pair must contain two distinct registered teams. The match receives the next first-submission position and contributes once to both teams. For an existing match ID, the supplied ordered pair must exactly match the stored pair. A correction first removes both old contributions, then applies both new contributions as one atomic change.
A greater score awards winPoints to that team and lossPoints to the other. Equal scores award drawPoints to both teams. The three configured awards need not be ordered or distinct.
Standings comparisons are descending except for registration order:
| Policy | Comparison keys in order |
|---|---|
score-difference | points, score difference, score for, registration order ascending |
wins | points, wins, score difference, score for, registration order ascending |
A semantic failure raises or throws the runtime's standings error with one observable code. Message text is not observable.
| Call | Checks in precedence order |
|---|---|
| Constructor | winPoints, drawPoints, and lossPoints ranges, then policy. Any failure is INVALID_ARGUMENT. |
registerTeam | ID syntax gives INVALID_ARGUMENT; an existing ID gives DUPLICATE_TEAM; a full 10,000-team season gives LIMIT_EXCEEDED. |
recordMatch | matchId, homeTeamId, and awayTeamId syntax; homeScore and awayScore ranges; equal team IDs; missing home team; missing away team; changed ordered pair for an existing match; new match beyond 100,000. The corresponding codes are INVALID_ARGUMENT, INVALID_ARGUMENT, TEAM_NOT_FOUND, TEAM_NOT_FOUND, MATCH_TEAMS_MISMATCH, and LIMIT_EXCEEDED. |
getStandings | Invalid offset, then invalid limit, each gives INVALID_ARGUMENT. |
getTeamHistory | Invalid ID syntax gives INVALID_ARGUMENT; an unregistered valid ID gives TEAM_NOT_FOUND. |
Equal team IDs give INVALID_ARGUMENT even when the ID is unregistered. A correction remains allowed when the match limit is full.
Examples
With SeasonStandings(3, 1, 0, "score-difference"), register ALPHA, BETA, and GAMMA in that order.
| Step | Operation | Result |
|---|---|---|
| 1 | recordMatch("m1", "ALPHA", "BETA", 2, 1) | created |
| 2 | Repeat step 1 | unchanged |
| 3 | recordMatch("m2", "GAMMA", "ALPHA", 2, 0) | created |
| 4 | getStandings(0, 3) | GAMMA rank 1 with 3 points and difference 2; ALPHA rank 2 with 3 points and difference -1; BETA rank 3 with 0 points and difference -1. |
| 5 | recordMatch("m1", "ALPHA", "BETA", 3, 3) | corrected |
| 6 | getStandings(0, 3) | GAMMA rank 1 with 3 points; BETA rank 2 with 1 point and difference 0; ALPHA rank 3 with 1 point and difference -2. |
| 7 | getStandings(1, 1) | One BETA snapshot whose global rank is 2. |
| 8 | getTeamHistory("ALPHA") | m1 first as a home draw, then m2 as an away loss. |
For equal points, policy wins compares wins before score difference. Policy score-difference compares score difference directly after points.
Constraints
- At most 10,000 teams and 100,000 distinct matches exist in one season.
- At most 200,000 public method calls occur after construction.
- Point values are from 0 through 100, and match scores are from 0 through 1,000,000.
- Each identifier contains 1 through 32 characters under the declared ASCII grammar.
- Aggregate scores, score differences, and points require signed 64-bit arithmetic.
- One standings call returns at most 100 records. One history call may return every current match involving the team.
Notes
Calls are sequential in total action order. Implementations need no synchronization, waiting, or blocking. Python exposes StandingsError.code; Java and C++ expose StandingsException through getCode() and code() respectively. Python returns lists, Java returns List values, and C++ returns std::vector values. Returned field names and serialized values match the language-neutral fields above.