Configure: WP Cycling Road League Leaderboard

This procedure assumes the Leaderboard Model — it is the operator’s "set it up" companion to that design. The concrete ids below are for the 2026 Autumn Road League (Org 4); for a later season substitute that season’s series, event, and CustomList ids. Design rationale is captured in design journal 2026-04/wpca-2026-leaderboard.adoc.

1. Outcome

Three configured leaderboards for the WP Cycling Road League season, ready to be populated from imported race results:

Board type Scope

Individual

INDIVIDUAL_RIDER

All scored events except the TTT; per-Person season points.

Team/Club

SCHOOL (reused)

All scored events including the TTT; grouped by COALESCE(team, club).

All Rounder

INDIVIDUAL_RIDER

Starred events only (the multi-discipline subset); per-Person.

All three score with WpcaRoadLeaguePointsCalculator (positions 1–12 → 50/45/40/36/34/32/30/28/26/24/22/20; 13+ → 2 participation points) and aggregate per series category — no separate gender filter, because the category structure already splits men and women.

2. Cadence

Once per season, after the series and its events exist. The per-event result import that feeds these boards is the recurring task — see Import Operations Guide, selecting the WPCA calculator (next section).

3. Prerequisites

  1. The Road League Series exists (2026: Series 10, "2026 Road League", Org 4) with its eight series categories in place (2026: ids 2480–2487 — Cat 1–4, Women, U/19 Men, U/19 Women, Under 19 Development).

  2. All scored events exist under the series with their EventCategory linked to the series categories (2026: events 119, 120, 121, 127, 128, 129, 130, 131, 132 — Duynefontein, Red Hill, Killarney 1, Simonstown, TTT, Paardeberg, Philadelphia, Slent, Killarney 2).

  3. The Club CustomList is mapped to custom_list_1 and the Team CustomList to custom_list_2 on every Road League event (2026: club list 1504; Team list custom_list.id = 1505, "WP Teams 2026", custom_list_2_name = 'Team', set on all nine events).

4. Steps

4.1. 1. Import results with the WPCA calculator

The boards read RaceResult.points, so each event’s results must be imported with the Road League scale, not the default. Pass the calculator on the results-import endpoints:

PUT /api/result-sets/import?...&pointsCalculator=WpcaRoadLeaguePointsCalculator
PUT /api/result-sets/import-bulk?...&pointsCalculator=WpcaRoadLeaguePointsCalculator

pointsCalculator accepts the short code or the fully-qualified class name; omitting it falls back to the default SASchoolCyclingSeriesPointsCalculator. Full operator steps: Import Operations Guide.

Default behaviour is unchanged: any import that does not pass pointsCalculator still uses the SA Schools calculator. Only the Road League imports set it.

4.2. 2. Create the three Leaderboard rows

All three reference the Road League series, set pointsCalculatorClass = WpcaRoadLeaguePointsCalculator, gender = NULL, and active = true.

Board type groupByList

Individual — Road League 2026

INDIVIDUAL_RIDER

— (groups by Person)

Team/Club — Road League 2026

SCHOOL

the Team list (1505), with Club (1504) as fallback

All Rounder — Road League 2026

INDIVIDUAL_RIDER

— (groups by Person)

4.3. 3. Wire each board to all eight series categories

Add a LeaderboardCategory row per board for each of the eight series categories (no pointsMultiplier). All three boards aggregate across the full category set; the event scope (not the category scope) is what differs between them.

4.4. 4. Apply the Team/Club overlay (Team/Club board only)

A rider on a registered team scores for that team on the Team/Club board, while remaining a member of their club on every other report:

  • The Team/Club aggregation key is COALESCE(EventParticipant.custom_2_id /* team /, EventParticipant.custom_1_id / club */) — team overrides club when set; riders without a team fall back to their club.

  • Individual and All Rounder boards are unaffected — a rider’s club there comes from custom_1_id (normalise NULL / "None" / "Other" to "None").

  • Team CustomListValues are populated as teams register; the only operational addition to EP imports is a "Team" column.

4.5. 5. Apply the event-scope rules

For this season the event sets are hardcoded event-ID lists in the manual aggregation queries (a first-class per-leaderboard event set is deferred — see Leaderboard Sync):

  • Individual and All Rounderexclude the TTT (2026: event 128) from the aggregation.

  • Team/Clubinclude the TTT. The TTT produces a RaceResult row for every rider with the team’s finishing-position points, so club/team aggregation has the numbers it needs with no special import path.

  • All Rounder — restrict to the starred events (2026: Duynefontein, Simonstown, Paardeberg, Slent), which reward performance across Road + Hill Climb + ITT.

4.6. 6. Populate the standings

Until Leaderboard Sync lands, aggregate the standings manually from RaceResult.points per the event-scope rules above. Points on RaceResult must be correct (step 1), because manual SQL aggregation reads from them.

5. Verification

  • Three Leaderboard rows exist under the Road League series, each with pointsCalculatorClass = WpcaRoadLeaguePointsCalculator and the full eight-category set.

  • For one imported event, position→points follows 50/45/40/36/34/32/30/28/26/24/22/20 exactly (13+ → 2).

  • Individual and All Rounder exclude the TTT; Team/Club includes it.

  • A team-registered rider scores under their team on Team/Club but under their club on Individual.

6. Recovery

  • Wrong points (e.g. default scale applied) → re-import the event with pointsCalculator=WpcaRoadLeaguePointsCalculator, then re-aggregate. See Import Operations Guide.

  • Rider on wrong team → correct the EP’s custom_2_id; Individual/All Rounder are unaffected.

  • All Rounder includes a non-starred event → audit the hardcoded event-ID list in the aggregation query.

7. References