Skip to content

Commit 9bf2d56

Browse files
committed
Update README
1 parent e43da46 commit 9bf2d56

1 file changed

Lines changed: 322 additions & 13 deletions

File tree

README.md

Lines changed: 322 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,340 @@
11
# CodeSignal Probability Lab
22

3-
A Bespoke Simulation for repeated-trial probability experiments (coin, die, spinner) that visualizes convergence: as you run more trials, relative frequencies become more stable and tend to approach theoretical probabilities.
3+
## Overview
44

5-
## What’s Included
5+
CodeSignal Probability Lab is an interactive probability simulator for repeated-trial experiments. It is designed to help learners compare theoretical probability with experimental results and see convergence over time.
66

7-
- **One event mode**: event builder (select outcomes), live bar chart, convergence chart, frequency table
8-
- **Two events mode**: joint heatmap + two-way table; click a cell to see joint and conditional probabilities
9-
- **Bias controls**: explore fair vs biased devices
7+
The app currently supports:
108

11-
## Development
9+
- Single-event experiments with a coin, die, spinner, or custom device
10+
- Two-event experiments with joint outcomes shown in a heatmap and two-way table
11+
- Fair and biased devices
12+
- Independent and dependent relationships in two-event mode
13+
- Custom devices with 2-50 outcomes and optional custom probabilities
14+
- Optional UI sections such as bar chart, convergence chart, frequency table, joint distribution, two-way table, and single-mode history
15+
- Activity logging to `activity.log` for grading
16+
17+
## Using the App
18+
19+
### Install dependencies
20+
21+
```bash
22+
npm ci
23+
# or
24+
npm install
25+
```
26+
27+
### Start in development
1228

1329
```bash
1430
npm run start:dev
1531
```
1632

