oracular 0.12.1 → 0.14.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 +14 -0
- package/README.md +6 -5
- package/dist/index.cjs +14 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +28 -12
- package/dist/index.d.ts +28 -12
- package/dist/index.js +14 -10
- package/dist/index.js.map +1 -1
- package/dist/inspect.d.cts +2 -2
- package/dist/inspect.d.ts +2 -2
- package/dist/presets.d.cts +7 -0
- package/dist/presets.d.ts +7 -0
- package/package.json +8 -2
- package/schema/config.schema.json +6 -1
package/dist/index.d.cts
CHANGED
|
@@ -54,6 +54,13 @@ interface RevealOptions {
|
|
|
54
54
|
z?: number;
|
|
55
55
|
/** How long after `markRevealed` the intent stays revealed and `shouldReveal` is false. Default: the intent's `halfLife`. */
|
|
56
56
|
cooldown?: number;
|
|
57
|
+
/**
|
|
58
|
+
* How long after the intent's outcome event `shouldReveal` stays false. Default: `cooldown`.
|
|
59
|
+
* 0 is allowed: the outcome then pauses nothing, while a reveal still starts its cooldown, for
|
|
60
|
+
* an intent whose outcome recurs within minutes, or a "stuck" intent whose help should be back
|
|
61
|
+
* at the next attempt.
|
|
62
|
+
*/
|
|
63
|
+
outcomeCooldown?: number;
|
|
57
64
|
/**
|
|
58
65
|
* How `lower` is computed. `'normal'` (default): p − z·sd, clamped to [0, 1]. `'exact'`: the
|
|
59
66
|
* Beta distribution's own quantile at the same one-sided level, Φ(−z), so `z` keeps its
|
|
@@ -307,8 +314,8 @@ interface Prediction {
|
|
|
307
314
|
revealedAt: number | null;
|
|
308
315
|
/**
|
|
309
316
|
* When the current suppression ends: the later of the reveal cooldown (after `markRevealed`)
|
|
310
|
-
* and the outcome cooldown (after the outcome event) still running at `at`,
|
|
311
|
-
* neither is. `shouldReveal` is false while this is non-null.
|
|
317
|
+
* and the outcome cooldown (`outcomeCooldown` after the outcome event) still running at `at`,
|
|
318
|
+
* or null when neither is. `shouldReveal` is false while this is non-null.
|
|
312
319
|
*/
|
|
313
320
|
suppressedUntil: number | null;
|
|
314
321
|
}
|
|
@@ -395,7 +402,11 @@ type Migrate = (stored: unknown, version: number) => EngineState;
|
|
|
395
402
|
* ```
|
|
396
403
|
*/
|
|
397
404
|
interface DeserializeOptions {
|
|
398
|
-
/**
|
|
405
|
+
/**
|
|
406
|
+
* The app's current config. Validated, swapped in, and used to prune signals and intents no
|
|
407
|
+
* longer declared; the stored snapshot is then not read, so a key or value a newer release wrote
|
|
408
|
+
* into it does not stop the load.
|
|
409
|
+
*/
|
|
399
410
|
config?: EngineConfig;
|
|
400
411
|
/**
|
|
401
412
|
* Called when the stored version is neither `STATE_VERSION`, nor one this library migrates
|
|
@@ -726,9 +737,10 @@ declare function episodeClosed(prev: EngineState, next: EngineState): boolean;
|
|
|
726
737
|
* @param state - The current state. Not mutated.
|
|
727
738
|
* @param intent - Name of an intent declared in the config.
|
|
728
739
|
* @param at - The time to evaluate at, in the same unit as your durations.
|
|
729
|
-
* @param opts - Overrides for `threshold`, `z`, `cooldown` and `bound`,
|
|
730
|
-
* intent → engine → defaults. `z` and `bound` set `lower`; `cooldown` sets
|
|
731
|
-
* `
|
|
740
|
+
* @param opts - Overrides for `threshold`, `z`, `cooldown`, `outcomeCooldown` and `bound`,
|
|
741
|
+
* resolved as call → intent → engine → defaults. `z` and `bound` set `lower`; `cooldown` sets
|
|
742
|
+
* `revealed` and, with `outcomeCooldown` (default: `cooldown`), `suppressedUntil`;
|
|
743
|
+
* `threshold` only matters to {@link shouldReveal}.
|
|
732
744
|
* @returns The prediction. `p`, `sd`, `lower` and `confidence` are in [0, 1]; `evidence` is
|
|
733
745
|
* α + β.
|
|
734
746
|
* @throws `RangeError` when `intent` is not declared in the config.
|
|
@@ -751,7 +763,8 @@ declare function predict(state: EngineState, intent: string, at: number, opts?:
|
|
|
751
763
|
* @param intent - Name of an intent declared in the config.
|
|
752
764
|
* @param at - The time to evaluate at.
|
|
753
765
|
* @param opts - Overrides for `threshold` (default 0.5), `z` (default 1.28, a one-sided 90%
|
|
754
|
-
* bound)
|
|
766
|
+
* bound), `cooldown` (default: the intent's `halfLife`) and `outcomeCooldown` (default:
|
|
767
|
+
* `cooldown`; 0 lets the outcome pause nothing).
|
|
755
768
|
* @returns True when the app should show, offer or reveal whatever this intent gates.
|
|
756
769
|
* @throws `RangeError` when `intent` is not declared in the config.
|
|
757
770
|
* @throws `TypeError` when `state` is not an engine state, `at` is not finite, or `opts` is
|
|
@@ -850,11 +863,14 @@ declare function serialize(state: EngineState): string;
|
|
|
850
863
|
/**
|
|
851
864
|
* Restores a state saved with {@link serialize}.
|
|
852
865
|
*
|
|
853
|
-
* Always pass the app's current `config`: it is validated and replaces the stored snapshot
|
|
854
|
-
*
|
|
855
|
-
*
|
|
856
|
-
*
|
|
857
|
-
*
|
|
866
|
+
* Always pass the app's current `config`: it is validated and replaces the stored snapshot,
|
|
867
|
+
* which is then not read at all, so a state written by a newer release, with a config key or
|
|
868
|
+
* value this one does not know, still loads after a rollback. Intents and signals no longer
|
|
869
|
+
* declared are dropped (α and β are recomputed from the kept contributions), and newly declared
|
|
870
|
+
* intents start at their prior. Without a config the stored snapshot runs the engine: it is read
|
|
871
|
+
* without the checks that refuse a config that cannot work, so a state saved before those checks
|
|
872
|
+
* existed still loads, but a key the library does not know is an error, since the engine would
|
|
873
|
+
* otherwise run without an option the state was built under. States saved by oracular
|
|
858
874
|
* 0.1.0 to 0.11.x are upgraded to the current shape on their own, and a state one version newer
|
|
859
875
|
* than {@link STATE_VERSION} (written by the next release, read after a rollback) is read as the
|
|
860
876
|
* current one, ignoring the fields it adds; `migrate` is only for a version this library does
|
package/dist/index.d.ts
CHANGED
|
@@ -54,6 +54,13 @@ interface RevealOptions {
|
|
|
54
54
|
z?: number;
|
|
55
55
|
/** How long after `markRevealed` the intent stays revealed and `shouldReveal` is false. Default: the intent's `halfLife`. */
|
|
56
56
|
cooldown?: number;
|
|
57
|
+
/**
|
|
58
|
+
* How long after the intent's outcome event `shouldReveal` stays false. Default: `cooldown`.
|
|
59
|
+
* 0 is allowed: the outcome then pauses nothing, while a reveal still starts its cooldown, for
|
|
60
|
+
* an intent whose outcome recurs within minutes, or a "stuck" intent whose help should be back
|
|
61
|
+
* at the next attempt.
|
|
62
|
+
*/
|
|
63
|
+
outcomeCooldown?: number;
|
|
57
64
|
/**
|
|
58
65
|
* How `lower` is computed. `'normal'` (default): p − z·sd, clamped to [0, 1]. `'exact'`: the
|
|
59
66
|
* Beta distribution's own quantile at the same one-sided level, Φ(−z), so `z` keeps its
|
|
@@ -307,8 +314,8 @@ interface Prediction {
|
|
|
307
314
|
revealedAt: number | null;
|
|
308
315
|
/**
|
|
309
316
|
* When the current suppression ends: the later of the reveal cooldown (after `markRevealed`)
|
|
310
|
-
* and the outcome cooldown (after the outcome event) still running at `at`,
|
|
311
|
-
* neither is. `shouldReveal` is false while this is non-null.
|
|
317
|
+
* and the outcome cooldown (`outcomeCooldown` after the outcome event) still running at `at`,
|
|
318
|
+
* or null when neither is. `shouldReveal` is false while this is non-null.
|
|
312
319
|
*/
|
|
313
320
|
suppressedUntil: number | null;
|
|
314
321
|
}
|
|
@@ -395,7 +402,11 @@ type Migrate = (stored: unknown, version: number) => EngineState;
|
|
|
395
402
|
* ```
|
|
396
403
|
*/
|
|
397
404
|
interface DeserializeOptions {
|
|
398
|
-
/**
|
|
405
|
+
/**
|
|
406
|
+
* The app's current config. Validated, swapped in, and used to prune signals and intents no
|
|
407
|
+
* longer declared; the stored snapshot is then not read, so a key or value a newer release wrote
|
|
408
|
+
* into it does not stop the load.
|
|
409
|
+
*/
|
|
399
410
|
config?: EngineConfig;
|
|
400
411
|
/**
|
|
401
412
|
* Called when the stored version is neither `STATE_VERSION`, nor one this library migrates
|
|
@@ -726,9 +737,10 @@ declare function episodeClosed(prev: EngineState, next: EngineState): boolean;
|
|
|
726
737
|
* @param state - The current state. Not mutated.
|
|
727
738
|
* @param intent - Name of an intent declared in the config.
|
|
728
739
|
* @param at - The time to evaluate at, in the same unit as your durations.
|
|
729
|
-
* @param opts - Overrides for `threshold`, `z`, `cooldown` and `bound`,
|
|
730
|
-
* intent → engine → defaults. `z` and `bound` set `lower`; `cooldown` sets
|
|
731
|
-
* `
|
|
740
|
+
* @param opts - Overrides for `threshold`, `z`, `cooldown`, `outcomeCooldown` and `bound`,
|
|
741
|
+
* resolved as call → intent → engine → defaults. `z` and `bound` set `lower`; `cooldown` sets
|
|
742
|
+
* `revealed` and, with `outcomeCooldown` (default: `cooldown`), `suppressedUntil`;
|
|
743
|
+
* `threshold` only matters to {@link shouldReveal}.
|
|
732
744
|
* @returns The prediction. `p`, `sd`, `lower` and `confidence` are in [0, 1]; `evidence` is
|
|
733
745
|
* α + β.
|
|
734
746
|
* @throws `RangeError` when `intent` is not declared in the config.
|
|
@@ -751,7 +763,8 @@ declare function predict(state: EngineState, intent: string, at: number, opts?:
|
|
|
751
763
|
* @param intent - Name of an intent declared in the config.
|
|
752
764
|
* @param at - The time to evaluate at.
|
|
753
765
|
* @param opts - Overrides for `threshold` (default 0.5), `z` (default 1.28, a one-sided 90%
|
|
754
|
-
* bound)
|
|
766
|
+
* bound), `cooldown` (default: the intent's `halfLife`) and `outcomeCooldown` (default:
|
|
767
|
+
* `cooldown`; 0 lets the outcome pause nothing).
|
|
755
768
|
* @returns True when the app should show, offer or reveal whatever this intent gates.
|
|
756
769
|
* @throws `RangeError` when `intent` is not declared in the config.
|
|
757
770
|
* @throws `TypeError` when `state` is not an engine state, `at` is not finite, or `opts` is
|
|
@@ -850,11 +863,14 @@ declare function serialize(state: EngineState): string;
|
|
|
850
863
|
/**
|
|
851
864
|
* Restores a state saved with {@link serialize}.
|
|
852
865
|
*
|
|
853
|
-
* Always pass the app's current `config`: it is validated and replaces the stored snapshot
|
|
854
|
-
*
|
|
855
|
-
*
|
|
856
|
-
*
|
|
857
|
-
*
|
|
866
|
+
* Always pass the app's current `config`: it is validated and replaces the stored snapshot,
|
|
867
|
+
* which is then not read at all, so a state written by a newer release, with a config key or
|
|
868
|
+
* value this one does not know, still loads after a rollback. Intents and signals no longer
|
|
869
|
+
* declared are dropped (α and β are recomputed from the kept contributions), and newly declared
|
|
870
|
+
* intents start at their prior. Without a config the stored snapshot runs the engine: it is read
|
|
871
|
+
* without the checks that refuse a config that cannot work, so a state saved before those checks
|
|
872
|
+
* existed still loads, but a key the library does not know is an error, since the engine would
|
|
873
|
+
* otherwise run without an option the state was built under. States saved by oracular
|
|
858
874
|
* 0.1.0 to 0.11.x are upgraded to the current shape on their own, and a state one version newer
|
|
859
875
|
* than {@link STATE_VERSION} (written by the next release, read after a rollback) is read as the
|
|
860
876
|
* current one, ignoring the fields it adds; `migrate` is only for a version this library does
|
package/dist/index.js
CHANGED
|
@@ -287,7 +287,7 @@ var INTENT_KEYS = [
|
|
|
287
287
|
"attribution",
|
|
288
288
|
"reveal"
|
|
289
289
|
];
|
|
290
|
-
var REVEAL_KEYS = ["threshold", "z", "cooldown", "bound"];
|
|
290
|
+
var REVEAL_KEYS = ["threshold", "z", "cooldown", "outcomeCooldown", "bound"];
|
|
291
291
|
var LEARNING_KEYS = ["halfLife", "gain", "priorEpisodes", "lift"];
|
|
292
292
|
var LIFT_TYPES = [
|
|
293
293
|
"absolute-difference",
|
|
@@ -373,6 +373,9 @@ function normalizeRevealOptions(value, path, strict = true) {
|
|
|
373
373
|
if (record2.cooldown !== void 0) {
|
|
374
374
|
out.cooldown = expectNonNegative(record2.cooldown, `${path}.cooldown`);
|
|
375
375
|
}
|
|
376
|
+
if (record2.outcomeCooldown !== void 0) {
|
|
377
|
+
out.outcomeCooldown = expectNonNegative(record2.outcomeCooldown, `${path}.outcomeCooldown`);
|
|
378
|
+
}
|
|
376
379
|
if (record2.bound !== void 0) {
|
|
377
380
|
if (record2.bound !== "normal" && record2.bound !== "exact") {
|
|
378
381
|
typeError(`${path}.bound`, 'must be "normal" or "exact"');
|
|
@@ -511,10 +514,12 @@ function splitEvaluateOptions(value, path) {
|
|
|
511
514
|
}
|
|
512
515
|
function resolveReveal(config, intent, opts, normalized = false) {
|
|
513
516
|
const o = opts === void 0 ? {} : normalized ? opts : normalizeRevealOptions(opts, "options", false);
|
|
517
|
+
const cooldown = o.cooldown ?? intent.reveal?.cooldown ?? config?.reveal?.cooldown ?? intent.halfLife;
|
|
514
518
|
return {
|
|
515
519
|
threshold: o.threshold ?? intent.reveal?.threshold ?? config?.reveal?.threshold ?? DEFAULT_THRESHOLD,
|
|
516
520
|
z: o.z ?? intent.reveal?.z ?? config?.reveal?.z ?? DEFAULT_Z,
|
|
517
|
-
cooldown
|
|
521
|
+
cooldown,
|
|
522
|
+
outcomeCooldown: o.outcomeCooldown ?? intent.reveal?.outcomeCooldown ?? config?.reveal?.outcomeCooldown ?? cooldown,
|
|
518
523
|
bound: o.bound ?? intent.reveal?.bound ?? config?.reveal?.bound ?? "normal"
|
|
519
524
|
};
|
|
520
525
|
}
|
|
@@ -619,7 +624,7 @@ function buildPrediction(is, name, reveal, at) {
|
|
|
619
624
|
const p = betaMean(is.alpha, is.beta);
|
|
620
625
|
const sd = betaSd(is.alpha, is.beta);
|
|
621
626
|
const revealEnd = cooldownEnd(is.lastRevealedAt, reveal.cooldown, at);
|
|
622
|
-
const outcomeEnd = cooldownEnd(is.lastOutcomeAt, reveal.
|
|
627
|
+
const outcomeEnd = cooldownEnd(is.lastOutcomeAt, reveal.outcomeCooldown, at);
|
|
623
628
|
const suppressedUntil = revealEnd === null ? outcomeEnd : outcomeEnd === null ? revealEnd : Math.max(revealEnd, outcomeEnd);
|
|
624
629
|
return {
|
|
625
630
|
intent: name,
|
|
@@ -1003,9 +1008,9 @@ function deserialize(json, opts = {}) {
|
|
|
1003
1008
|
stored = opts.migrate(parsed, parsed.version);
|
|
1004
1009
|
}
|
|
1005
1010
|
}
|
|
1006
|
-
const
|
|
1007
|
-
const
|
|
1008
|
-
return requireComputable(reconcile(raw, config));
|
|
1011
|
+
const config = opts.config === void 0 ? void 0 : normalizeConfig(opts.config);
|
|
1012
|
+
const raw = parseState(stored, config);
|
|
1013
|
+
return requireComputable(reconcile(raw, raw.config));
|
|
1009
1014
|
}
|
|
1010
1015
|
function typeError2(path, message) {
|
|
1011
1016
|
throw new TypeError(`oracular: ${path} ${message}`);
|
|
@@ -1114,16 +1119,15 @@ function clockState(value, path) {
|
|
|
1114
1119
|
behindAt: nullOrFinite(r.behindAt, `${path}.behindAt`)
|
|
1115
1120
|
};
|
|
1116
1121
|
}
|
|
1117
|
-
function parseState(value) {
|
|
1122
|
+
function parseState(value, config) {
|
|
1118
1123
|
const r = record(value, "state");
|
|
1119
1124
|
if (r.version !== STATE_VERSION) {
|
|
1120
1125
|
throw new RangeError(`oracular: state.version must be ${STATE_VERSION} after migration`);
|
|
1121
1126
|
}
|
|
1127
|
+
const snapshot = record(r.config, "state.config");
|
|
1122
1128
|
return {
|
|
1123
1129
|
version: STATE_VERSION,
|
|
1124
|
-
|
|
1125
|
-
// when it is only stored, and the config passed in replaces it anyway.
|
|
1126
|
-
config: normalizeConfig(r.config, false),
|
|
1130
|
+
config: config ?? normalizeConfig(snapshot, false),
|
|
1127
1131
|
intents: mapOwn(r.intents, "state.intents", intentState),
|
|
1128
1132
|
episode: episodeState(r.episode, "state.episode"),
|
|
1129
1133
|
clock: clockState(r.clock, "state.clock")
|