Sightscreen runs five background jobs inside the main server process. They execute on independent timers and coordinate through DynamoDB state.
Job overview
| Job | Purpose | Interval | Status |
|---|
LiveScoreJob | Poll SportMonks for live score data | 2.5s (live) / 60s (idle) | Active |
ActivityPushJob | Push Live Activity updates to iOS devices | Runs after each LiveScoreJob cycle | Legacy |
PrematchNotificationJob | Send “starting soon” push notifications | 60s | Legacy |
FixtureSyncJob | Sync fixture metadata from SportMonks | 6 hours | Legacy |
ReplayScoreJob | Advance tape playback for replay matches | 2.5s (mock mode only) | Legacy |
System diagram
LiveScoreJob
Polls SportMonks for live match data and writes updated scores to DynamoDB.
What it does
- Checks which matches are currently live or about to start.
- Fetches score data from SportMonks for those matches.
- Writes updated score records to the scores table.
- Triggers the ActivityPushJob to propagate changes.
Polling interval
- 2.5 seconds when at least one match is live.
- 60 seconds when no matches are live (idle polling to detect new live matches).
Tables read/written
| Table | Operation |
|---|
| Matches table | Read (to find live matches) |
| Scores table | Write (updated score data) |
| Circuit breaker state | Read/Write |
Dependencies
- SportMonks API — the sole data source for live scores.
Circuit breaker
The job implements a circuit breaker to handle SportMonks outages:
- Closed (normal): requests flow through.
- Open (tripped): after N consecutive failures, the job stops calling SportMonks and enters a cooldown period.
- Half-open: after the cooldown, a single test request is made. If it succeeds, the circuit closes; if it fails, it re-opens.
During a circuit breaker open state, the last known scores remain in DynamoDB. Clients continue to see the most recent data, just not real-time updates.
Reconciliation
On each cycle, the job compares the SportMonks response with the current DynamoDB state. If discrepancies are found (e.g., missed ball events), it overwrites the DynamoDB record with the canonical SportMonks data.
Relevant env vars
| Variable | Purpose |
|---|
SPORTMONKS_API_TOKEN | API token for SportMonks |
SPORTMONKS_SEASON_ID | Season to query |
DISABLE_LIVE_SCORE_JOB | Set to true to disable this job |
LIVE_SCORE_POLL_INTERVAL_MS | Override the live polling interval |
IDLE_SCORE_POLL_INTERVAL_MS | Override the idle polling interval |
Failure modes
- SportMonks down: Circuit breaker opens. Scores freeze at last known state. Auto-recovers when SportMonks returns.
- DynamoDB write failure: Retried on next cycle. No data loss since SportMonks is the source of truth.
- Malformed response: Logged and skipped. The existing score record is preserved.
ActivityPushJob
Legacy — scheduled for refactoring. The current implementation documented below will change.
Detects score changes and pushes Live Activity updates to iOS devices via APNs.
What it does
- After LiveScoreJob writes new data, computes a hash of the current score state.
- Compares the hash to the previously pushed hash.
- If different, constructs a Live Activity payload and sends it to all devices subscribed to that match.
- Updates the stored hash.
State change detection
The job hashes key score fields (runs, wickets, overs, batting/bowling state) to produce a compact fingerprint. Only when the fingerprint changes does it push an update. This prevents redundant pushes when SportMonks returns identical data across cycles.
Self-heal recovery
If the job detects that a device’s Live Activity is in an inconsistent state (e.g., the activity ended on-device but the backend still thinks it’s active), it:
- Sends an
end event to terminate the stale activity.
- Optionally starts a new activity if the match is still live.
Tables read/written
| Table | Operation |
|---|
| Scores table | Read (current score state) |
| Push state table | Read/Write (last pushed hash, device tokens) |
| Devices table | Read (APNs tokens) |
| Subscriptions table | Read (which users follow which matches) |
Dependencies
- APNs — Apple Push Notification service for Live Activity updates.
Relevant env vars
| Variable | Purpose |
|---|
APNS_KEY_ID | APNs auth key ID |
APNS_TEAM_ID | Apple Developer Team ID |
APNS_BUNDLE_ID | App bundle identifier |
APNS_KEY_PATH | Path to the .p8 private key file |
APNS_ENVIRONMENT | production or sandbox |
DISABLE_ACTIVITY_PUSH_JOB | Set to true to disable |
Failure modes
- APNs down: Pushes fail silently. Next cycle retries because the hash won’t be updated.
- Invalid device token: APNs returns an error. The token is marked invalid and removed on next cleanup cycle.
- Hash collision (extremely unlikely): A real change is missed for one cycle but caught on the next.
PrematchNotificationJob
Legacy — scheduled for refactoring. The current implementation documented below will change.
Sends “match starting soon” push notifications to users who follow upcoming matches.
What it does
- Scans for matches starting within the notification window (default: 30 minutes).
- Looks up users who follow those matches.
- Sends a push notification to each user’s registered devices.
- Records that the notification was sent (dedup tracking) to avoid sending duplicates.
Polling interval
Runs every 60 seconds.
Tables read/written
| Table | Operation |
|---|
| Matches table | Read (upcoming matches) |
| Subscriptions table | Read (followers for each match) |
| Devices table | Read (APNs tokens) |
| Notifications tracking table | Read/Write (dedup records) |
Dependencies
- APNs — for sending push notifications.
Dedup tracking
Each (matchId, userId) pair is recorded after the notification is sent. If the job restarts or re-scans, it skips pairs that already have a record.
Relevant env vars
| Variable | Purpose |
|---|
DISABLE_PREMATCH_NOTIFICATION_JOB | Set to true to disable |
PREMATCH_NOTIFICATION_WINDOW_MINUTES | How many minutes before start to notify (default: 30) |
Failure modes
- APNs failure: Notification is not marked as sent. Will retry on next cycle.
- DynamoDB read failure: Job skips the cycle and retries next interval.
- Duplicate sends on restart: Mitigated by dedup tracking. If tracking write fails, the user may receive a duplicate — acceptable for “starting soon” alerts.
FixtureSyncJob
Legacy — scheduled for refactoring. The current implementation documented below will change.
Periodically syncs fixture metadata (start times, venues, team assignments) from SportMonks.
What it does
- Fetches the fixture list for the configured season from SportMonks.
- Compares with existing fixture records in DynamoDB.
- Updates any changed fields (start time, venue, status).
- Creates records for newly discovered fixtures.
Polling interval
Runs every 6 hours.
Tables read/written
| Table | Operation |
|---|
| Matches table | Read/Write (fixture metadata) |
| Leagues table | Read (to map SportMonks league IDs) |
Dependencies
- SportMonks API — fixture list endpoint.
Relevant env vars
| Variable | Purpose |
|---|
SPORTMONKS_API_TOKEN | API token for SportMonks |
SPORTMONKS_SEASON_ID | Season to sync |
DISABLE_FIXTURE_SYNC_JOB | Set to true to disable |
FIXTURE_SYNC_INTERVAL_MS | Override the sync interval |
Failure modes
- SportMonks down: Sync skipped. Existing fixture data remains unchanged. Retries on next cycle.
- Partial response: The job processes whatever fixtures are returned. Missing fixtures remain unchanged.
ReplayScoreJob
Legacy — scheduled for refactoring. The current implementation documented below will change.
Advances tape playback for replay matches. Only active in mock mode.
What it does
- Reads active replay matches and their associated tapes.
- For each replay match, advances the tape by one step (one ball/event).
- Writes the new score state to the scores table, mimicking what LiveScoreJob would write for a real match.
- Triggers ActivityPushJob to push the update to devices.
Polling interval
Runs every 2.5 seconds (same cadence as LiveScoreJob for realistic simulation).
Tables read/written
| Table | Operation |
|---|
| Replay matches table | Read (active replays, playback state) |
| Tapes table | Read (recorded match data) |
| Scores table | Write (mock score data) |
| Replay matches table | Write (advance playback position) |
Dependencies
- No external dependencies. Operates entirely on local DynamoDB data.
Relevant env vars
| Variable | Purpose |
|---|
MOCK_SPORTMONKS_URL | URL of the mock SportMonks server |
MOCK_SPORTMONKS_TOKEN | Token for the mock server |
DISABLE_REPLAY_SCORE_JOB | Set to true to disable |
Failure modes
- Missing tape data: Replay stops at the last available position. Logged as a warning.
- DynamoDB write failure: Retried on next cycle. Playback position is not advanced until the write succeeds.
ReplayScoreJob should never be enabled in production. It is gated behind mock mode configuration.