17-
Open `http://localhost:3000`.
33+
Then open [http://localhost:3000](http://localhost:3000).
1834

19-
## Build / Production
35+
Development uses two local servers:
36+
37+
- `http://localhost:3000`: Vite dev server for the app UI
38+
- `http://localhost:3001`: API server that accepts `/log` requests and writes `activity.log`
39+
40+
In dev mode, the browser sends activity events to `/log`, and Vite proxies those requests to port `3001`.
41+
42+
### Start a production-style build locally
2043

2144
```bash
2245
npm run build
2346
npm run start:prod
2447
```
2548

26-
## Key Files
49+
This serves the built app on [http://localhost:3000](http://localhost:3000) and writes activity events to the same root-level `activity.log` file.
50+
51+
### Use an alternate production config
52+
53+
By default, the production server reads `./config.json` from the repository root. You can point it at another file with `CONFIG_PATH`.
54+
55+
```bash
56+
CONFIG_PATH=./some-other-config.json npm run start:prod
57+
```
58+
59+
## Configuring `config.json`
60+
61+
The app loads its runtime configuration from `/config.json`.
62+
63+
If a field is missing or invalid, the app falls back to safe defaults. Invalid custom probabilities are normalized when possible; otherwise they fall back to a uniform distribution.
64+
65+
### Supported top-level keys
66+
67+
| Key | Used in | Description |
68+
| --- | --- | --- |
69+
| `mode` | all configs | `"single"` or `"two"` |
70+
| `device` | single mode | Initial device: `"coin"`, `"die"`, `"spinner"`, or `"custom"` |
71+
| `deviceA` | two mode | Initial device for event A |
72+
| `deviceB` | two mode | Initial device for event B |
73+
| `deviceSettings` | single mode with `device: "custom"` | Custom device definition |
74+
| `deviceASettings` | two mode with `deviceA: "custom"` | Custom device definition for A |
75+
| `deviceBSettings` | two mode with `deviceB: "custom"` | Custom device definition for B |
76+
| `sections` | all configs | Controls which result panels are visible |
77+
| `visualElements` | all configs | Controls selected UI elements such as the edit button and bias tag |
78+
79+
### Supported values and defaults
80+
81+
| Setting | Valid values | Default if missing or invalid |
82+
| --- | --- | --- |
83+
| `mode` | `single`, `two` | `single` |
84+
| `device`, `deviceA`, `deviceB` | `coin`, `die`, `spinner`, `custom` | `coin` |
85+
| `sections.*` | `true`, `false` | `false` for each supported section key |
86+
| `visualElements.editExperimentButton` | `true`, `false` | `true` |
87+
| `visualElements.biasTag` | `true`, `false` | `true` |
88+
89+
### `sections` keys
90+
91+
Single-mode section keys:
92+
93+
- `barChart`
94+
- `convergence`
95+
- `frequencyTable`
96+
- `history`
97+
98+
Two-mode section keys:
99+
100+
- `jointDistribution`
101+
- `twoWayTable`
102+
103+
Notes:
104+
105+
- `history` only applies to single mode
106+
- When `history` is `true` in single mode, history is shown as a standalone widget card instead of only through the History modal
107+
- Missing or invalid section values default to `false`
108+
109+
### `visualElements` keys
110+
111+
Supported UI toggles:
112+
113+
- `editExperimentButton`
114+
- `biasTag`
115+
116+
Both default to `true`.
117+
118+
### Custom device settings
119+
120+
Each custom device settings object can include:
121+
122+
| Key | Required | Description |
123+
| --- | --- | --- |
124+
| `name` | no | Display name for the custom device |
125+
| `icon` | no | Optional icon string shown in the UI |
126+
| `outcomes` | yes | Array of outcome labels |
127+
| `probabilities` | no | Array of non-negative weights or probabilities aligned with `outcomes` |
128+
129+
Current constraints:
130+
131+
- `outcomes` must contain 2-50 unique, non-empty strings
132+
- Extra outcomes beyond 50 are truncated
133+
- Duplicate or empty outcome labels are ignored
134+
- If `probabilities` is present, it must match the final outcome count
135+
- Probability values must be non-negative numbers
136+
- Probability values are normalized to sum to `1`
137+
- If `probabilities` is missing, invalid, or sums to `0`, the app uses a uniform distribution
138+
139+
### What is not configured through `config.json`
140+
141+
Do not pretend `config.json` controls everything. It does not.
142+
143+
These are adjusted in the app UI, not through runtime config:
144+
145+
- Bias settings for coin, die, and spinner
146+
- Spinner sector count
147+
- Selected event outcomes in single mode
148+
- Relationship mode in two-event experiments (`independent` or `dependent`)
149+
150+
## `config.json` Examples
151+
152+
### Single mode with a standard device
153+
154+
```json
155+
{
156+
"mode": "single",
157+
"device": "die",
158+
"sections": {
159+
"barChart": true,
160+
"convergence": true,
161+
"frequencyTable": true,
162+
"history": false
163+
},
164+
"visualElements": {
165+
"editExperimentButton": true,
166+
"biasTag": true
167+
}
168+
}
169+
```
170+
171+
### Single mode with a custom device
172+
173+
```json
174+
{
175+
"mode": "single",
176+
"device": "custom",
177+
"deviceSettings": {
178+
"name": "Exam",
179+
"icon": "📚",
180+
"outcomes": ["Pass", "Fail"],
181+
"probabilities": [0.7, 0.3]
182+
},
183+
"sections": {
184+
"barChart": true,
185+
"convergence": true,
186+
"frequencyTable": true,
187+
"history": true
188+
},
189+
"visualElements": {
190+
"editExperimentButton": true,
191+
"biasTag": true
192+
}
193+
}
194+
```
195+
196+
### Two-event mode with standard devices
197+
198+
```json
199+
{
200+
"mode": "two",
201+
"deviceA": "coin",
202+
"deviceB": "die",
203+
"sections": {
204+
"jointDistribution": true,
205+
"twoWayTable": true
206+
},
207+
"visualElements": {
208+
"editExperimentButton": true,
209+
"biasTag": true
210+
}
211+
}
212+
```
213+
214+
### Two-event mode with custom devices
215+
216+
```json
217+
{
218+
"mode": "two",
219+
"deviceA": "custom",
220+
"deviceASettings": {
221+
"name": "Weather",
222+
"icon": "🌦",
223+
"outcomes": ["Sunny", "Rainy", "Snowy"],
224+
"probabilities": [0.6, 0.3, 0.1]
225+
},
226+
"deviceB": "custom",
227+
"deviceBSettings": {
228+
"name": "Traffic",
229+
"icon": "🚗",
230+
"outcomes": ["Light", "Medium", "Heavy"],
231+
"probabilities": [0.5, 0.35, 0.15]
232+
},
233+
"sections": {
234+
"jointDistribution": true,
235+
"twoWayTable": true
236+
},
237+
"visualElements": {
238+
"editExperimentButton": true,
239+
"biasTag": true
240+
}
241+
}
242+
```
243+
244+
## Activity Logging and Grading
245+
246+
The app writes grading or review data to a root-level `activity.log` file as JSON Lines: one JSON object per line.
247+
248+
Behavior by environment:
249+
250+
- Development: the browser posts to `/log`, Vite proxies that request to the API server on port `3001`, and `server.js` appends to `activity.log`
251+
- Production: `server.js` serves the built app and appends the same event stream to `activity.log`
252+
253+
The log is append-only. If you want a clean grading run, you need to clear or rotate the file yourself before starting.
254+
255+
### Event types written today
256+
257+
| Event type | When it appears | What it contains |
258+
| --- | --- | --- |
259+
| `app_start` | Initial app load | A config snapshot with mode, device selection, and visible sections |
260+
| `settings_change` | User changes settings | Changed keys plus a full settings snapshot |
261+
| `run_reset` | A run is reset because settings changed | Reset reason plus a full settings snapshot |
262+
| `status` | At trial milestones during simulation | Current trial count and mode-specific results |
263+
| `click` | User clicks a selected cell in two-event mode | Source and selected cell labels |
264+
265+
### Status milestone schedule
266+
267+
`status` events are not written on every single trial forever. They are logged at milestone counts:
268+
269+
`1, 2, 5, 10, 20, 50, 100, 200, 500, 1000, ...`
270+
271+
That keeps the log useful without making it absurdly noisy.
272+
273+
### Mode-specific `status` payloads
274+
275+
Single-mode `status` events can include:
276+
277+
- `trials`
278+
- `lastOutcome`
279+
- `event.selectedOutcomes`
280+
- `event.pEstimated`
281+
- `event.pTheoretical`
282+
- `barChart.rows` when the bar chart section is enabled
283+
- `convergence` when the convergence section is enabled
284+
- `frequencyTable.rows` when the frequency table section is enabled
285+
286+
Two-mode `status` events can include:
287+
288+
- `trials`
289+
- `lastOutcome.a` and `lastOutcome.b`
290+
- `relationship`
291+
- `jointDistribution.labelsA`, `jointDistribution.labelsB`, `jointDistribution.matrixRel`
292+
- `twoWayTable.labelsA`, `twoWayTable.labelsB`, `twoWayTable.jointCounts`
293+
294+
### Example log lines
295+
296+
Single-mode `status`:
297+
298+
```json
299+
{"type":"status","data":{"mode":"single","trials":100,"lastOutcome":"Heads","event":{"selectedOutcomes":["Heads"],"pEstimated":0.55,"pTheoretical":0.5},"barChart":{"rows":[{"outcome":"Heads","count":55,"relFreq":0.55},{"outcome":"Tails","count":45,"relFreq":0.45}]}}}
300+
```
301+
302+
Two-mode `status`:
303+
304+
```json
305+
{"type":"status","data":{"mode":"two","trials":200,"lastOutcome":{"a":"Sunny","b":"Heavy"},"relationship":"independent","jointDistribution":{"labelsA":["Sunny","Rainy"],"labelsB":["Light","Heavy"],"matrixRel":[[0.4,0.2],[0.3,0.1]]},"twoWayTable":{"labelsA":["Sunny","Rainy"],"labelsB":["Light","Heavy"],"jointCounts":[[80,40],[60,20]]}}}
306+
```
307+
308+
`settings_change`:
309+
310+
```json
311+
{"type":"settings_change","data":{"changed":["bias"],"settings":{"mode":"single","device":"coin","sections":{"barChart":true,"convergence":true,"frequencyTable":true,"jointDistribution":false,"twoWayTable":false},"spinnerSectors":8,"bias":{"coinProbabilities":[1,0],"dieProbabilities":[0.167,0.167,0.167,0.167,0.167,0.167],"spinnerSkew":0},"eventSelected":["Heads"]}}}
312+
```
313+
314+
`run_reset`:
315+
316+
```json
317+
{"type":"run_reset","data":{"reason":"bias_change","settings":{"mode":"single","device":"coin","sections":{"barChart":true,"convergence":true,"frequencyTable":true,"jointDistribution":false,"twoWayTable":false},"spinnerSectors":8,"bias":{"coinProbabilities":[0,1],"dieProbabilities":[0.167,0.167,0.167,0.167,0.167,0.167],"spinnerSkew":0},"eventSelected":["Heads"]}}}
318+
```
319+
320+
`click`:
321+
322+
```json
323+
{"type":"click","data":{"source":"jointDistributionHeatmap","cell":{"r":0,"c":1},"labels":{"a":"Sunny","b":"Heavy"}}}
324+
```
325+
326+
## CI/CD and Automated Releases
327+
328+
This repository has a GitHub Actions workflow at [`.github/workflows/build-release.yml`](/Users/diego/repos/bespoke-sims/learn_probability-lab/.github/workflows/build-release.yml).
329+
330+
Current behavior:
331+
332+
- Every push to `main` triggers the workflow
333+
- The workflow checks out the repo and initializes the design-system submodule
334+
- It installs dependencies with `npm ci`
335+
- It builds the app with `npm run build`
336+
- It installs production dependencies only
337+
- It creates a `release.tar.gz` archive containing the built app, `server.js`, `package.json`, and production `node_modules`
338+
- It uploads the build artifact in GitHub Actions
339+
- It publishes a GitHub Release automatically
27340

28-
- `client/index.html` – app shell + layout
29-
- `client/app.js` – simulation engine + rendering
30-
- `client/app.css` – app-specific styling
31-
- `client/help-content.html` – Help modal content

0 commit comments

Comments
 (0)