Architecture
The pool has a Python background worker, a static browser client, and a separate documentation site. Storage connects the worker and the client; they do not call each other directly.
Resources and trust boundaries
flowchart TB
subgraph Delivery[GitLab]
Repo[Repository] --> Checks[Tests, lint, SBOMs, security]
Checks --> WebDeploy[Static Web Apps deployment]
Checks --> FunctionDeploy[Function ZIP deployment]
Checks --> DocsDeploy[Pages publication]
end
subgraph Azure[Azure application resources]
Timer[Hourly timer] --> Function[Python Function]
ESPN[ESPN API] --> Function
Function <--> State[Private pool-state container]
Function --> Public[Public results in Blob Storage]
Function --> ACS[Communication Services Email]
WebDeploy --> SWA[Static Web Apps]
FunctionDeploy --> Function
end
SWA --> Browser[Visitor browser]
Public --> Browser
Browser --> Sentry[Sentry browser error reporting]
ACS --> Inbox[Recipient inbox]
DocsDeploy --> Pages[Documentation on GitLab Pages]| Component | Responsibility | Source |
|---|---|---|
| Timer entry point | Registers check_pool and invokes the scheduler | function_app.py |
| Scheduler | Active-season checks, kickoff cache, processing, delivery claims | app/scheduler.py |
| ESPN client | Schedule and final-score retrieval with bounded requests | app/espn.py |
| Pool rules | Square ownership and per-week digit assignments | app/pool.py |
| Report builder | Public snapshot, winner lookup, summary content | app/service.py |
| State store | Conditional writes to private season state | app/state.py |
| Publisher | Latest and UTC-day JSON snapshots | app/storage_publish.py |
| Email and templates | ACS submission, HTML/plain-text rendering | app/emailer.py, app/templates/ |
| Browser client | Player selector, schedule, board, results | public/ |
| Browser monitoring | JavaScript errors, environment, and deployed release | public/sentry.js, ci/swa/configure-sentry.cjs |
| Documentation | Searchable maintenance guides and Mermaid diagrams | docs/, ci/docs/astro.config.mjs |
Storage has two roles
Function host storage supports the Azure Functions runtime and timer
coordination. Azure configures it through AzureWebJobsStorage.
Results storage is selected by RESULTS_STORAGE_CONNECTION_STRING and holds:
| Path | Access | Contents |
|---|---|---|
$web/results.json | Public read for the website | Latest public pool snapshot |
$web/history/YYYY-MM-DD.json | Public read | Most recent snapshot written on that UTC day |
pool-state/<season>.json | Private | Schedule cache, results, publication and email markers |
These roles may use the same storage account, but they are separate settings.
The application creates pool-state when needed and rejects public access to
that container. It does not create the $web container for you.
The daily history path is overwritten when another publication occurs on the same UTC day. It is not an append-only audit log or a backup of private state.
Public data boundary
The public snapshot includes player IDs, display names, squares, digit assignments, verified game results, and earned-prize totals. It excludes email addresses and notification flags. Private season state contains processing and delivery markers and must not be copied into the public container or documentation output.
The documentation build reads docs/ only. It does not include the roster PDF,
private state, player contact records, or local environment files.
Independent release paths
- Changing
public/deploys the browser client to Static Web Apps. - Changing Function source or runtime dependencies deploys the Python ZIP.
- Updating pool/player configuration requires a Function deployment; the next active-season invocation publishes changed public data.
- The documentation is built into
docs-site/and published to GitLab Pages.
See the pipeline guide for exact jobs and deployment triggers.