Score-Engine Integration (Uplink → Match State)
At a glance
The score-engine integration turns raw scoreboard hardware events into validated match state — applying uplinks to Match documents, enforcing pétanque rules, escalating tampers, raising alerts, automating match-final transitions and streaming live admin views over SSE — so boards on the piste and the digital match in the database stay in sync.
How it works
Every score change a referee makes on a rented scoreboard fires a ScoreboardEvent uplink. The score-engine service's apply_uplink() function is triggered automatically as soon as the ingest path persists the event, so there is no polling lag. The engine reads the event flags, locates the active Match document, and applies the change with strict validation: scores cap at 13 points (the canonical pétanque finishing score), single-step decrements are allowed (correcting an over-tap) but jumps greater than one are rejected, and any update against a match already marked completed is refused outright.
When a match_final flag arrives, the engine flips the Match to status completed, writes the winner, and dispatches a set_match downlink that primes the same physical scoreboard for the next match in the schedule — the board is back in service for the next pair without an operator touching it. ScoreboardAlert documents capture environmental and tamper conditions with severity (warning/critical): low_battery, over_temp, lost (heartbeat lost), stale (no events in expected window), tamper. Tamper escalation is rule-based — more than three tamper events within five minutes flips severity to critical and increments a tamper_event_count, so persistent attacks are visible in the admin console rather than buried in noise.
Admin endpoints expose assign-next-match, tamper-arm, tamper-disarm and a filterable alerts list. A Server-Sent Events stream (GET /scoreboards/{id}/event-stream) lets the live admin view watch a single board in real time without polling — perfect for incident triage during a televised final.
Key capabilities
- apply_uplink() service that ingests ScoreboardEvent flags into Match documents
- Strict score validation: max 13, decrement-by-1, no updates on completed matches
- Match-final flow with auto winner-write and set_match downlink for the next match
- ScoreboardAlert model for low_battery, over_temp, lost, stale and tamper events
- Tamper escalation: >3 events within 5 minutes promotes severity to critical
- Admin endpoints: assign-next-match, tamper-arm, tamper-disarm, alerts list
- SSE event stream for real-time per-board live admin views
In practice
During a televised final the referee accidentally taps +1 twice on the away team. He immediately hits the correction key on the rented scoreboard. The next uplink decrements by one — the score-engine accepts it because it's a single step.
The board now reads correctly, the live overlay updates within a second, and the Match document in the database matches the board exactly. Two matches later, the away team scores their thirteenth point. The match_final flag fires, the engine completes the match, writes the winner and dispatches set_match for the next pairing on that piste.
Operations watching the SSE stream in the admin console see every transition without refreshing.
Features in this subsystem
8| ID | Status | Features |
|---|---|---|
| F19.15.01 | Shipped | Uplink → match-state: den signaturverifierade LNS-webhooken POST /v1/scoreboards/ingest dekodar 12-byte-payloaden och skriver en riktig score_entry på D-COMP:s match (source='board'), aldrig ett eget dokument ✅ 1ee83e2c.m-1 (t-1) |
| F19.15.02 | Shipped | Score-validering mot den resolvade poänggränsen (competition.ResolveScoring — aldrig hårdkodad 13:a), decrement > 1 avvisas (score_decrement_too_large), båda lagen ändrade avvisas (invalid_score_delta), stängd match avvisas (scoreboard_match_closed); en minskning med 1 skrivs som husets undo-rad, aldrig en negativ poäng ✅ 1ee83e2c.m-1 (t-1) |
| F19.15.03 | Shipped | Match-final: match_final-flaggan driver in_progress → completed genom D-COMP:s egen FSM med vinnare ur DeriveOutcome. Tavlan sätter aldrig confirmed_by_*. Resten stängd: en lyckad assign-match köar nu ett set_match-kommando (0x01) genom downlink-porten, konvergent på kommandokönss partiella index ✅ 1ee83e2c.m-1 (t-1) + 1ee83e2c.m-2 (t-2, set_match) |
| F19.15.04 | Shipped | Larm-modellen (low_battery, over_temp, stale, lost, tamper med warning/critical): flaggstyrda larm reses i ingest-transaktionen, frånvaro-larmen av run-job scoreboard_health_sweep, en öppen rad per (enhet, typ) mekaniskt via ett partiellt unikt index, auto-lösning per typ (tamper löses endast manuellt). Trösklarna är konfig-data med treskikts-upplösning (enhet → kit → plattform), aldrig konstanter ✅ 1ee83e2c.m-2 (t-1) |
| F19.15.05 | Shipped | Tamper-eskalering: > tamper_escalation_count (default 3) händelser inom tamper_escalation_window_min (default 5) räknat sedan senaste operatörskvittens ⇒ severity='critical'; kvittensen fryser eskaleringen och är idempotent. Värdena är data, inte konstanter ✅ 1ee83e2c.m-2 (t-1) |
| F19.15.06 | Shipped | Förvaltningsytan: GET /v1/scoreboards, GET\ PATCH /v1/scoreboards/{id}, POST /v1/scoreboards och POST /v1/scoreboards/{id}/assign-match (bindningen höjer sessions-epoken och nollställer frame counter-fönstret) med ETag/If-Match, cursor-paginering och server-injicerat skop; m-2 lade larm-listan (GET /v1/scoreboards/alerts + kvittera/lös), trösklarna (/v1/scoreboards/telemetry-config*) och tamper-arm/-disarm. m-3 STÄNGDE resten: operatörens tvärs-tenant projektion (GET /v1/sys/scoreboards[/{id}], larm-inboxen GET /v1/sys/scoreboards/alerts och de fem mutationerna bakom sys.scoreboard.manage) och sys-konsolens yta /poangtavlor som renderar dem | ✅ 1ee83e2c.m-1 (t-2) + 1ee83e2c.m-2 (t-1/t-2) + 1ee83e2c.m-3 (t-2/t-3, ytan) |
| F19.15.07 | Shipped | SSE-ström av board-state till konsumenter: GET /v1/scoreboards/{id}/stream (hyresgästen/PL-driften) och GET /v1/sys/scoreboards/{id}/stream (operatören) — EN kärna, två handskakningar, fryst vokabulär (snapshot · board_state · health · alert), hjärtslag som SSE-kommentar, replay via Last-Event-ID (in-process buffert ≤ 50 ramar/5 min — garantin är snapshotten) och den normativa ?mode=snapshot-fallbacken. Konsumeras av sys-ytan /poangtavlor genom transportens requestEventStream ✅ 1ee83e2c.m-3 (t-1 strömmen, t-3 ytan) |
| F19.15.08 | Shipped | Ingesten är atomär, inte en efterföljande trigger: uplink-raden, poängraden, enhetens telemetri och audit-raden skrivs i en transaktion — varje utfall (applied/duplicate/rejected) lämnar ett spår i scoreboard_uplink_event ✅ 1ee83e2c.m-1 (t-1) |