Live Scoring & Official Result Entry
At a glance
Real-time scoring infrastructure: mobile-first live entry with offline support, official scorekeeper role with digital signing, SSE-driven leaderboard updates, push notifications for match events, score correction flow under audit, score protests from team captains, walkover handling, and configurable round timing.
How it works
Live score entry on mobile is offline-capable from the ground up. The scorekeeper opens the match on a phone or tablet, taps points per mène, and the device queues every action locally. When connectivity returns, queued events sync in order with idempotency keys so duplicate replays are safe.
The official scorekeeper role digitally signs the final score with their authenticated session — a cryptographic signature attached to the result record so the line of accountability is traceable. Real-time leaderboard updates push via Server-Sent Events: any subscribed client (player apps, public leaderboards, embedded CMS widgets, scoreboards on venue screens) sees standings refresh within a second of each confirmed mène. Push notifications for match events fan out to subscribers — match starting in 5 min, score 10-10 in your followed match, your team has finished, podium reached.
Subscriptions are granular: per-match, per-player, per-competition, per-club, per-nation. Score correction flow lets the scorekeeper edit a recorded mène with mandatory reason; the original entry stays in the audit trail and a correction event broadcasts so live viewers see the adjustment immediately. Score protest from team captain is a structured action — the captain raises a protest tied to a specific mène or final result, the match enters a protest state, the jury (F05) reviews and resolves, and the outcome locks.
Walkover and WO handling captures the operational reality when one team does not show: the official records WO, the absent team forfeits, the present team advances with a default score per the format, and ranking impact applies the federation's WO rules. Round timing per round (45 min default) enforces session pace for poule rounds and Swiss rounds — the round timer counts down on the official's tablet, with warnings at 10 and 5 minutes, and configurable handling at expiry (current mène completes, no new mène starts).
Key capabilities
- Mobile-first live score entry with offline queue and idempotent sync
- Official scorekeeper role with digital signing on the final score
- SSE-driven real-time leaderboard updates for clients, embeds, and venue scoreboards
- Push notifications for match events with granular per-match/player/competition subscriptions
- Score correction flow with mandatory reason and audit trail
- Score protest from team captain with structured jury workflow
- Walkover handling and configurable round timing (45 min default) with warnings and expiry rules
In practice
Outdoor Saturday tournament with patchy 4G. The official scorekeeper at court 5 records mène after mène on her tablet; the device is offline for 12 minutes, then back. All queued events sync in order with no duplicates.
The leaderboard widget on the federation site refreshes for spectators. The losing captain protests the final mène measurement; the match flips to protest state and the jury reviews via tablet, examines the photo attached in F04.05, and confirms the original score. The scorekeeper digitally signs the result; ranking points queue for the overnight recompute.
The next round timer starts at 45 minutes and warns at 35 and 40.
Features in this subsystem
27| ID | Status | Features |
|---|---|---|
| F04.21.01 | Shipped | Live score entry on mobile with offline support ✅ PL-F0421a ✅ PL-F0421a |
| F04.21.02 | Shipped | Official scorekeeper role with digital signing ✅ PL-F0421a ✅ PL-F0421a |
| F04.21.03 | Shipped | Real-time leaderboard updates (SSE) ✅ PL-F0421a ✅ PL-F0421a |
| F04.21.04 | Shipped | Push notifications for match events (subscriptions) ✅ PL-F0421a ✅ PL-F0421a |
| F04.21.05 | Shipped | Score correction flow with audit ✅ PL-F0421b ✅ PL-F0421b |
| F04.21.06 | Shipped | Score protest from team captain ✅ PL-F0421b ✅ PL-F0421b |
| F04.21.07 | Shipped | Walkover and WO handling ✅ PL-F0421b ✅ PL-F0421b |
| F04.21.08 | Shipped | Round timing per round (45 min default) ✅ PL-F0421b ✅ PL-F0421b |
| F04.21.20 | Shipped | VolunteerScoreEntry — parallel light-weight scoring surface optimised for volunteers, big-button UI, idempotent on (device_id, client_ts, end_number), soft-validation with stable warning codes (zero_score_end / both_teams_scored / score_above_13 / missing_end_<n>) — PL-T222 ✅ PL-T222 ✅ PL-T222 |
| F04.21.21 | Shipped | Volunteer offline cache — exponential-backoff retry-loop, server-idempotency on tuple guarantees no duplicates, sync-status badge (synced/pending/error) — PL-T222 ✅ PL-T222 ✅ PL-T222 |
| F04.21.22 | Shipped | Dispute flagging — measure / dead_end / timeout / rule_question / other, sets is_dispute=true + needs_review, dispatchar scoring.dispute_raised till court-marshal via F09.02 — PL-T222 ✅ PL-T222 ✅ PL-T222 |
| F04.21.23 | Shipped | BulkScoreUpload — fixed-column CSV (competition_match_ref,end_number,team_a_points,team_b_points,scorer_note), parse-job med per-row error/warning, atomic-per-match apply, status uploaded/parsing/validated/applied/partially_applied/failed — PL-T222 ✅ PL-T222 ✅ PL-T222 |
| F04.21.30 | Shipped | Enhetsregistret scoreboard_device (migration 00161): den fysiska tavlan ägd av PL:s tenant-noll, bunden till hyresgästens match med mjuka referenser, med serial_no/kit_instance_id mot D-EQUIP:s brädregister — 1ee83e2c.m-1 ✅ 1ee83e2c.m-1 (t-1) |
| F04.21.31 | Shipped | Signaturverifierad uplink-ingest POST /v1/scoreboards/ingest (LNS-webhook, X-PL-Webhook-Signature, ±5 min färskhetsfönster, fail-closed utan en enda DB-skrivning) — 1ee83e2c.m-1 ✅ 1ee83e2c.m-1 (t-1) |
| F04.21.32 | Shipped | Uplink → score_entry med source='board' och device_id: delta mot competition.DeriveScore, ökning ⇒ ny mène, minskning med 1 ⇒ husets undo-rad (is_undo/replaces_entry_id), aldrig negativ poäng eller DELETE — 1ee83e2c.m-1 ✅ 1ee83e2c.m-1 (t-1) |
| F04.21.33 | Shipped | Validering mot den resolvade poänggränsen (ResolveScoring, aldrig hårdkodad 13:a) + stabila avvisningskoder (score_above_max, score_decrement_too_large, invalid_score_delta, scoreboard_match_closed, scoreboard_match_mismatch) — 1ee83e2c.m-1 ✅ 1ee83e2c.m-1 (t-1) |
| F04.21.34 | Shipped | Replay-skydd på frame counter inom sessions-epok + partiellt unikt index på (device_id, session_epoch, f_cnt) where outcome='applied' ⇒ exakt-en-gång även vid at-least-once-leverans; varje utfall spåras append-only i scoreboard_uplink_event — 1ee83e2c.m-1 ✅ 1ee83e2c.m-1 (t-1) |
| F04.21.35 | Shipped | match_final → D-COMP:s egen FSM (Transition + CompleteMatch) med vinnare ur DeriveOutcome; tavlan sätter aldrig confirmed_by_* — det fysiska långtrycket är en inmatning, inte resultatsignering — 1ee83e2c.m-1 ✅ 1ee83e2c.m-1 (t-1) |
| F04.21.36 | Shipped | Flott-/bindnings-API GET /v1/scoreboards, GET\ PATCH /v1/scoreboards/{id}, POST /v1/scoreboards, POST /v1/scoreboards/{id}/assign-match — ETag/If-Match, cursor-paginering, server-injicerat skop (hyresgästen ser bara enheter med aktiv uthyrning), nyckelfälten projiceras aldrig — 1ee83e2c.m-1 | ✅ 1ee83e2c.m-1 (t-2) |
| F04.21.37 | Shipped | Två mandat, inte ett (scoreboard:fleet_admin i den smala systemmallen platform_scoreboard_admin; scoreboard:operate på klubb-/distrikts-/förbundsadmin) + fixturen seed-scoreboard-hw som konsumerar D-EQUIP:s custody-brädor — 1ee83e2c.m-1 ✅ 1ee83e2c.m-1 (t-2) |
| F04.21.38 | Shipped | Enhetstelemetri som riktiga rader (migration 00162): scoreboard_device_health (1:1 per enhet — batteri, tavlans egen RSSI, gateway-RSSI/SNR ur rxInfo, senast sedd, härledd presence) + tidsserien scoreboard_health_sample, upsertad i ingestens enda transaktion. Ingen temperaturserie: v1-payloaden mäter ingen temperatur, over_temp är en flagga — 1ee83e2c.m-2 ✅ 1ee83e2c.m-2 (t-1) |
| F04.21.39 | Shipped | Larm-inboxen scoreboard_alert + GET /v1/scoreboards/alerts(/{id}, /acknowledge, /resolve): fem typer, warning/critical, en öppen rad per (enhet, typ) via partiellt unikt index, kvittens ≠ lösning (kvittensen är idempotent och fryser eskaleringen), auto-lösning per typ — tamper löses endast manuellt — 1ee83e2c.m-2 ✅ 1ee83e2c.m-2 (t-1) |
| F04.21.40 | Shipped | Trösklarna är konfig-data, aldrig konstanter — scoreboard_telemetry_config med treskikts-upplösning enhet → kit → plattform (null = ärv) och GET/PUT /v1/scoreboards/telemetry-config*; en ändring får effekt på nästa uplink utan omstart — 1ee83e2c.m-2 ✅ 1ee83e2c.m-2 (t-1) |
| F04.21.41 | Shipped | Downlink-porten och kön (LNSDownlink + register + deterministisk fake + ChirpStack-adapter bakom env): scoreboard_command med den ärliga statusmaskinen queued → sent → acked \ expired | failed, där sent betyder LNS:en accepterade enqueuen, aldrig levererad — 1ee83e2c.m-2 | ✅ 1ee83e2c.m-2 (t-2) |
| F04.21.42 | Shipped | Tamper-armering POST /v1/scoreboards/{id}/tamper-arm\ tamper-disarm (202 med kommandoraden, If-Match, konvergent på partiellt unikt index) + kvittensvägen POST /v1/scoreboards/ingest?event=ack på samma signatur-fail-closed: enhetens tamper_armed speglas först vid tavlans egen kvittens — 1ee83e2c.m-2 | ✅ 1ee83e2c.m-2 (t-2) |
| F04.21.43 | Shipped | assign-match köar set_match (0x01) och auto-arm/disarm ur D-EQUIP:s egna emitterade scoreboard_inventory_event-rader (received ⇒ arm, returning ⇒ disarm) — läsning, aldrig skrivning i D-EQUIP:s revir; dedup-cursorn lever på m-2:s egen kommandorad — 1ee83e2c.m-2 ✅ 1ee83e2c.m-2 (t-2) |
| F04.21.44 | Shipped | Hälsosvepet api run-job scoreboard_health_sweep med fyra plikter (närvaro-larm stale/lost, auto-arm/disarm-avstämning, kommando-TTL + omförsök, retention på tidsserien), now injicerat, batch-takat och idempotent — 1ee83e2c.m-2 ✅ 1ee83e2c.m-2 (t-1/t-2) |
Stakeholders who need this subsystem
Surfaces in 3 stakeholder analyses