oracular 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/README.md +14 -6
- package/dist/cli.js +695 -88
- package/dist/index.cjs +16 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +60 -1
- package/dist/index.d.ts +60 -1
- package/dist/index.js +15 -1
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
- package/schema/config.schema.json +141 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#24](https://github.com/Yuribenjamin/oracular/pull/24) [`7809c6a`](https://github.com/Yuribenjamin/oracular/commit/7809c6af5c26ec836e43b76eac647bcf85c42514) Thanks [@Yuribenjamin](https://github.com/Yuribenjamin)! - **`npx oracular tune <log>... --config <file>`** seeds a config from a shadow log. Grouped by install, it reads what the engine itself counts, hits, misses and episode totals, off the state after every record, so it cannot disagree with what a device would learn from the same events. The base rate becomes the prior; the spread of per-install base rates becomes the prior strength (method of moments on a Beta, once the spread that sampling alone would produce is taken out; kept as it was below 10 installs); each declared signal's weight is `priorStrength × lift` over the episodes without it, with a 90% Wilson interval and the sample sizes on both sides; and the half-life that predicts the log best (lowest Brier score under `replay`) wins a grid search over 1h to 30d plus the current value. It prints the fit and the config to paste, which `replay --config` takes as it is.
|
|
8
|
+
|
|
9
|
+
Like replay, it counts shadow periods only: an episode in which any record was live is left out, a mixed log gets a warning, a live-only log is refused unless `--include-live` is passed. `--all-events` also measures every other event name in the log as a candidate signal; `--half-lives 12h,1d,3d` sets the grid; `--window` sets the scoring window when the intent has no cooldown. Every number is a starting point: the engine corrects the weights per user.
|
|
10
|
+
|
|
11
|
+
`oracular --help` now lists both commands; `oracular replay --help` and `oracular tune --help` have the options.
|
|
12
|
+
|
|
13
|
+
## 0.4.0
|
|
14
|
+
|
|
15
|
+
### Minor Changes
|
|
16
|
+
|
|
17
|
+
- [#22](https://github.com/Yuribenjamin/oracular/pull/22) [`0bb5841`](https://github.com/Yuribenjamin/oracular/commit/0bb5841fb93d2f16dcac74601f1ee218b70dda0e) Thanks [@Yuribenjamin](https://github.com/Yuribenjamin)! - Three small ergonomics for writing configs.
|
|
18
|
+
|
|
19
|
+
- **`validateConfig(config)`** runs the checks `createState` and `deserialize` run, without building a state, and returns the normalized copy they would store. It accepts `unknown`, so parsed JSON can be passed as it is; use it to fail fast at startup or in a test.
|
|
20
|
+
- **`ms`**: `ms.seconds(n)`, `ms.minutes(n)`, `ms.hours(n)`, `ms.days(n)` and `ms.weeks(n)` spell durations in milliseconds, so `halfLife: ms.days(7)` replaces `7 * 24 * 60 * 60 * 1000`. Plain multiplications, nothing else.
|
|
21
|
+
- **A JSON Schema for the config**, shipped in the package as `oracular/config.schema.json` and served at https://yuribenjamin.github.io/oracular/config.schema.json, for completion and checks on a config kept as JSON. `oracular replay --config file.json` now ignores a top-level `$schema` key, so a file can point at the schema and still be the config the engine runs.
|
|
22
|
+
|
|
3
23
|
## 0.3.0
|
|
4
24
|
|
|
5
25
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -73,7 +73,7 @@ That block is copied verbatim into the test suite, so it always runs.
|
|
|
73
73
|
- [API](#api)
|
|
74
74
|
- [Persistence](#persistence)
|
|
75
75
|
- [Explainability](#explainability)
|
|
76
|
-
- [Shadow mode
|
|
76
|
+
- [Shadow mode, replay and tune](#shadow-mode-replay-and-tune)
|
|
77
77
|
- [Design rules](#design-rules)
|
|
78
78
|
- [Roadmap](#roadmap)
|
|
79
79
|
- [Contributing](#contributing)
|
|
@@ -250,6 +250,8 @@ createRecord(state, event, { installId, mode }): EventRecord // one recorder l
|
|
|
250
250
|
createEndRecord(at, { installId, mode }): EndRecord // one recorder line: the episode ended
|
|
251
251
|
serialize(state): string
|
|
252
252
|
deserialize(json: string, opts?: { config?: EngineConfig; migrate?: Migrate }): EngineState
|
|
253
|
+
validateConfig(config: unknown): EngineConfig // the checks createState runs, without a state; returns the normalized copy
|
|
254
|
+
ms.seconds(n), ms.minutes(n), ms.hours(n), ms.days(n), ms.weeks(n) // durations in milliseconds
|
|
253
255
|
STATE_VERSION // 2
|
|
254
256
|
RECORD_VERSION // 1
|
|
255
257
|
```
|
|
@@ -286,7 +288,7 @@ interface RevealOptions {
|
|
|
286
288
|
}
|
|
287
289
|
```
|
|
288
290
|
|
|
289
|
-
Reveal options resolve per call → intent → engine → defaults. Invalid config throws a `TypeError` or `RangeError` with the offending path in the message.
|
|
291
|
+
Reveal options resolve per call → intent → engine → defaults. Invalid config throws a `TypeError` or `RangeError` with the offending path in the message. `validateConfig(config)` runs those checks without building a state and returns the normalized copy `createState` stores, so a bad weight fails at startup or in a test rather than on first use. For a config kept as JSON, the package ships a [JSON Schema](./schema/config.schema.json) as `oracular/config.schema.json`, also served at <https://yuribenjamin.github.io/oracular/config.schema.json>, for editor completion; `oracular replay` ignores the `$schema` key such a file carries, the engine does not.
|
|
290
292
|
|
|
291
293
|
### Results
|
|
292
294
|
|
|
@@ -317,7 +319,7 @@ interface SignalExplanation {
|
|
|
317
319
|
|
|
318
320
|
### Time
|
|
319
321
|
|
|
320
|
-
`at` is a timestamp. Every duration (`halfLife`, `cooldown`, `episodeTimeout`, `learning.halfLife`) is in the same unit as `at`. Milliseconds from `Date.now()` are the obvious choice, but the library never calls `Date.now()` itself: you pass time in, which keeps every function deterministic and makes tests trivial. A clock that jumps backwards is tolerated (no decay, no error).
|
|
322
|
+
`at` is a timestamp. Every duration (`halfLife`, `cooldown`, `episodeTimeout`, `learning.halfLife`) is in the same unit as `at`. Milliseconds from `Date.now()` are the obvious choice, but the library never calls `Date.now()` itself: you pass time in, which keeps every function deterministic and makes tests trivial. A clock that jumps backwards is tolerated (no decay, no error). When `at` is in milliseconds, `ms.days(7)`, `ms.hours(3)` and `ms.minutes(30)` spell the durations; they are plain multiplications.
|
|
321
323
|
|
|
322
324
|
## Persistence
|
|
323
325
|
|
|
@@ -361,7 +363,7 @@ explain(state, 'upgrade', Date.now());
|
|
|
361
363
|
|
|
362
364
|
Rows are sorted by the size of their impact. `limit.hit` has no history yet, so its effective weight is exactly the initial one. `pricing.viewed` was followed by an upgrade in six of its seven sessions while the three sessions without it saw one upgrade, so it learned a weight above the initial 3. `export.clicked` appeared in every session: it has no counterfactual, so whatever its hit rate, its weight stands at exactly the initial 1 until a session happens without it. See [Episodes & learning](https://yuribenjamin.github.io/oracular/guide/concepts/episodes-and-learning) for why learning needs contrast.
|
|
363
365
|
|
|
364
|
-
## Shadow mode
|
|
366
|
+
## Shadow mode, replay and tune
|
|
365
367
|
|
|
366
368
|
Run oracular silently first. Record what it decides, export the log, and measure a config offline before any reveal is shown:
|
|
367
369
|
|
|
@@ -374,7 +376,13 @@ log.append(JSON.stringify(createRecord(state, event, { installId, mode: 'shadow'
|
|
|
374
376
|
npx oracular replay shadow.ndjson --config oracular.config.json
|
|
375
377
|
```
|
|
376
378
|
|
|
377
|
-
`replay` reports precision, recall, lead time and reveals per user for every intent and threshold, and a reliability table with a Brier score. It scores only the periods in which reveals were not shown, because once they are, the reveal itself causes outcomes. `installId` is a random id your app generates once and stores; it is never derived from the device. The log holds event names and times, never `props`, and leaves the device only if your app sends it.
|
|
379
|
+
`replay` reports precision, recall, lead time and reveals per user for every intent and threshold, and a reliability table with a Brier score. It scores only the periods in which reveals were not shown, because once they are, the reveal itself causes outcomes. `installId` is a random id your app generates once and stores; it is never derived from the device. The log holds event names and times, never `props`, and leaves the device only if your app sends it.
|
|
380
|
+
|
|
381
|
+
```sh
|
|
382
|
+
npx oracular tune shadow.ndjson --config oracular.config.json
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
`tune` seeds a config from the same log: grouped by install, the base rate becomes the prior, the spread of per-install base rates the prior strength, each signal's lift over the episodes without it its weight, and the half-life that predicts the log best wins a grid search scored by `replay`. The counts come from the engine itself, so tune cannot disagree with what a device would learn. It prints sample sizes and a 90% interval per weight, and the config to paste; the engine corrects it per user from there. The [Shadow mode guide](https://yuribenjamin.github.io/oracular/guide/shadow-mode) has the workflow, the record format and both commands.
|
|
378
386
|
|
|
379
387
|
## Design rules
|
|
380
388
|
|
|
@@ -385,7 +393,7 @@ npx oracular replay shadow.ndjson --config oracular.config.json
|
|
|
385
393
|
|
|
386
394
|
## Roadmap
|
|
387
395
|
|
|
388
|
-
|
|
396
|
+
Now is 0.4, themed **learning that matches how people behave**: cross-episode attribution, so a session's signals can earn credit for an outcome days later, and presets. Shipped so far: `npx oracular tune` to seed a config from a shadow log, and small ergonomics (`validateConfig`, `ms`, a JSON Schema for the config). 0.3 shipped shadow mode, `npx oracular replay` and per-episode saturation. Then a written stability policy before 1.0. Framework bindings wait for a third integration. The full list, with the reasoning and what was declined, is on the [roadmap page](https://yuribenjamin.github.io/oracular/guide/roadmap).
|
|
389
397
|
|
|
390
398
|
## Contributing
|
|
391
399
|
|