Repository navigation
Releases: Yuribenjamin/oracular
Release list
oracular@0.17.0
Minor Changes
- #75
bdce2bdThanks @Yuribenjamin! - Six lift measures are deprecated. 0.9 addedlearning.liftwith 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@deprecatedinLiftType, 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.liftitself stays, accepting the default, so that a measure can come back as a minor when a real integration shows it should.
oracular@0.16.0
Minor Changes
- #74
7e6b6fcThanks @Yuribenjamin! - The outcome record keeps the estimate the reset wipes.createRecordtakes the state afterobserve, so on the record of an intent's own outcome eventpis what the engine decides right after it, withresetOnOutcomethe 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 gainsbefore, itspandloweratevent.aton that state. Every other record is byte for byte what it was.RECORD_VERSIONstays 1: a new optional field does not change it, readers ignore fields they do not know, andreplayandtunenever read decisions, so a log withbeforereplays on every release that reads version 1. Found by the second production integration.
oracular@0.15.0
Minor Changes
-
#73
3363311Thanks @Yuribenjamin! - An episode per outcome, for intents shaped as attempts. The episode is the session, shared by every intent, so for an upload that fails, completes, and completes again in one session the engine counted one episode: the second completion, with nothing seen before it, was a duplicate under 0.10's rule, and a second attempt that failed and never completed stayed neutral at the close. The failure signal never saw an outcome without it, which is the contrast learning needs, andtuneprinted "1 counted episode" where the app saw two.IntentConfig.episodePerOutcome: truemakes each outcome an episode of its own for that intent: a later outcome in the same session counts as another episode even with nothing seen since, and signals seen after an outcome take a miss at the session's close, or wait underattribution. Everything else is as before,resetOnOutcomeis independent, andtunefollows the engine's counts. Off unless set: every state and every number without it is exactly what it was. With it set, a duplicated outcome event counts twice, so dedupe such events in the app for these intents. The JSON Schema lists it. Found by the second production integration, whose workaround,endEpisodeon the outcome, ends the session for every other intent too.Rollback. A stored config that sets
episodePerOutcomeloads in 0.13 and later, which do not read the stored snapshot when the app passes its config; 0.12 and earlier throw aTypeErroron the unknown key, so remove it from the saved config before rolling a deploy back that far. The state version is unchanged.
oracular@0.14.0
Minor Changes
-
#71
d9e9769Thanks @Yuribenjamin! - A cooldown of its own after the outcome. The outcome event paused reveals for the revealcooldown: after a "stuck" intent's outcome, help was suppressed for the hour the cooldown named, and the next attempt that went wrong, exactly when the help should be back, got nothing; the only way out,cooldown: 0, disabled the reveal cooldown too.reveal.outcomeCooldownis the pause after the outcome, resolved per call → intent → engine like the other reveal options, 0 allowed; it defaults to the resolvedcooldown, so nothing moves unless it is set. The JSON Schema lists it. Found by the second production integration.Rollback. A stored config that sets
outcomeCooldownloads in 0.13 and later, which do not read the stored snapshot when the app passes its config; 0.12 and earlier throw aTypeErroron the unknown key, so remove it from the saved config before rolling a deploy back that far. The state version is unchanged.
oracular@0.13.0
Minor Changes
- #69
02e0c44Thanks @Yuribenjamin! - A rollback no longer trips over a config key it does not know.deserializevalidated the config snapshot stored in the state even when the app passed its current config, which replaces it. So a state written by a release that set a new option (boundin 0.8,learning.liftin 0.9) threw aTypeErrorin the release before it, the changelogs asked for the key to be removed from the saved config before rolling a deploy back, and in@oracular/reactthe load was a'parse'failure that blocked writes untilclear(). With a config passed, the stored snapshot is now not read at all: a key or a value this release does not know is no obstacle, and every number the state holds comes through as before. Without a config, the stored snapshot runs the engine and is read as it was: without the checks that refuse a config that cannot work, and with a key the library does not know still an error, since the engine would otherwise run without an option the state was built under. Nothing changes for a state that loaded before. Under rule 6 of the stability policy, states now move between releases in both directions for the config snapshot as they already did for the state's fields, and the policy says so.
oracular@0.12.1
Patch Changes
- #64
c108ca9Thanks @Yuribenjamin! -replayshows the reveals it could not score. A reveal whose window runs past the end of the log is not scored for precision, since its verdict is not in yet, but an outcome it preceded still counts for recall, so a short log printedreveals 0besiderecall 100.0% of 1, and--jsonshowedreveals: 0, caught: 1, censored: 1. Both numbers were right and the table did not say why. The text table now shows such reveals in the row (0 (+1 censored)) with a footnote that says what recall still counts, and a log shorter than an intent's scoring window, in which every reveal and point is censored, gets a warning on stderr that points at--window. The--jsonreport is unchanged:censoredwas already there. Found by the second production integration on a one-minute log with the default one-hour window.
oracular@0.12.0
Minor Changes
-
#62
0502b78Thanks @Yuribenjamin! - The clock rule: one glitch cannot move the engine. 0.10.0 made every write trust the latest event's time, so that one event stamped in the future could no longer hold an intent until the clock caught up. A production re-test found the cost: a clock that steps backwards (a phone that wakes up in 1970 before it syncs, a seconds-for-milliseconds bug) pulled every anchor back with it, and the next event decayed the whole gap again and closed the session, recording a miss nobody earned. The two cases are one ambiguity: after one backward step the engine cannot tell whether the new stamp or the old anchor is the wrong clock. It now waits one event. The engine keeps a clock, the latest event time it trusts; an event stamped behind it counts now, at the clock's time, and its stamp is held. If the next event is also behind the clock, the clock was corrected: every anchor ahead of the held stamp (evidence, learned counts, reveals, outcomes, the open episode, the episodes waiting underattribution) moves back to it, and decay and cooldowns resume from there. If the next event is at or after the clock, the held stamp was a glitch, and nothing moved. Monotone clocks see no difference at all, and every number in the docs is unchanged. What changes: a clock that was ahead heals two events after it is corrected rather than one, and heals every intent at once rather than only the ones later events touch; a reveal stamped ahead of the clock is pinned by the next event on any intent; a single backward stamp wipes nothing and closes nothing.Two costs, stated. The first event after a correction is processed at the uncorrected time, so the intents it touches are decayed to that time and their episodes waiting under
attributioncan settle as misses before the second event moves everything back. And two bad stamps in a row read as a correction, which costs exactly what 0.10 paid on every backward step: the anchors move to the bad time and the next good event decays the gap again. One case no rule covers: a device that boots in 1970 and syncs forward later makes one monotone jump of decades, indistinguishable from a long absence, so holdobserveuntil the device clock is trusted.State version 4. The clock is stored, so
STATE_VERSIONis 4 and version 3 states (0.6.0 to 0.11.x) migrate on load: the clock is set to the latest time the stored anchors were written at, nothing is held, and no estimate changes. Under rule 5 of the stability policy, 0.11 reads a version 4 state as its own; what it loses is the clock: a backward stamp awaiting confirmation is forgotten, and the next event is trusted the way 0.11 trusted every event.
Patch Changes
-
#62
0502b78Thanks @Yuribenjamin! - Size. The wholeoracularentry goes from 7.72 kB to 8.09 kB minified and gzipped with the clock rule and its state version, the quickstart import (createState,observe,shouldReveal,markRevealed) from 4.63 kB to 4.85 kB. That crosses the 7.9 kB ceiling the stability page set in 0.8, so the ceiling moves as it did then, to the shipped size plus about 10%: 8.9 kB, with the quickstart budget at 5 kB. -
#62
0502b78Thanks @Yuribenjamin! - Presets are read-only in the types too.presetsandpresetSourcesareDeepReadonly<...>, matching the frozen objects: assigning into a preset is a type error, spreading one is unchanged.
oracular@0.11.0
Minor Changes
- #58
d6d1e87Thanks @Yuribenjamin! - Learning no longer drifts without outcomes. The outcome rates a signal is judged by, with it and without it, were Laplace-smoothed, which centres both on 0.5 with one pseudo-hit each: two groups with no outcome at all then differ only by their sizes, so with zero outcomes a common signal drifted negative and a rare one positive, and a weight could change sign on no evidence (a weight of 2 seen in 90 of 100 outcome-free episodes ended at −0.59). Both rates are now shrunk toward the intent's own base rate by two pseudo-episodes,(hits + 2·q₀) / (n + 2)withq₀ = outcomes / episodes: a signal whose outcome rate equals the base rate learns exactly zero whatever the sample sizes, with no outcome every rate is 0 and no weight moves away from the developer's, and a weight never changes sign without outcomes on at least one side. The association-rule baseline becomes the base rate itself.tunefits with the same smoothing, so its weights, intervals and lifts move a little on small samples (a signal seen once with the outcome over a low base rate is worth a little less than before); on large samples the change is in the third decimal. Every learned number in the documentation's generated tables was regenerated. The state format is unchanged, and existing learned counts are kept: only the weight read from them moves.
@oracular/react@0.2.2
Patch Changes
-
#67
709d782Thanks @Yuribenjamin! - Hydration with a store that loaded first. The hooks hydrated with the store's current state, so following the cold-start advice,await store.load()before the first screen renders, under server rendering made the client's first render disagree with the markup the server sent: React reported a hydration mismatch and rendered the tree again.useIntent'sstate,usePredictionanduseIntentSelectornow hydrate with the fresh state the server rendered,store.getInitialState()(new: the state the store was created with, the same object for its life), and then show the loaded one;usePredictionis computed from that snapshot rather than read from the store, with the same re-rendering as before. The functionsuseIntentreturns go to the current state, as documented, and belong in handlers in a server-rendered tree.An adapter that returns
undefinedcounts as an empty key. An adapter written in JavaScript that answeredundefinedinstead ofnullfor a missing key was treated as unreadable data: reported as'parse', writes blocked untilclear(). It is now an empty key, as the shipped adapters already report.
@oracular/react@0.2.1
Patch Changes
-
#60
ac0b2bcThanks @Yuribenjamin! - A parse failure is reported once and stops the re-reads. Stored data thatdeserializerejects blocked writes, as it should, but every debounced save then read it again, parsed it again and reported'parse'again, for data that cannot parse any better the second time. Saves after a parse failure now read and write nothing;load()reads again and reports only when the stored data changed, andclear()starts over, which is the handling to pass:onError: (error, phase) => { if (phase === 'parse') void store.clear(); }. A read that throws is still retried by every save, since the next attempt may succeed.An empty key counts as saved. A store that found nothing stored wrote a fresh state on the first flush or unmount even when nothing had been tracked, and so did a store after
clear(). An empty key holds what a fresh state holds, so those writes are gone; the first event writes as before.Cold start, in the docs. Before the read settles,
revealdecides on the state in memory, not on the stored history. The README and the guide now say toawait store.load()before the first screen renders where a nudge there must respect a stored cooldown.