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 --> ExitThe 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
| Behavior | Implementation |
|---|---|
| Schedule refresh | Once per UTC day while games remain uncompleted |
| Game matching | NFL week, so a moved kickoff can still use the configured squares |
| First eligible check | Three hours after the cached kickoff |
| Normal retry | Each hourly invocation until final status |
| Long delay | Once per UTC day after kickoff + 24 hours |
| Finished games | Reuse cached final results; completed games need no new score requests |
| ESPN request limits | 5-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 handlingThe 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.