Skip to main content

Documentation Index

Fetch the complete documentation index at: https://285e39fd5e337e58f16290.sightscreen.app/llms.txt

Use this file to discover all available pages before exploring further.

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

PollerCadenceDataMatches
ScorePoller (Poller 1)5 secondsScores, batting/bowling stats, match statusAll live matches
MarqueePoller (Poller 2)2.5 secondsEverything above + ball-by-ball dataCurrent marquee league only
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.
After filtering, 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):
PriorityEventAlert worthy?
P1match_endYes (P10 push)
P2innings_endYes
P3hattrickYes
P4on_hattrickYes
P5hundredYes
P6batsman_on_90sYes
P7consecutive_sixesYes
P8fiftyYes
P9batsman_on_fireYes
P10bowler_on_fireYes
P11wicketYes
P12new_batsmanYes
P13maidenYes
P14sixNo (P5 silent)
P15fourNo (P5 silent)
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

The LACoordinator listens for MatchBusEvent emissions from the EventBus:
  1. Queries live-activities table for all records with matchId and status=active
  2. Builds a single APNs payload from the UnifiedDisplayState
  3. Sets push priority based on isAlertWorthy (P10 or P5)
  4. Calls sendBatch via ApnsDeliveryService for all tokens
  5. Handles invalid token responses by marking records as status=ended, endReason=token_invalid
The LACoordinator builds ONE payload per match, not per user. Every follower of the same match gets the identical payload. This is a deliberate simplification — there is no team-centric perspective switching at the push level.

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.
Both pollers run independently with separate locks. The marquee poller and the main poller can process the same match concurrently for marquee league matches. The EventBus handles this gracefully — the active-matches record is the single source of truth for previous state, so duplicate raw data just produces no-op detections.

Key tables

TableKey schemaPurpose
active-matchesPK: matchIdTracks polling state, previous scores, cooldown map, last 30 balls
live-activitiesPK: matchId, SK: userId#deviceIdMaps users + devices to active LA tokens