oracular 0.14.0 → 0.15.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
@@ -138,6 +138,18 @@ interface IntentConfig {
138
138
  * weight.
139
139
  */
140
140
  saturate?: boolean;
141
+ /**
142
+ * Each outcome is an episode of its own for this intent, rather than the session. After the
143
+ * outcome arrives, the intent's bookkeeping starts over: a later outcome in the same session
144
+ * counts as another episode even with nothing seen since, and signals seen after an outcome
145
+ * take a miss at the session's close (or wait under `attribution`), as a first attempt would.
146
+ * Default false: the episode is the session, a repeated outcome with nothing seen since counts
147
+ * nothing, and signals seen after an outcome are neutral at the close. For intents shaped as
148
+ * attempts (an upload that fails, then completes, then completes again), where the second
149
+ * attempt is the contrast learning needs. With it set, a duplicated outcome event counts twice:
150
+ * dedupe such events in the app.
151
+ */
152
+ episodePerOutcome?: boolean;
141
153
  /**
142
154
  * How long a closed episode's signals wait for the outcome before they take a miss, in the same
143
155
  * unit as `at`. With it set, an episode that ends without the outcome is held instead of
@@ -657,7 +669,8 @@ declare function explain(state: EngineState, intent: string, at: number, opts?:
657
669
  * it either way; otherwise every occurrence adds all of `w`. For every intent the event is the
658
670
  * outcome of: give the signals remembered since the episode started or the outcome last arrived a
659
671
  * hit and count the episode toward the base rate (a repeated outcome with nothing seen since
660
- * counts nothing), credit
672
+ * counts nothing, unless `IntentConfig.episodePerOutcome` makes every outcome an episode of its
673
+ * own for the intent), credit
661
674
  * the closed episodes still waiting under `IntentConfig.attribution` by `2^(−age / halfLife)`
662
675
  * each, record `lastOutcomeAt`, and (by default) reset the evidence to the prior. Any event opens
663
676
  * or extends the current episode; an idle gap longer than `config.episodeTimeout` closes the
package/dist/index.d.ts CHANGED
@@ -138,6 +138,18 @@ interface IntentConfig {
138
138
  * weight.
139
139
  */
140
140
  saturate?: boolean;
141
+ /**
142
+ * Each outcome is an episode of its own for this intent, rather than the session. After the
143
+ * outcome arrives, the intent's bookkeeping starts over: a later outcome in the same session
144
+ * counts as another episode even with nothing seen since, and signals seen after an outcome
145
+ * take a miss at the session's close (or wait under `attribution`), as a first attempt would.
146
+ * Default false: the episode is the session, a repeated outcome with nothing seen since counts
147
+ * nothing, and signals seen after an outcome are neutral at the close. For intents shaped as
148
+ * attempts (an upload that fails, then completes, then completes again), where the second
149
+ * attempt is the contrast learning needs. With it set, a duplicated outcome event counts twice:
150
+ * dedupe such events in the app.
151
+ */
152
+ episodePerOutcome?: boolean;
141
153
  /**
142
154
  * How long a closed episode's signals wait for the outcome before they take a miss, in the same
143
155
  * unit as `at`. With it set, an episode that ends without the outcome is held instead of
@@ -657,7 +669,8 @@ declare function explain(state: EngineState, intent: string, at: number, opts?:
657
669
  * it either way; otherwise every occurrence adds all of `w`. For every intent the event is the
658
670
  * outcome of: give the signals remembered since the episode started or the outcome last arrived a
659
671
  * hit and count the episode toward the base rate (a repeated outcome with nothing seen since
660
- * counts nothing), credit
672
+ * counts nothing, unless `IntentConfig.episodePerOutcome` makes every outcome an episode of its
673
+ * own for the intent), credit
661
674
  * the closed episodes still waiting under `IntentConfig.attribution` by `2^(−age / halfLife)`
662
675
  * each, record `lastOutcomeAt`, and (by default) reset the evidence to the prior. Any event opens
663
676
  * or extends the current episode; an idle gap longer than `config.episodeTimeout` closes the
package/dist/index.js CHANGED
@@ -284,6 +284,7 @@ var INTENT_KEYS = [
284
284
  "outcome",
285
285
  "resetOnOutcome",
286
286
  "saturate",
287
+ "episodePerOutcome",
287
288
  "attribution",
288
289
  "reveal"
289
290
  ];
@@ -441,6 +442,12 @@ function normalizeIntentConfig(value, path, strict = true) {
441
442
  if (typeof record2.saturate !== "boolean") typeError(`${path}.saturate`, "must be a boolean");
442
443
  out.saturate = record2.saturate;
443
444
  }
445
+ if (record2.episodePerOutcome !== void 0) {
446
+ if (typeof record2.episodePerOutcome !== "boolean") {
447
+ typeError(`${path}.episodePerOutcome`, "must be a boolean");
448
+ }
449
+ out.episodePerOutcome = record2.episodePerOutcome;
450
+ }
444
451
  if (record2.attribution !== void 0) {
445
452
  out.attribution = expectPositive(record2.attribution, `${path}.attribution`);
446
453
  }
@@ -757,7 +764,9 @@ function observe(state, event) {
757
764
  }
758
765
  if (isOutcome) {
759
766
  const record2 = episodeRecord(episode, intentName);
760
- if (!record2.resolved || record2.seen.length > 0) is = recordEpisode(is, record2.seen, 1);
767
+ if (!record2.resolved || record2.seen.length > 0 || intent.episodePerOutcome === true) {
768
+ is = recordEpisode(is, record2.seen, 1);
769
+ }
761
770
  is = creditPending(is, intent, at);
762
771
  episode = markResolved(episode, intentName);
763
772
  is = { ...is, lastOutcomeAt: at };
@@ -810,7 +819,8 @@ function closeEpisode(state, at) {
810
819
  const intents = { ...state.intents };
811
820
  for (const [intentName, intent] of Object.entries(state.config.intents)) {
812
821
  const record2 = episodeRecord(state.episode, intentName);
813
- if (record2.resolved) continue;
822
+ if (record2.resolved && (intent.episodePerOutcome !== true || record2.seen.length === 0))
823
+ continue;
814
824
  let is = getIntentState(state, intentName, intent);
815
825
  is = settlePending(is, intent, learning, at);
816
826
  is = intent.attribution === void 0 ? recordEpisode(is, record2.seen, 0) : deferEpisode(is, record2.seen, at);