@wral/hsot-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 CHANGED
@@ -10,9 +10,9 @@ exactly one of these elements.
10
10
  | Component | Page | Key attributes |
11
11
  |---|---|---|
12
12
  | `<hsot-scores>` | Scores/Schedules (the scoreboard; future dates are schedules) | `sport`, `date` (YYYY-MM-DD, deep-linkable via `?date=`; defaults to the closest matchday: today when games are on, otherwise the previous one), `season`, `refresh-interval` |
13
- | `<hsot-standings>` | Standings, every conference for a sport | `sport`, `season` |
13
+ | `<hsot-standings>` | Standings, every conference for a sport | `sport`, `gender` (one side of a gendered sport), `season` |
14
14
  | `<hsot-rankings>` | The weekly Top 25 with movement + rank history | `sport`, `gender` (poll family), `season`, deep link `?poll=YYYY-MM-DD` |
15
- | `<hsot-school>` | School hub: hero, team tabs, schedule, roster (headed by the season's roster photo when one is set; positions render as a list, "WR/CB"; a College column appears when a player carries a commitment), honors | `school-id`, `sport` (initial tab, deep link `?sport=`), `season` |
15
+ | `<hsot-school>` | School hub: hero, team tabs, schedule, roster (headed by the season's roster photo when one is set; positions render as a list, "WR/CB"; a College column appears when a player carries a commitment), honors | `school-id`, `sport` + `gender` (initial tab, deep links `?sport=` / `?gender=`), `season` |
16
16
  | `<hsot-boxscore>` | Game detail: line score (stored `periodScores`, or derived from the scoring plays when nothing wrote one — `src/lib/line-score.mjs`, shown only when it reconciles with the score), the desk's scoring-play log when a game carries one (period, clock, play, running score), coverage, season context; game notes keep their line breaks | `game-id`, `refresh-interval` |
17
17
  | `<hsot-brackets>` | Playoff brackets: a sport's tournaments for the season, rounds as columns laid out from the winner edges (seedings place round one; `GET /tournaments/{id}/edges` is the tree), group tabs for split brackets (East / West / Championship), scores and the champion once played | `sport`, `season`, `tournament-id` (pin one; otherwise `?tournament=` or the bracket in play), `refresh-interval` |
18
18
  | `<hsot-school-search>` | "Find Your School" typeahead for the right rail (standalone: fetches `GET /schools?q=` itself, no injected client) | `api`, `school-href`, `limit`, `placeholder` |
@@ -32,8 +32,10 @@ Shared attributes on every data component (`hsot-school-search` takes only
32
32
  Alternatively set the `.client` **property** to any object with the client
33
33
  method surface (see `src/lib/client.mjs`) — the demo client, a test double,
34
34
  or the future `@wral/sdk-hsot`. The property wins over the attribute.
35
- - `game-href`, `school-href`, `section-href` — link templates with `{sport}`,
36
- `{id}` and `{view}` tokens for cross-page links. Defaults follow the
35
+ - `game-href`, `school-href`, `section-href`, `report-href` — link templates
36
+ with `{sport}`, `{id}` and `{view}` tokens for cross-page links
37
+ (`report-href` is the "Report a Score" page, default
38
+ `/report-a-score/?game={id}`). Defaults follow the
37
39
  sport-first hierarchy: `/{sport}/game/{id}/`, `/school/{id}/`,
38
40
  `/{sport}/{view}/`. The site's URL space belongs to Studio publications, so
39
41
  these are overridable per mount.
@@ -88,6 +90,16 @@ so the components never know what lives there:
88
90
 
89
91
  Without slotted rail content the view renders full width, unchanged.
90
92
 
93
+ ## Reporting a score
94
+
95
+ The "Report a Score" page is its own package,
96
+ [web-components.hsot-score-report](https://bitbucket.org/cbcnm/web-components.hsot-score-report)
97
+ (`<hsot-score-report>`), with no dependency on this one. These components
98
+ only link to it: `hsot-boxscore` renders "Report a score for this game" and
99
+ `hsot-scores` renders "report a score" in its toolbar, both from the
100
+ `report-href` template (default `/report-a-score/?game={id}`; `reportHref()`
101
+ in `src/lib/hrefs.mjs` drops the parameter when there is no game).
102
+
91
103
  ## Derivation lives here, for now
92
104
 
93
105
  Standings, records, streaks and rank badges are **derived client-side** from
@@ -107,6 +119,33 @@ That logic is `src/lib/records.mjs` and `src/lib/polls.mjs`, unit-tested with
107
119
  `node --test`. If api-hsot ever grows derived read endpoints, those two files
108
120
  are what they replace.
109
121
 
122
+ ## Sport is not gender
123
+
124
+ Most sports HSOT covers are **gendered**: soccer, basketball, lacrosse, cross
125
+ country, swimming, track and the rest field a boys and a girls team per
126
+ school. Both teams carry the same `sport` — the side is a separate `gender`
127
+ field on Team — and a Game carries no gender at all (it inherits one from the
128
+ teams playing it). So **scoping a view by sport alone merges the two halves of
129
+ the sport**, which is exactly what shipped: the standings table listed every
130
+ school twice, once for each side, sorted into one order.
131
+
132
+ Any view that stands for "the teams of a sport" therefore needs a gender too:
133
+
134
+ - `src/lib/teams.mjs` holds the rule — `teamsOfGender()` for the filter,
135
+ `splitsByGender()` for "does this sport have two sides". The family rule
136
+ matches the gendered polls: a side includes coed teams, and a team with no
137
+ gender belongs to no side.
138
+ - `<hsot-standings>` takes `gender`; the section sub-nav links
139
+ `/{sport}/{gender}/standings/` whenever the sport fields both, and an
140
+ un-pinned mount offers a Boys/Girls switch instead of merging.
141
+ - `<hsot-school>` selects its tab by **team id**, not sport: two of a school's
142
+ tabs answer to "soccer", and its conference-place tile counts only the
143
+ team's own side.
144
+
145
+ Records themselves are safe either way — `computeRecords()` keys off team ids,
146
+ and a team only appears in its own side's games — so the merge shows up in the
147
+ team **lists** a view builds, not in the numbers on a row.
148
+
110
149
  ## Development
111
150
 
112
151
  Requires Node 18+.
@@ -0,0 +1,49 @@
1
+ function m(l = {}) {
2
+ const i = Object.entries(l).filter(([, r]) => r != null && r !== "");
3
+ if (!i.length) return "";
4
+ const c = new URLSearchParams();
5
+ return i.forEach(([r, u]) => c.set(r, String(u))), `?${c.toString()}`;
6
+ }
7
+ function $({ baseUrl: l, token: i = "", fetchFn: c } = {}) {
8
+ if (!l) throw new Error("createClient requires a baseUrl");
9
+ const r = l.replace(/\/+$/, ""), u = c || ((...t) => fetch(...t));
10
+ async function a(t) {
11
+ const s = { Accept: "application/json" };
12
+ i && (s.Authorization = `Bearer ${i}`);
13
+ const n = await u(`${r}${t}`, { headers: s });
14
+ if (!n.ok) {
15
+ const o = new Error(`api-hsot ${n.status} on ${t}`);
16
+ throw o.status = n.status, o;
17
+ }
18
+ return n.json();
19
+ }
20
+ async function e(t, s = {}) {
21
+ const n = [];
22
+ let o = null;
23
+ for (let f = 0; f < 50; f += 1) {
24
+ const h = m(o ? { ...s, cursor: o } : s), g = await a(`${t}${h}`);
25
+ if (n.push(...g.items || []), o = g.nextCursor || null, !o) break;
26
+ }
27
+ return { items: n, total: n.length, nextCursor: null };
28
+ }
29
+ return {
30
+ listSchools: (t) => e("/schools", t),
31
+ getSchool: (t) => a(`/schools/${t}`),
32
+ listTeams: (t) => e("/teams", t),
33
+ getTeam: (t) => a(`/teams/${t}`),
34
+ listConferences: (t) => e("/conferences", t),
35
+ listPlayers: (t) => e("/players", t),
36
+ listRosters: (t) => e("/rosters", t),
37
+ listGames: (t) => e("/games", t),
38
+ getGame: (t) => a(`/games/${t}`),
39
+ listTournaments: (t) => e("/tournaments", t),
40
+ listSeedings: (t, s) => e(`/tournaments/${t}/seedings`, s),
41
+ listEdges: (t, s) => e(`/tournaments/${t}/edges`, s),
42
+ listRankings: (t) => e("/rankings", t),
43
+ listSports: () => a("/sports")
44
+ };
45
+ }
46
+ export {
47
+ $ as createClient,
48
+ $ as default
49
+ };
package/dist/define.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { a as e, d as c, H as n, c as a, b as i, f as r, e as h } from "./hsot-brackets-D5cwGage.js";
1
+ import { a as e, d as c, H as n, c as a, b as i, f as r, e as h } from "./hsot-brackets-Bqk0gI3J.js";
2
2
  const l = {
3
3
  "hsot-scores": h,
4
4
  "hsot-standings": r,