@zmnatz/usa-club-rugby-data 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/eligibility.d.ts +8 -0
- package/dist/eligibility.js +25 -0
- package/dist/types.d.ts +6 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ const players = await client.players('rocky-gorge'); // a verdict per player per
|
|
|
23
23
|
const report = await client.eligibility('rocky-gorge'); // the same, grouped by side
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
-
A player record carries `participation` (Match Participation: matches started plus matches entered as a substitute), `eligible`, `reasons` for a failure, `pending` for the matches an eligible player still needs, and per-round detail in `rows[].rounds`.
|
|
26
|
+
A player record carries `participation` (Match Participation: matches started plus matches entered as a substitute), `eligible`, `reasons` for a failure, `pending` for the matches an eligible player still needs, `cautions` when one more upper-side match would end an eligible player's eligibility for the rest of the season, and per-round detail in `rows[].rounds`.
|
|
27
27
|
|
|
28
28
|
Everything the client does is also a pure function: `clubEligibility(snapshot, fixtures, club, { rules, now })`, `computeBreakdown`, `parseTeamSide`, `possibleFixtures` and so on. The same inputs always give the same verdicts. Pass your own `EligibilityRules` to apply a different season's thresholds; `DEFAULT_RULES` are the 2026-27 Senior Club Regulations (§3.4.2, §3.5).
|
|
29
29
|
|
package/dist/eligibility.d.ts
CHANGED
|
@@ -35,3 +35,11 @@ export declare function clubsInRows(rows: PlayerRow[]): Club[];
|
|
|
35
35
|
* are combined into a single total; rows with no id cannot be joined and are
|
|
36
36
|
* excluded. */
|
|
37
37
|
export declare function computeBreakdown(rows: PlayerRow[], sides: Side[], rules: EligibilityRules, remaining?: RemainingMatches): EligibilityBreakdown;
|
|
38
|
+
/** Where a player stands on a side, most urgent first. */
|
|
39
|
+
export declare const VERDICT_STATUSES: readonly ['ineligible', 'caution', 'on-track', 'eligible'];
|
|
40
|
+
export type VerdictStatus = (typeof VERDICT_STATUSES)[number];
|
|
41
|
+
/** A verdict as one status. Ineligible outranks Caution, and Caution outranks On
|
|
42
|
+
* Track, which a player can still recover from. */
|
|
43
|
+
export declare function verdictStatus(player: Pick<PlayerEligibility, 'eligible' | 'pending' | 'cautions'>): VerdictStatus;
|
|
44
|
+
/** A status's place in `VERDICT_STATUSES`, for sorting: lowest is most urgent. */
|
|
45
|
+
export declare function verdictRank(player: Pick<PlayerEligibility, 'eligible' | 'pending' | 'cautions'>): number;
|
package/dist/eligibility.js
CHANGED
|
@@ -99,8 +99,10 @@ function evaluate(row, index, rosters, rules, remaining) {
|
|
|
99
99
|
const isLowest = index === rosters.length - 1;
|
|
100
100
|
const ownLabel = isLowest ? own.label : `${own.label} or lower`;
|
|
101
101
|
const fixturesLeft = sum(rosters.slice(index).map((roster) => remaining[roster.side.teamName] ?? 0));
|
|
102
|
+
const fixturesKnown = rosters.slice(index).every((roster) => remaining[roster.side.teamName] !== undefined);
|
|
102
103
|
const reasons = [];
|
|
103
104
|
const pending = [];
|
|
105
|
+
const cautions = [];
|
|
104
106
|
// A two-match minimum is met if the player can still reach it by Super
|
|
105
107
|
// Regionals (Reg 3.5.1(a)); see docs/adr/008-eligibility-rules.md.
|
|
106
108
|
const checkMinimum = (count, matchLabel) => {
|
|
@@ -123,10 +125,16 @@ function evaluate(row, index, rosters, rules, remaining) {
|
|
|
123
125
|
if (count >= rules.upperMatchLimit) {
|
|
124
126
|
reasons.push(`Played ${count} ${rosters[aboveIndex].side.label} matches — playing ${rules.upperMatchLimit} for an upper side loses ${own.label} eligibility`);
|
|
125
127
|
}
|
|
128
|
+
else if (count === rules.upperMatchLimit - 1) {
|
|
129
|
+
cautions.push(`One more ${rosters[aboveIndex].side.label} match (${count} played) loses ${own.label} eligibility for the rest of the season`);
|
|
130
|
+
}
|
|
126
131
|
}
|
|
127
132
|
if (atOrBelow < sum(above)) {
|
|
128
133
|
reasons.push(`Upper-side participation (${sum(above)}) exceeds ${ownLabel} participation (${atOrBelow})`);
|
|
129
134
|
}
|
|
135
|
+
else if (atOrBelow === sum(above) && fixturesLeft === 0 && fixturesKnown) {
|
|
136
|
+
cautions.push(`One more upper-side match loses ${own.label} eligibility for the rest of the season (level at ${atOrBelow} with no ${ownLabel} fixtures left)`);
|
|
137
|
+
}
|
|
130
138
|
}
|
|
131
139
|
return {
|
|
132
140
|
playerId: row.playerId,
|
|
@@ -146,6 +154,7 @@ function evaluate(row, index, rosters, rules, remaining) {
|
|
|
146
154
|
eligible: reasons.length === 0,
|
|
147
155
|
reasons,
|
|
148
156
|
pending,
|
|
157
|
+
cautions: reasons.length === 0 ? cautions : [],
|
|
149
158
|
};
|
|
150
159
|
}
|
|
151
160
|
const byName = (a, b) => a.lastName.localeCompare(b.lastName) || a.firstName.localeCompare(b.firstName);
|
|
@@ -234,6 +243,7 @@ export function computeBreakdown(rows, sides, rules, remaining = {}) {
|
|
|
234
243
|
total: players.length,
|
|
235
244
|
eligible: players.filter((player) => player.eligible).length,
|
|
236
245
|
onTrack: players.filter((player) => player.eligible && player.pending.length > 0).length,
|
|
246
|
+
caution: players.filter((player) => player.cautions.length > 0).length,
|
|
237
247
|
competitions: [...roster.competitions].sort(),
|
|
238
248
|
},
|
|
239
249
|
};
|
|
@@ -241,3 +251,18 @@ export function computeBreakdown(rows, sides, rules, remaining = {}) {
|
|
|
241
251
|
ignoredTeamNames: [...new Set(rows.map((row) => row.teamName).filter((name) => !teamNames.has(name)))],
|
|
242
252
|
};
|
|
243
253
|
}
|
|
254
|
+
/** Where a player stands on a side, most urgent first. */
|
|
255
|
+
export const VERDICT_STATUSES = ['ineligible', 'caution', 'on-track', 'eligible'];
|
|
256
|
+
/** A verdict as one status. Ineligible outranks Caution, and Caution outranks On
|
|
257
|
+
* Track, which a player can still recover from. */
|
|
258
|
+
export function verdictStatus(player) {
|
|
259
|
+
if (!player.eligible)
|
|
260
|
+
return 'ineligible';
|
|
261
|
+
if (player.cautions.length > 0)
|
|
262
|
+
return 'caution';
|
|
263
|
+
return player.pending.length > 0 ? 'on-track' : 'eligible';
|
|
264
|
+
}
|
|
265
|
+
/** A status's place in `VERDICT_STATUSES`, for sorting: lowest is most urgent. */
|
|
266
|
+
export function verdictRank(player) {
|
|
267
|
+
return VERDICT_STATUSES.indexOf(verdictStatus(player));
|
|
268
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -73,6 +73,10 @@ export interface PlayerEligibility {
|
|
|
73
73
|
/** Matches an eligible player still needs by Super Regionals; empty when the
|
|
74
74
|
* minimum is already met. These players are "on track". */
|
|
75
75
|
pending: string[];
|
|
76
|
+
/** How one more upper-side match would end an eligible player's eligibility
|
|
77
|
+
* for this side for the rest of the season; empty otherwise. These players
|
|
78
|
+
* are in "Caution". */
|
|
79
|
+
cautions: string[];
|
|
76
80
|
}
|
|
77
81
|
/** One side's verdicts and summary. */
|
|
78
82
|
export interface SideBreakdown extends Side {
|
|
@@ -90,6 +94,8 @@ export interface DivisionSummary {
|
|
|
90
94
|
eligible: number;
|
|
91
95
|
/** How many of the eligible players still need matches to reach a minimum. */
|
|
92
96
|
onTrack: number;
|
|
97
|
+
/** How many of the eligible players are in Caution. */
|
|
98
|
+
caution: number;
|
|
93
99
|
/** Distinct competitions this side's roster spans, sorted. */
|
|
94
100
|
competitions: string[];
|
|
95
101
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zmnatz/usa-club-rugby-data",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "USA Club Rugby senior club teams, players and NCS eligibility from the Rugby Xplorer match record: typed readers for the nightly published data, a rate-limited CMS client, and the reproducible eligibility rules.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"rugby",
|