Skip to content

Game processing

function_app.py registers one timer with the six-field schedule 0 0 * * * *: the start of every hour. The scheduler decides which work is due. No public HTTP function is registered in this application.

From timer to completed game

flowchart TD
    Tick[Hourly invocation] --> Active{Inside active season?}
    Active -->|No| Exit[Return without external work]
    Active -->|Yes| Dry{DRY_RUN enabled?}
    Dry -->|Yes| Exit
    Dry -->|No| Load[Load private season state]
    Load --> Snapshot[Publish changed public pool data]
    Snapshot --> Done{All games completed?}
    Done -->|Yes| Milestones[Check pending halfway and season-end emails]
    Done -->|No| Schedule[Refresh kickoff cache if due]
    Schedule --> Due{Uncompleted game at least 3 hours after kickoff?}
    Due -->|No| Milestones
    Due -->|Yes| Final{Cached final result or ESPN final?}
    Final -->|No| Retry[Record check and retry later]
    Final -->|Yes| Save[Cache scores and winners]
    Save --> Publish[Publish latest results]
    Publish --> Deliver[Claim and submit winner and summary emails]
    Deliver --> Complete[Mark game completed after successful handling]
    Complete --> Milestones
    Retry --> Milestones
    Milestones --> Exit

The active window begins seven days before the first configured game date and ends fourteen days after the last. Outside it, the scheduler returns before accessing storage or ESPN. DRY_RUN=true also exits before state, publishing, or delivery work.

Kickoff and score checks

BehaviorImplementation
Schedule refreshOnce per UTC day while games remain uncompleted
Game matchingNFL week, so a moved kickoff can still use the configured squares
First eligible checkThree hours after the cached kickoff
Normal retryEach hourly invocation until final status
Long delayOnce per UTC day after kickoff + 24 hours
Finished gamesReuse cached final results; completed games need no new score requests
ESPN request limits5-second connection and 25-second read timeouts; up to two GET retries

The timer waits for ESPN’s post state. Halftime notifications are not live halftime notifications: both periods are calculated and sent after the game finishes. Quarter-one plus quarter-two scores determine halftime; the final score includes whatever ESPN reports as the completed game’s final total. Missing or malformed scores are rejected rather than treated as zero.

Winner lookup

For each period, take the last digit of each team’s score. Find those digits in the week’s Ravens and opponent digit lists. Their intersection selects one owned square. A 23–17 score therefore selects Ravens digit 3 and opponent digit 7.

The data model stores Ravens on rows and opponents on columns; the browser transposes that layout to match the printed pool sheet. See pool data.

Publication and email order

sequenceDiagram
    participant Timer as Scheduler
    participant State as Private state
    participant Blob as Public Blob Storage
    participant ACS as Email provider
    Timer->>State: Persist verified final result
    Timer->>Blob: Write latest and daily JSON
    Timer->>State: Save publication marker
    Timer->>State: Claim delivery as sending using ETag
    Timer->>ACS: Submit HTML and plain text
    ACS-->>Timer: Accepted successfully
    Timer->>State: Mark delivery sent
    Timer->>State: Mark game completed after delivery handling

The scheduler also checks the public snapshot’s content hash before game processing. Changes to names, squares, or other public data can refresh the site even when all games are completed. The hash excludes as_of_utc, so an unchanged snapshot does not get rewritten merely because another hour passed.

After game processing, the scheduler checks season-summary milestones. It also checks them when all games are already completed. Each milestone waits for verified results across its reporting period and uses a separate delivery claim. A failed milestone does not undo completed games or resend their emails; pending milestones are checked again on the next invocation inside the active window. Ambiguous delivery claims still require operator review.

Game-processing errors are logged by operation and exception type. After attempting other eligible games, the invocation fails if any game work failed. The initial state load, snapshot publication, and schedule refresh occur before that per-game error loop; a failure there can stop the invocation earlier.

For delivery claims that remain sending, follow the recovery procedure.