How it works
The live score pipeline is fully event-driven. There is no monolithic job — instead, a chain of specialized components each do one thing.Two pollers, two cadences
Both pollers are instances of the same reusable
ScorePoller class. The marquee poller fetches the balls include from SportMonks, which provides ball-by-ball data needed for Tier1 event detection (sixes, fours, maidens, hat-tricks).
For non-marquee matches, only Tier0 (score-based) events are detected.
MatchEventBus: the detection pipeline
The EventBus receives raw match data and runs it through a four-stage detection pipeline:1
Tier0EventDetector (score-based)
Compares current scores against previous scores stored in
active-matches. Detects: match_end, innings_end, hundred, batsman_on_90s, fifty, batsman_on_fire, bowler_on_fire, wicket, new_batsman.2
Tier1EventDetector (ball-based)
Analyzes the last 30 balls stored in
active-matches. Detects: hattrick, on_hattrick, consecutive_sixes, maiden, six, four. Only runs for marquee league matches (requires ball data).3
CooldownFilter
Suppresses duplicate events using a cooldowns map stored on the
active-matches record. For example, a fifty event for the same batsman won’t fire again within the cooldown window.4
StalenessFilter
Ignores events that are stale given the current state. For example, a
fifty event is stale if the batsman already has 100 — the hundred event takes priority.pickBestEvent priority-sorts all remaining candidates and selects the single highest-priority event as the keyEvent.
Event priorities and alert worthiness
Events are sorted by priority (lower number = higher priority):Alert-worthy events are sent as P10 (high priority) APNs pushes that wake the screen and show a banner. Non-alert events (six, four) are sent as P5 (silent) pushes that update the Live Activity without disturbing the user.
DisplayStateBuilder
DisplayStateBuilder takes the raw match data and the selected keyEvent and builds a UnifiedDisplayState. This is the single payload shape that iOS renders on the Lock Screen and Dynamic Island. It includes: team names, scores, overs, batting/bowling stats, recent balls, run rate, chase info, and the key event label.
LACoordinator: fan-out to devices
TheLACoordinator listens for MatchBusEvent emissions from the EventBus:
- Queries
live-activitiestable for all records withmatchIdandstatus=active - Builds a single APNs payload from the
UnifiedDisplayState - Sets push priority based on
isAlertWorthy(P10 or P5) - Calls
sendBatchviaApnsDeliveryServicefor all tokens - Handles invalid token responses by marking records as
status=ended, endReason=token_invalid
Resilience
Circuit breaker
The SportMonks client uses cockatiel for circuit breaking. If the API returns repeated 5xx errors, the circuit opens and requests are short-circuited for a cooldown period. This prevents hammering a degraded upstream.Distributed locking
Each poller acquires a distributed lock before polling to prevent duplicate processing when multiple backend instances are running. If the lock is held by another instance, the poller skips that cycle.Key tables
Related pages
- Live Activity lifecycle — how LAs are created, updated, and ended
- Match following — what triggers the LA creation that feeds into this pipeline
- Device registration — how push tokens get registered in the first place