@zmnatz/usa-club-rugby-data 0.2.0 → 0.3.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 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
 
@@ -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
  };
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.2.0",
3
+ "version": "0.3.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",