oracular 0.6.0 → 0.7.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/dist/index.d.cts CHANGED
@@ -2,8 +2,10 @@
2
2
  * Version of the serialized state shape, stored in every state as `state.version`. Bumped only
3
3
  * when `EngineState` changes incompatibly, and every bump ships its migration: {@link deserialize}
4
4
  * upgrades states saved by earlier versions of this library on its own (version 1, written by
5
- * oracular 0.1.0 to 0.2.x, and version 2, written by 0.3.0 to 0.5.x, become version 3) and calls
6
- * your `migrate` function only for a version it does not know.
5
+ * oracular 0.1.0 to 0.2.x, and version 2, written by 0.3.0 to 0.5.x, become version 3), reads a
6
+ * state one version newer than its own as its own (a version bump only adds fields, so a rollback
7
+ * by one release keeps the user's history), and calls your `migrate` function only for a version
8
+ * it does not know.
7
9
  *
8
10
  * @example
9
11
  * ```ts
@@ -337,7 +339,8 @@ interface Evaluation {
337
339
  /**
338
340
  * Turns a stored state of a version this library cannot migrate itself into a current
339
341
  * `EngineState`. Receives the parsed JSON as-is and the version it declares. States saved by
340
- * oracular 0.1.0 to 0.5.x (versions 1 and 2) never reach it: `deserialize` upgrades those on its own.
342
+ * oracular 0.1.0 to 0.5.x (versions 1 and 2) never reach it, nor does a state one version newer
343
+ * than `STATE_VERSION`: `deserialize` handles both on its own.
341
344
  *
342
345
  * @example
343
346
  * ```ts
@@ -360,8 +363,9 @@ interface DeserializeOptions {
360
363
  /** The app's current config. Validated, swapped in, and used to prune signals and intents no longer declared. */
361
364
  config?: EngineConfig;
362
365
  /**
363
- * Called when the stored version is neither `STATE_VERSION` nor one this library migrates
364
- * itself (states from oracular 0.1.0 to 0.5.x are upgraded without it).
366
+ * Called when the stored version is neither `STATE_VERSION`, nor one this library migrates
367
+ * itself (states from oracular 0.1.0 to 0.5.x are upgraded without it), nor the next version,
368
+ * which it reads as its own.
365
369
  */
366
370
  migrate?: Migrate;
367
371
  }
@@ -456,6 +460,51 @@ interface EndRecord {
456
460
  }
457
461
  /** A line of a recorder log. */
458
462
  type RecorderRecord = EventRecord | EndRecord;
463
+ /** One intent in a {@link Snapshot}: its name and what {@link evaluate} says about it. */
464
+ interface IntentSnapshot extends Evaluation {
465
+ intent: string;
466
+ }
467
+ /**
468
+ * Every intent's prediction, gate and explanation at one moment, as {@link inspect} returns it:
469
+ * what a debug panel shows, what a test asserts on, what `console.table` prints.
470
+ *
471
+ * @example
472
+ * ```ts
473
+ * const { intents } = inspect(state, Date.now());
474
+ * console.table(intents.map((i) => ({ intent: i.intent, p: i.prediction.p, reveal: i.reveal })));
475
+ * ```
476
+ */
477
+ interface Snapshot {
478
+ at: number;
479
+ /** In config order. */
480
+ intents: IntentSnapshot[];
481
+ }
482
+ /**
483
+ * The intents `oracular/presets` ships. Each is a plain {@link IntentConfig} to spread into your
484
+ * config and override; `presetSources` there says where its numbers come from.
485
+ */
486
+ type PresetName = 'willBuy' | 'upgrade' | 'stuck' | 'churn';
487
+ /**
488
+ * Where a preset's numbers come from, as `presetSources` in `oracular/presets` reports it. A tuned
489
+ * preset was fitted by `oracular tune` on a real log and carries the sample behind it; a
490
+ * hand-written one is a careful guess nobody has measured. Either is a starting point: run in
491
+ * shadow mode and let `tune` replace it with your own numbers.
492
+ *
493
+ * @example
494
+ * ```ts
495
+ * import { presetSources } from 'oracular/presets';
496
+ * presetSources.willBuy.kind; // 'tuned'
497
+ * ```
498
+ */
499
+ interface PresetSource {
500
+ kind: 'tuned' | 'hand-written';
501
+ /** What the numbers were fitted on, or where the guess comes from. */
502
+ note: string;
503
+ /** Counted episodes behind a tuned preset; null for a hand-written one. */
504
+ episodes: number | null;
505
+ /** Installs those episodes came from; null for a hand-written one. */
506
+ installs: number | null;
507
+ }
459
508
 
460
509
  /**
461
510
  * {@link predict}, {@link shouldReveal} and {@link explain} in one call, from a single decay
@@ -480,6 +529,25 @@ type RecorderRecord = EventRecord | EndRecord;
480
529
  * ```
481
530
  */
482
531
  declare function evaluate(state: EngineState, intent: string, at: number, opts?: EvaluateOptions): Evaluation;
532
+ /**
533
+ * {@link evaluate} for every declared intent at once, in config order: the whole engine's view at
534
+ * one moment, as plain data. This is what to log, assert on, or hand to `renderInspector` from
535
+ * `oracular/inspect` for a live panel. Read-only.
536
+ *
537
+ * @param state - The current state. Not mutated.
538
+ * @param at - The time to evaluate at.
539
+ * @param opts - Reveal overrides and explain filters, applied to every intent.
540
+ * @returns `at` and one {@link IntentSnapshot} per intent.
541
+ * @throws `TypeError` when `state` is not an engine state, `at` is not finite, or `opts` is
542
+ * malformed.
543
+ * @example
544
+ * ```ts
545
+ * for (const { intent, prediction, reveal } of inspect(state, Date.now()).intents) {
546
+ * console.log(intent, prediction.p.toFixed(3), reveal ? 'reveal' : '');
547
+ * }
548
+ * ```
549
+ */
550
+ declare function inspect(state: EngineState, at: number, opts?: EvaluateOptions): Snapshot;
483
551
 
484
552
  /**
485
553
  * How much positive evidence an intent needs before {@link shouldReveal} first becomes true:
@@ -737,8 +805,10 @@ declare function serialize(state: EngineState): string;
737
805
  * Always pass the app's current `config`: it is validated and replaces the stored snapshot,
738
806
  * intents and signals no longer declared are dropped (α and β are recomputed from the kept
739
807
  * contributions), and newly declared intents start at their prior. States saved by oracular
740
- * 0.1.0 to 0.5.x are upgraded to the current shape on their own; `migrate` is only for a version
741
- * this library does not know.
808
+ * 0.1.0 to 0.5.x are upgraded to the current shape on their own, and a state one version newer
809
+ * than {@link STATE_VERSION} (written by the next release, read after a rollback) is read as the
810
+ * current one, ignoring the fields it adds; `migrate` is only for a version this library does
811
+ * not know.
742
812
  *
743
813
  * @param json - A string produced by {@link serialize}.
744
814
  * @param opts - `config`: the app's current config. `migrate`: called as
@@ -748,8 +818,9 @@ declare function serialize(state: EngineState): string;
748
818
  * @throws `SyntaxError` when `json` is not valid JSON.
749
819
  * @throws `TypeError` when the stored data, the result of `migrate`, `opts` or `opts.config`
750
820
  * has the wrong shape.
751
- * @throws `RangeError` when the stored version is unknown and no `migrate` is given, when
752
- * `migrate` does not return the current version, when a stored number is out of range, or
821
+ * @throws `RangeError` when the stored version is neither one this library reads (1 to one past
822
+ * {@link STATE_VERSION}) nor handled by a given `migrate`, when `migrate` does not return the
823
+ * current version, when a stored number is out of range, or
753
824
  * when stored numbers are finite but too large to compute with (a tampered or corrupted state
754
825
  * that would otherwise answer NaN).
755
826
  * @example
@@ -849,4 +920,4 @@ declare const ms: Units;
849
920
  */
850
921
  declare function validateConfig(config: unknown): EngineConfig;
851
922
 
852
- export { type DeserializeOptions, type EndRecord, type EngineConfig, type EngineState, type EpisodeIntentRecord, type EpisodeState, type EvaluateOptions, type Evaluation, type EventRecord, type ExplainOptions, type IntentConfig, type IntentEvent, type IntentState, type LearningConfig, type Migrate, type PendingEpisode, type Prediction, RECORD_VERSION, type RecordMode, type RecordOptions, type RecordedDecision, type RecorderRecord, type RevealOptions, STATE_VERSION, type SignalContribution, type SignalExplanation, type SignalRecord, type Units, createEndRecord, createRecord, createState, deserialize, endEpisode, episodeClosed, evaluate, evidenceToReveal, explain, markRevealed, ms, observe, predict, serialize, shouldReveal, validateConfig };
923
+ export { type DeserializeOptions, type EndRecord, type EngineConfig, type EngineState, type EpisodeIntentRecord, type EpisodeState, type EvaluateOptions, type Evaluation, type EventRecord, type ExplainOptions, type IntentConfig, type IntentEvent, type IntentSnapshot, type IntentState, type LearningConfig, type Migrate, type PendingEpisode, type Prediction, type PresetName, type PresetSource, RECORD_VERSION, type RecordMode, type RecordOptions, type RecordedDecision, type RecorderRecord, type RevealOptions, STATE_VERSION, type SignalContribution, type SignalExplanation, type SignalRecord, type Snapshot, type Units, createEndRecord, createRecord, createState, deserialize, endEpisode, episodeClosed, evaluate, evidenceToReveal, explain, inspect, markRevealed, ms, observe, predict, serialize, shouldReveal, validateConfig };
package/dist/index.d.ts CHANGED
@@ -2,8 +2,10 @@
2
2
  * Version of the serialized state shape, stored in every state as `state.version`. Bumped only
3
3
  * when `EngineState` changes incompatibly, and every bump ships its migration: {@link deserialize}
4
4
  * upgrades states saved by earlier versions of this library on its own (version 1, written by
5
- * oracular 0.1.0 to 0.2.x, and version 2, written by 0.3.0 to 0.5.x, become version 3) and calls
6
- * your `migrate` function only for a version it does not know.
5
+ * oracular 0.1.0 to 0.2.x, and version 2, written by 0.3.0 to 0.5.x, become version 3), reads a
6
+ * state one version newer than its own as its own (a version bump only adds fields, so a rollback
7
+ * by one release keeps the user's history), and calls your `migrate` function only for a version
8
+ * it does not know.
7
9
  *
8
10
  * @example
9
11
  * ```ts
@@ -337,7 +339,8 @@ interface Evaluation {
337
339
  /**
338
340
  * Turns a stored state of a version this library cannot migrate itself into a current
339
341
  * `EngineState`. Receives the parsed JSON as-is and the version it declares. States saved by
340
- * oracular 0.1.0 to 0.5.x (versions 1 and 2) never reach it: `deserialize` upgrades those on its own.
342
+ * oracular 0.1.0 to 0.5.x (versions 1 and 2) never reach it, nor does a state one version newer
343
+ * than `STATE_VERSION`: `deserialize` handles both on its own.
341
344
  *
342
345
  * @example
343
346
  * ```ts
@@ -360,8 +363,9 @@ interface DeserializeOptions {
360
363
  /** The app's current config. Validated, swapped in, and used to prune signals and intents no longer declared. */
361
364
  config?: EngineConfig;
362
365
  /**
363
- * Called when the stored version is neither `STATE_VERSION` nor one this library migrates
364
- * itself (states from oracular 0.1.0 to 0.5.x are upgraded without it).
366
+ * Called when the stored version is neither `STATE_VERSION`, nor one this library migrates
367
+ * itself (states from oracular 0.1.0 to 0.5.x are upgraded without it), nor the next version,
368
+ * which it reads as its own.
365
369
  */
366
370
  migrate?: Migrate;
367
371
  }
@@ -456,6 +460,51 @@ interface EndRecord {
456
460
  }
457
461
  /** A line of a recorder log. */
458
462
  type RecorderRecord = EventRecord | EndRecord;
463
+ /** One intent in a {@link Snapshot}: its name and what {@link evaluate} says about it. */
464
+ interface IntentSnapshot extends Evaluation {
465
+ intent: string;
466
+ }
467
+ /**
468
+ * Every intent's prediction, gate and explanation at one moment, as {@link inspect} returns it:
469
+ * what a debug panel shows, what a test asserts on, what `console.table` prints.
470
+ *
471
+ * @example
472
+ * ```ts
473
+ * const { intents } = inspect(state, Date.now());
474
+ * console.table(intents.map((i) => ({ intent: i.intent, p: i.prediction.p, reveal: i.reveal })));
475
+ * ```
476
+ */
477
+ interface Snapshot {
478
+ at: number;
479
+ /** In config order. */
480
+ intents: IntentSnapshot[];
481
+ }
482
+ /**
483
+ * The intents `oracular/presets` ships. Each is a plain {@link IntentConfig} to spread into your
484
+ * config and override; `presetSources` there says where its numbers come from.
485
+ */
486
+ type PresetName = 'willBuy' | 'upgrade' | 'stuck' | 'churn';
487
+ /**
488
+ * Where a preset's numbers come from, as `presetSources` in `oracular/presets` reports it. A tuned
489
+ * preset was fitted by `oracular tune` on a real log and carries the sample behind it; a
490
+ * hand-written one is a careful guess nobody has measured. Either is a starting point: run in
491
+ * shadow mode and let `tune` replace it with your own numbers.
492
+ *
493
+ * @example
494
+ * ```ts
495
+ * import { presetSources } from 'oracular/presets';
496
+ * presetSources.willBuy.kind; // 'tuned'
497
+ * ```
498
+ */
499
+ interface PresetSource {
500
+ kind: 'tuned' | 'hand-written';
501
+ /** What the numbers were fitted on, or where the guess comes from. */
502
+ note: string;
503
+ /** Counted episodes behind a tuned preset; null for a hand-written one. */
504
+ episodes: number | null;
505
+ /** Installs those episodes came from; null for a hand-written one. */
506
+ installs: number | null;
507
+ }
459
508
 
460
509
  /**
461
510
  * {@link predict}, {@link shouldReveal} and {@link explain} in one call, from a single decay
@@ -480,6 +529,25 @@ type RecorderRecord = EventRecord | EndRecord;
480
529
  * ```
481
530
  */
482
531
  declare function evaluate(state: EngineState, intent: string, at: number, opts?: EvaluateOptions): Evaluation;
532
+ /**
533
+ * {@link evaluate} for every declared intent at once, in config order: the whole engine's view at
534
+ * one moment, as plain data. This is what to log, assert on, or hand to `renderInspector` from
535
+ * `oracular/inspect` for a live panel. Read-only.
536
+ *
537
+ * @param state - The current state. Not mutated.
538
+ * @param at - The time to evaluate at.
539
+ * @param opts - Reveal overrides and explain filters, applied to every intent.
540
+ * @returns `at` and one {@link IntentSnapshot} per intent.
541
+ * @throws `TypeError` when `state` is not an engine state, `at` is not finite, or `opts` is
542
+ * malformed.
543
+ * @example
544
+ * ```ts
545
+ * for (const { intent, prediction, reveal } of inspect(state, Date.now()).intents) {
546
+ * console.log(intent, prediction.p.toFixed(3), reveal ? 'reveal' : '');
547
+ * }
548
+ * ```
549
+ */
550
+ declare function inspect(state: EngineState, at: number, opts?: EvaluateOptions): Snapshot;
483
551
 
484
552
  /**
485
553
  * How much positive evidence an intent needs before {@link shouldReveal} first becomes true:
@@ -737,8 +805,10 @@ declare function serialize(state: EngineState): string;
737
805
  * Always pass the app's current `config`: it is validated and replaces the stored snapshot,
738
806
  * intents and signals no longer declared are dropped (α and β are recomputed from the kept
739
807
  * contributions), and newly declared intents start at their prior. States saved by oracular
740
- * 0.1.0 to 0.5.x are upgraded to the current shape on their own; `migrate` is only for a version
741
- * this library does not know.
808
+ * 0.1.0 to 0.5.x are upgraded to the current shape on their own, and a state one version newer
809
+ * than {@link STATE_VERSION} (written by the next release, read after a rollback) is read as the
810
+ * current one, ignoring the fields it adds; `migrate` is only for a version this library does
811
+ * not know.
742
812
  *
743
813
  * @param json - A string produced by {@link serialize}.
744
814
  * @param opts - `config`: the app's current config. `migrate`: called as
@@ -748,8 +818,9 @@ declare function serialize(state: EngineState): string;
748
818
  * @throws `SyntaxError` when `json` is not valid JSON.
749
819
  * @throws `TypeError` when the stored data, the result of `migrate`, `opts` or `opts.config`
750
820
  * has the wrong shape.
751
- * @throws `RangeError` when the stored version is unknown and no `migrate` is given, when
752
- * `migrate` does not return the current version, when a stored number is out of range, or
821
+ * @throws `RangeError` when the stored version is neither one this library reads (1 to one past
822
+ * {@link STATE_VERSION}) nor handled by a given `migrate`, when `migrate` does not return the
823
+ * current version, when a stored number is out of range, or
753
824
  * when stored numbers are finite but too large to compute with (a tampered or corrupted state
754
825
  * that would otherwise answer NaN).
755
826
  * @example
@@ -849,4 +920,4 @@ declare const ms: Units;
849
920
  */
850
921
  declare function validateConfig(config: unknown): EngineConfig;
851
922
 
852
- export { type DeserializeOptions, type EndRecord, type EngineConfig, type EngineState, type EpisodeIntentRecord, type EpisodeState, type EvaluateOptions, type Evaluation, type EventRecord, type ExplainOptions, type IntentConfig, type IntentEvent, type IntentState, type LearningConfig, type Migrate, type PendingEpisode, type Prediction, RECORD_VERSION, type RecordMode, type RecordOptions, type RecordedDecision, type RecorderRecord, type RevealOptions, STATE_VERSION, type SignalContribution, type SignalExplanation, type SignalRecord, type Units, createEndRecord, createRecord, createState, deserialize, endEpisode, episodeClosed, evaluate, evidenceToReveal, explain, markRevealed, ms, observe, predict, serialize, shouldReveal, validateConfig };
923
+ export { type DeserializeOptions, type EndRecord, type EngineConfig, type EngineState, type EpisodeIntentRecord, type EpisodeState, type EvaluateOptions, type Evaluation, type EventRecord, type ExplainOptions, type IntentConfig, type IntentEvent, type IntentSnapshot, type IntentState, type LearningConfig, type Migrate, type PendingEpisode, type Prediction, type PresetName, type PresetSource, RECORD_VERSION, type RecordMode, type RecordOptions, type RecordedDecision, type RecorderRecord, type RevealOptions, STATE_VERSION, type SignalContribution, type SignalExplanation, type SignalRecord, type Snapshot, type Units, createEndRecord, createRecord, createState, deserialize, endEpisode, episodeClosed, evaluate, evidenceToReveal, explain, inspect, markRevealed, ms, observe, predict, serialize, shouldReveal, validateConfig };
package/dist/index.js CHANGED
@@ -555,6 +555,14 @@ function evaluate(state, intent, at, opts) {
555
555
  explanation: buildExplanation(is, config, learning, split.explain)
556
556
  };
557
557
  }
