oracular 0.15.0 → 0.17.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 CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.17.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#75](https://github.com/Yuribenjamin/oracular/pull/75) [`bdce2bd`](https://github.com/Yuribenjamin/oracular/commit/bdce2bdcffceab6465b08cf1ae1361ec47b8a1bf) Thanks [@Yuribenjamin](https://github.com/Yuribenjamin)! - **Six lift measures are deprecated.** 0.9 added `learning.lift` with seven association measures. A sweep of all seven on the clickstream case study found none that beats the default, `'normalized-difference'`, on either split, and 1.0 commits every value in the schema for the whole of 1.x. So `'absolute-difference'`, `'relative-lift'`, `'risk-ratio'`, `'log-lift'`, `'odds-ratio'` and `'association-rule'` are marked `@deprecated` in `LiftType`, named in the JSON Schema's description, and removed in 1.0. Nothing changes in this release: every value keeps working exactly as it did, a config that sets one keeps loading, and the type checker shows the mark. `learning.lift` itself stays, accepting the default, so that a measure can come back as a minor when a real integration shows it should.
8
+
9
+ ## 0.16.0
10
+
11
+ ### Minor Changes
12
+
13
+ - [#74](https://github.com/Yuribenjamin/oracular/pull/74) [`7e6b6fc`](https://github.com/Yuribenjamin/oracular/commit/7e6b6fc3885dac0167b92661e188e3f779a73a45) Thanks [@Yuribenjamin](https://github.com/Yuribenjamin)! - **The outcome record keeps the estimate the reset wipes.** `createRecord` takes the state after `observe`, so on the record of an intent's own outcome event `p` is what the engine decides right after it, with `resetOnOutcome` the prior (0.150 where it had been 0.506 one event earlier). Replay recomputes everything from the events and never reads it, but people read the log, and the number that matters most on an outcome line was gone. `createRecord(state, event, options, previous)` takes the state from before the event as an optional fourth argument; the decision of each intent whose outcome the event is then gains `before`, its `p` and `lower` at `event.at` on that state. Every other record is byte for byte what it was. `RECORD_VERSION` stays 1: a new optional field does not change it, readers ignore fields they do not know, and `replay` and `tune` never read decisions, so a log with `before` replays on every release that reads version 1. Found by the second production integration.
14
+
3
15
  ## 0.15.0
4
16
 
5
17
  ### Minor Changes
package/README.md CHANGED
@@ -204,15 +204,13 @@ effective = (k × w₀ + nEff × learned) / (k + nEff) k = priorEpiso
204
204
 
205
205
  `w₀` is your initial weight, `gain` (default 2) caps the learned magnitude at `gain × |w₀|` for every lift option, and `priorEpisodes` (default 10) is how much contrast it takes for the user's own history to count as much as your guess. A signal's strength and your confidence in it are separate knobs. A signal is judged against the sessions in which it did *not* appear, so for an outcome that happens in 3% of sessions, a signal followed by it 30% of the time is strong evidence, not weak. And learning needs contrast: a signal that has never been absent has no counterfactual, so its weight stands until there are sessions without it. Nothing drifts after a single outcome.
206
206
 
207
- The formula above is the default **normalized difference**. `learning.lift` is an optional
208
- setting for advanced users who want to choose the learning scale. Choose another at construction with
209
- `createState({ intents, learning: { lift: 'relative-lift' } })`. Omit `learning.lift` to keep the
210
- current behavior, including when loading older saved states. The available names and formulas
211
- are listed in [Episodes & learning](https://yuribenjamin.github.io/oracular/guide/concepts/episodes-and-learning#choosing-a-lift-formula).
212
- Every choice produces a signed evidence score in [−1, 1], with independence at zero. Ratios use
213
- `(R − 1) / (R + 1)`, relative/log lift use `x / (1 + |x|)`, and absolute/normalized differences
214
- keep their scale. Learning uses `gain × |w₀| × score`; `oracular tune` uses the same score for
215
- weights and intervals and reports raw lift separately when it differs from the score.
207
+ The formula above is the default **normalized difference**. `learning.lift` can name another
208
+ association measure (`'relative-lift'`, `'risk-ratio'`, `'log-lift'`, `'odds-ratio'`,
209
+ `'absolute-difference'`, `'association-rule'`), each converted to a signed evidence score in
210
+ [−1, 1] with independence at zero, as [Episodes & learning](https://yuribenjamin.github.io/oracular/guide/concepts/episodes-and-learning#choosing-a-lift-formula)
211
+ lists. **Those six are deprecated and removed in 1.0:** a sweep of all seven on the clickstream
212
+ case study found none that beats the default on either split. They keep working until then; omit
213
+ `learning.lift`, or set the default, and nothing changes.
216
214
 
217
215
  Learned counts never decay unless you set `learning.halfLife`.
218
216
 
@@ -264,7 +262,7 @@ evaluate(state, intent: string, at: number, opts?): { prediction, reveal, explan
264
262
  inspect(state, at: number, opts?): { at, intents: [{ intent, prediction, reveal, explanation }] } // evaluate for every intent
265
263
  evidenceToReveal(intentConfig, opts?): number // pseudo-counts at which shouldReveal first becomes true
266
264
  episodeClosed(prev, next): boolean // did an episode close between two states?
267
- createRecord(state, event, { installId, mode }): EventRecord // one recorder line: the event and what was decided
265
+ createRecord(state, event, { installId, mode }, previous?): EventRecord // one recorder line: the event and what was decided; with the state before the event, the outcome's record keeps the estimate the reset wipes
268
266
  createEndRecord(at, { installId, mode }): EndRecord // one recorder line: the episode ended
269
267
  serialize(state): string
270
268
  deserialize(json: string, opts?: { config?: EngineConfig; migrate?: Migrate }): EngineState
package/dist/index.cjs CHANGED
@@ -899,10 +899,11 @@ function normalizeRecordOptions(value) {
899
899
  }
900
900
  return { installId, mode, top };
901
901
  }
902
- function createRecord(state, event, opts) {
902
+ function createRecord(state, event, opts, previous) {
903
903
  const s = requireState(state);
904
904
  const { name, at } = validateEvent(event);
905
905
  const { installId, mode, top } = normalizeRecordOptions(opts);
906
+ const prev = previous === void 0 ? void 0 : requireState(previous);
906
907
  const learning = resolveLearning(s.config);
907
908
  const decisions = {};
908
909
  for (const [intentName, intent] of Object.entries(s.config.intents)) {
@@ -914,7 +915,7 @@ function createRecord(state, event, opts) {
914
915
  for (const signal of Object.keys(intent.signals)) {
915
916
  weights[signal] = rows.find((row) => row.signal === signal).effectiveWeight;
916
917
  }
917
- decisions[intentName] = {
918
+ const decision = {
918
919
  p: prediction.p,
919
920
  lower: prediction.lower,
920
921
  shouldReveal: decideReveal(prediction, reveal),
@@ -922,6 +923,17 @@ function createRecord(state, event, opts) {
922
923
  top: rows.filter((row) => row.contribution !== 0).slice(0, top).map((row) => ({ signal: row.signal, impact: row.impact })),
923
924
  weights
924
925
  };
926
+ const prevIntent = prev === void 0 ? void 0 : getOwn(prev.config.intents, intentName);
927
+ if (prev !== void 0 && prevIntent !== void 0 && intent.outcome === name) {
928
+ const was = buildPrediction(
929
+ assessIntent(prev, intentName, prevIntent, resolveLearning(prev.config), at),
930
+ intentName,
931
+ resolveReveal(prev.config, prevIntent, void 0),
932
+ at
933
+ );
934
+ decision.before = { p: was.p, lower: was.lower };
935
+ }
936
+ decisions[intentName] = decision;
925
937
  }
926
938
  return { v: RECORD_VERSION, kind: "event", installId, mode, event: { name, at }, decisions };
927
939
  }