# andyscanzio/nl-predict architecture

> Architecture diagram of the GitHub repository andyscanzio/nl-predict, generated by GitDiagram from its file tree, README and a sample of source files (last updated 2026-09-30). It is an AI-made overview: check details against the source.

- Interactive diagram: https://gitdiagram.com/andyscanzio/nl-predict
- Repository: https://github.com/andyscanzio/nl-predict

## Overview

NL Predict is a browser-based Swiss National League standings projector. The workflow starts with a committed SIHF snapshot, derives current standings and form, applies a selectable model to remaining games, and simulates the season to estimate ranks and playoff-zone chances. The page presents current and projected tables, upcoming games, what-if outcomes, and projection-history charts. README documents snapshot ingestion and precomputed history; unsampled implementation details and their wiring are not inferred here.

Main entry points: [`src/ui/App.tsx`](https://github.com/andyscanzio/nl-predict/blob/main/src/ui/App.tsx) (Page orchestration), [`src/domain/projectionModels.ts`](https://github.com/andyscanzio/nl-predict/blob/main/src/domain/projectionModels.ts) (Model registry), [`src/domain/project.ts`](https://github.com/andyscanzio/nl-predict/blob/main/src/domain/project.ts) (Projection and tables), [`src/ui/ProjectedTable.tsx`](https://github.com/andyscanzio/nl-predict/blob/main/src/ui/ProjectedTable.tsx) (Projected table)

## Diagram (Mermaid)

```mermaid
flowchart TD

subgraph group_data["Snapshot data"]
  node_snapshot_builder["Snapshot builder<br/>[buildSnapshot.ts]"]
  node_sihf_response["SIHF response parser<br/>[sihfResponse.ts]"]
  node_snapshot["Season snapshot<br/>[games.json]"]
  node_history_build["History precompute"]
  node_history_cache["History cache<br/>[historyCache.ts]"]
end

subgraph group_models["Projection models"]
  node_elo["Elo model<br/>[eloModel.ts]"]
  node_season_rate["Season rate model<br/>[seasonRate.ts]"]
  node_matchup["Matchup model<br/>[matchupModel.ts]"]
  node_split_rate["Split form rate<br/>[splitFormRate.ts]"]
  node_model_registry["Model registry"]
end

subgraph group_engine["Projection engine"]
  node_forms["Game form<br/>[form.ts]"]
  node_outcomes["Outcome probabilities<br/>[outcomes.ts]"]
  node_projection["Projection and tables<br/>[project.ts]"]
  node_simulation["Season simulation"]
  node_cut_lines["Cut lines<br/>[cutLines.ts]"]
  node_history["Projection history"]
end

subgraph group_web["Browser experience"]
  node_app["Page orchestration<br/>[App.tsx]"]
  node_main["Browser entry<br/>[main.tsx]"]
  node_current_table["Current table<br/>[CurrentTable.tsx]"]
  node_projected_table["Projected table<br/>[ProjectedTable.tsx]"]
  node_upcoming["Upcoming games<br/>[UpcomingGames.tsx]"]
  node_team_detail["Team detail<br/>[TeamDetail.tsx]"]
  node_chart["History charts<br/>[projectionChart.ts]"]
  node_explanation["Model explanation"]
  node_team_name["Team names<br/>[TeamName.tsx]"]
end

node_visitor(("Visitor"))
node_sihf{{"SIHF API"}}

node_sihf -.->|"provides results"| node_snapshot_builder
node_snapshot_builder -->|"parses response"| node_sihf_response
node_snapshot_builder -.->|"writes snapshot"| node_snapshot
node_snapshot -.->|"loads data"| node_main
node_main -->|"renders"| node_app
node_visitor -.->|"uses page"| node_app
node_app -->|"projects season"| node_projection
node_app -->|"renders"| node_current_table
node_app -->|"renders"| node_explanation
node_app -->|"renders"| node_projected_table
node_app -->|"renders"| node_upcoming
node_model_registry -->|"registers"| node_elo
node_model_registry -->|"registers"| node_season_rate
node_model_registry -->|"registers"| node_matchup
node_model_registry -->|"registers"| node_split_rate
node_projection -.->|"selects model"| node_model_registry
node_elo -->|"scores games"| node_forms
node_elo -->|"computes probabilities"| node_outcomes
node_matchup -->|"uses venue form"| node_forms
node_matchup -->|"computes probabilities"| node_outcomes
node_season_rate -->|"computes probabilities"| node_outcomes
node_projection -->|"derives form"| node_forms
node_projection -.->|"simulates standings"| node_simulation
node_simulation -->|"assigns zones"| node_cut_lines
node_history -->|"replays projection"| node_projection
node_history_build -->|"precomputes history"| node_history
node_history_build -.->|"caches results"| node_history_cache
node_projected_table -->|"renders details"| node_team_detail
node_team_detail -->|"builds charts"| node_chart
node_projected_table -->|"reads history"| node_history
node_team_detail -->|"reads history"| node_history
node_current_table -->|"renders names"| node_team_name
node_projected_table -.->|"renders names"| node_team_name
node_upcoming -.->|"shows probabilities"| node_outcomes

click node_snapshot_builder "https://github.com/andyscanzio/nl-predict/blob/main/src/snapshot/buildSnapshot.ts"
click node_sihf_response "https://github.com/andyscanzio/nl-predict/blob/main/src/snapshot/sihfResponse.ts"
click node_snapshot "https://github.com/andyscanzio/nl-predict/blob/main/data/games.json"
click node_history_build "https://github.com/andyscanzio/nl-predict/blob/main/scripts/projectionHistoryPlugin.ts"
click node_history_cache "https://github.com/andyscanzio/nl-predict/blob/main/scripts/historyCache.ts"
click node_forms "https://github.com/andyscanzio/nl-predict/blob/main/src/domain/form.ts"
click node_outcomes "https://github.com/andyscanzio/nl-predict/blob/main/src/domain/outcomes.ts"
click node_elo "https://github.com/andyscanzio/nl-predict/blob/main/src/domain/eloModel.ts"
click node_season_rate "https://github.com/andyscanzio/nl-predict/blob/main/src/domain/seasonRate.ts"
click node_matchup "https://github.com/andyscanzio/nl-predict/blob/main/src/domain/matchupModel.ts"
click node_split_rate "https://github.com/andyscanzio/nl-predict/blob/main/src/domain/splitFormRate.ts"
click node_model_registry "https://github.com/andyscanzio/nl-predict/blob/main/src/domain/projectionModels.ts"
click node_projection "https://github.com/andyscanzio/nl-predict/blob/main/src/domain/project.ts"
click node_simulation "https://github.com/andyscanzio/nl-predict/blob/main/src/domain/seasonSimulation.ts"
click node_cut_lines "https://github.com/andyscanzio/nl-predict/blob/main/src/domain/cutLines.ts"
click node_history "https://github.com/andyscanzio/nl-predict/blob/main/src/domain/projectionHistory.ts"
click node_app "https://github.com/andyscanzio/nl-predict/blob/main/src/ui/App.tsx"
click node_main "https://github.com/andyscanzio/nl-predict/blob/main/src/ui/main.tsx"
click node_current_table "https://github.com/andyscanzio/nl-predict/blob/main/src/ui/CurrentTable.tsx"
click node_projected_table "https://github.com/andyscanzio/nl-predict/blob/main/src/ui/ProjectedTable.tsx"
click node_upcoming "https://github.com/andyscanzio/nl-predict/blob/main/src/ui/UpcomingGames.tsx"
click node_team_detail "https://github.com/andyscanzio/nl-predict/blob/main/src/ui/TeamDetail.tsx"
click node_chart "https://github.com/andyscanzio/nl-predict/blob/main/src/ui/projectionChart.ts"
click node_explanation "https://github.com/andyscanzio/nl-predict/blob/main/src/ui/ModelExplanation.tsx"
click node_team_name "https://github.com/andyscanzio/nl-predict/blob/main/src/ui/TeamName.tsx"

classDef toneNeutral fill:#f8fafc,stroke:#334155,stroke-width:1.5px,color:#0f172a
classDef toneBlue fill:#dbeafe,stroke:#2563eb,stroke-width:1.5px,color:#172554
classDef toneAmber fill:#fef3c7,stroke:#d97706,stroke-width:1.5px,color:#78350f
classDef toneMint fill:#dcfce7,stroke:#16a34a,stroke-width:1.5px,color:#14532d
classDef toneRose fill:#ffe4e6,stroke:#e11d48,stroke-width:1.5px,color:#881337
classDef toneIndigo fill:#e0e7ff,stroke:#4f46e5,stroke-width:1.5px,color:#312e81
classDef toneTeal fill:#ccfbf1,stroke:#0f766e,stroke-width:1.5px,color:#134e4a
class node_snapshot_builder,node_sihf_response,node_snapshot,node_history_build,node_history_cache toneBlue
class node_elo,node_season_rate,node_matchup,node_split_rate,node_model_registry toneAmber
class node_forms,node_outcomes,node_projection,node_simulation,node_cut_lines,node_history,node_sihf toneMint
class node_app,node_main,node_current_table,node_projected_table,node_upcoming,node_team_detail,node_chart,node_explanation,node_team_name toneRose
class node_visitor toneIndigo
```

## Components

### Snapshot data

- [Snapshot builder](https://github.com/andyscanzio/nl-predict/blob/main/src/snapshot/buildSnapshot.ts): `src/snapshot/buildSnapshot.ts`
- [SIHF response parser](https://github.com/andyscanzio/nl-predict/blob/main/src/snapshot/sihfResponse.ts): `src/snapshot/sihfResponse.ts`
- [Season snapshot](https://github.com/andyscanzio/nl-predict/blob/main/data/games.json): `data/games.json`
- [History precompute](https://github.com/andyscanzio/nl-predict/blob/main/scripts/projectionHistoryPlugin.ts): `scripts/projectionHistoryPlugin.ts`
- [History cache](https://github.com/andyscanzio/nl-predict/blob/main/scripts/historyCache.ts): `scripts/historyCache.ts`

### Projection models

- [Elo model](https://github.com/andyscanzio/nl-predict/blob/main/src/domain/eloModel.ts): `src/domain/eloModel.ts`
- [Season rate model](https://github.com/andyscanzio/nl-predict/blob/main/src/domain/seasonRate.ts): `src/domain/seasonRate.ts`
- [Matchup model](https://github.com/andyscanzio/nl-predict/blob/main/src/domain/matchupModel.ts): `src/domain/matchupModel.ts`
- [Split form rate](https://github.com/andyscanzio/nl-predict/blob/main/src/domain/splitFormRate.ts): `src/domain/splitFormRate.ts`
- [Model registry](https://github.com/andyscanzio/nl-predict/blob/main/src/domain/projectionModels.ts): `src/domain/projectionModels.ts`

### Projection engine

- [Game form](https://github.com/andyscanzio/nl-predict/blob/main/src/domain/form.ts): `src/domain/form.ts`
- [Outcome probabilities](https://github.com/andyscanzio/nl-predict/blob/main/src/domain/outcomes.ts): `src/domain/outcomes.ts`
- [Projection and tables](https://github.com/andyscanzio/nl-predict/blob/main/src/domain/project.ts): `src/domain/project.ts`
- [Season simulation](https://github.com/andyscanzio/nl-predict/blob/main/src/domain/seasonSimulation.ts): `src/domain/seasonSimulation.ts`
- [Cut lines](https://github.com/andyscanzio/nl-predict/blob/main/src/domain/cutLines.ts): `src/domain/cutLines.ts`
- [Projection history](https://github.com/andyscanzio/nl-predict/blob/main/src/domain/projectionHistory.ts): `src/domain/projectionHistory.ts`

### Browser experience

- [Page orchestration](https://github.com/andyscanzio/nl-predict/blob/main/src/ui/App.tsx): `src/ui/App.tsx`
- [Browser entry](https://github.com/andyscanzio/nl-predict/blob/main/src/ui/main.tsx): `src/ui/main.tsx`
- [Current table](https://github.com/andyscanzio/nl-predict/blob/main/src/ui/CurrentTable.tsx): `src/ui/CurrentTable.tsx`
- [Projected table](https://github.com/andyscanzio/nl-predict/blob/main/src/ui/ProjectedTable.tsx): `src/ui/ProjectedTable.tsx`
- [Upcoming games](https://github.com/andyscanzio/nl-predict/blob/main/src/ui/UpcomingGames.tsx): `src/ui/UpcomingGames.tsx`
- [Team detail](https://github.com/andyscanzio/nl-predict/blob/main/src/ui/TeamDetail.tsx): `src/ui/TeamDetail.tsx`
- [History charts](https://github.com/andyscanzio/nl-predict/blob/main/src/ui/projectionChart.ts): `src/ui/projectionChart.ts`
- [Model explanation](https://github.com/andyscanzio/nl-predict/blob/main/src/ui/ModelExplanation.tsx): `src/ui/ModelExplanation.tsx`
- [Team names](https://github.com/andyscanzio/nl-predict/blob/main/src/ui/TeamName.tsx): `src/ui/TeamName.tsx`

### Other components

- **Visitor**
- **SIHF API**

## Connections

- SIHF API → Snapshot builder: provides results (evidence: `README.md`)
- Snapshot builder → SIHF response parser: parses response (evidence: `src/snapshot/buildSnapshot.ts`)
- Snapshot builder → Season snapshot: writes snapshot (evidence: `README.md`)
- Season snapshot → Browser entry: loads data (evidence: `README.md`)
- Browser entry → Page orchestration: renders (evidence: `src/ui/main.tsx`)
- Visitor → Page orchestration: uses page
- Page orchestration → Projection and tables: projects season (evidence: `src/ui/App.tsx`)
- Page orchestration → Current table: renders (evidence: `src/ui/App.tsx`)
- Page orchestration → Model explanation: renders (evidence: `src/ui/App.tsx`)
- Page orchestration → Projected table: renders (evidence: `src/ui/App.tsx`)
- Page orchestration → Upcoming games: renders (evidence: `src/ui/App.tsx`)
- Model registry → Elo model: registers (evidence: `src/domain/projectionModels.ts`)
- Model registry → Season rate model: registers (evidence: `src/domain/projectionModels.ts`)
- Model registry → Matchup model: registers (evidence: `src/domain/projectionModels.ts`)
- Model registry → Split form rate: registers (evidence: `src/domain/projectionModels.ts`)
- Projection and tables → Model registry: selects model (evidence: `src/domain/projectionModels.ts`)
- Elo model → Game form: scores games (evidence: `src/domain/eloModel.ts`)
- Elo model → Outcome probabilities: computes probabilities (evidence: `src/domain/eloModel.ts`)
- Matchup model → Game form: uses venue form (evidence: `src/domain/matchupModel.ts`)
- Matchup model → Outcome probabilities: computes probabilities (evidence: `src/domain/matchupModel.ts`)
- Season rate model → Outcome probabilities: computes probabilities (evidence: `src/domain/seasonRate.ts`)
- Projection and tables → Game form: derives form (evidence: `src/domain/project.ts`)
- Projection and tables → Season simulation: simulates standings (evidence: `src/domain/project.ts`)
- Season simulation → Cut lines: assigns zones (evidence: `src/domain/seasonSimulation.ts`)
- Projection history → Projection and tables: replays projection (evidence: `src/domain/projectionHistory.ts`)
- History precompute → Projection history: precomputes history (evidence: `scripts/projectionHistoryPlugin.ts`)
- History precompute → History cache: caches results (evidence: `README.md`)
- Projected table → Team detail: renders details (evidence: `src/ui/ProjectedTable.tsx`)
- Team detail → History charts: builds charts (evidence: `src/ui/TeamDetail.tsx`)
- Projected table → Projection history: reads history (evidence: `src/ui/ProjectedTable.tsx`)
- Team detail → Projection history: reads history (evidence: `src/ui/TeamDetail.tsx`)
- Current table → Team names: renders names (evidence: `src/ui/CurrentTable.tsx`)
- Projected table → Team names: renders names (evidence: `src/ui/ProjectedTable.tsx`)
- Upcoming games → Outcome probabilities: shows probabilities (evidence: `src/ui/UpcomingGames.tsx`)