558
+ function inspect(state, at, opts) {
559
+ const s = requireState(state);
560
+ const intents = Object.keys(s.config.intents).map((intent) => ({
561
+ intent,
562
+ ...evaluate(s, intent, at, opts)
563
+ }));
564
+ return { at, intents };
565
+ }
558
566
 
559
567
  // src/evidence.ts
560
568
  function evidenceToReveal(intent, opts) {
@@ -825,9 +833,11 @@ function deserialize(json, opts = {}) {
825
833
  if (parsed.version !== STATE_VERSION) {
826
834
  if (canMigrate(parsed.version)) {
827
835
  stored = migrateStored(parsed);
836
+ } else if (parsed.version === STATE_VERSION + 1) {
837
+ stored = { ...parsed, version: STATE_VERSION };
828
838
  } else if (opts.migrate === void 0) {
829
839
  throw new RangeError(
830
- `oracular: stored state is version ${parsed.version} but this library reads versions 1 to ${STATE_VERSION}; pass a migrate function`
840
+ `oracular: stored state is version ${parsed.version} but this library reads versions 1 to ${STATE_VERSION + 1}; pass a migrate function`
831
841
  );
832
842
  } else {
833
843
  stored = opts.migrate(parsed, parsed.version);
@@ -1035,6 +1045,6 @@ function validateConfig(config) {
1035
1045
  return normalizeConfig(config);
1036
1046
  }
1037
1047
 
1038
- export { RECORD_VERSION, STATE_VERSION, createEndRecord, createRecord, createState, deserialize, endEpisode, episodeClosed, evaluate, evidenceToReveal, explain, markRevealed, ms, observe, predict, serialize, shouldReveal, validateConfig };
1048
+ export { RECORD_VERSION, STATE_VERSION, createEndRecord, createRecord, createState, deserialize, endEpisode, episodeClosed, evaluate, evidenceToReveal, explain, inspect, markRevealed, ms, observe, predict, serialize, shouldReveal, validateConfig };
1039
1049
  //# sourceMappingURL=index.js.map
1040
1050
  //# sourceMappingURL=index.js.map