How it works
Tiaude is built around a simple idea:
Product events can not only score a churn, but also leave a compact and standardized mathematical state that will be sufficient to compute the new score at the next event
Most churn scoring systems start from a batch question:
Given all historical events, what is this user's churn risk?Tiaude supports that with scoreUser(), but it also introduces a streaming question:
Given the previous state and one new event, how should the score evolve?That is what track() does.
The SDK is designed around this duality:
scoreUser()is the full recompute path.track()is the incremental runtime path.refreshScore()is the time-only update path.
They are not three different scoring models. They are three execution modes of the same deterministic model.
The model in one sentence
Tiaude turns configured business signals into bounded intensities, multiplies them by weights, sums their impacts, and stores only the compact residues needed to continue the computation later.
That sentence contains the whole architecture:
- signals express business meaning;
- intensities normalize different behaviors into
[0, 1]; - weights decide how much each signal moves risk;
- residues compress event history;
- state allows incremental continuation;
- hash compatibility prevents unsafe reuse.
Why this approach exists
A naive scorer has to load a user’s full event history every time something changes:
- load all historical events;
- sort them;
- replay them;
- recalculate every signal;
- persist the result.
That is simple, but it becomes expensive as event history grows.
Tiaude keeps the same mathematical correctness model, but gives you a cheaper hot path. Once a valid state exists, the next event can be processed without replaying everything.
events + config + now -> scoreUser() -> full state
previousState + event + now -> track() -> next state
previousState + now -> refreshScore() -> time-advanced stateThe important production rule
previousState is an optimization artifact.
It is not your source of truth.
Your raw event history should still be stored by your application if you want reliable recovery, future config changes, audits, or full recomputes.
In production, the recommended mental model is:
raw events in your database = source of truth
Tiaude state = compact runtime continuationtrack() and refreshScore() help you avoid loading and replaying all events on every request. They are not a reason to stop storing events.
When incremental reuse is unsafe, use scoreUser() to rebuild the state from raw events.
Read in this order
-
Mathematical model
The equations behind risk score, intensity, decay, confidence, and freshness. -
Residues and state
How Tiaude compresses event history into a compact persisted state. -
Incremental scaling
WhypreviousStatemakes the runtime path scale with configured signals rather than historical events.
Core properties
Deterministic
The same config, the same events, and the same now produce the same result.
There is no remote model, no sampling, no hidden state, and no backend dependency.
Explainable
Every active signal produces an impact.
The score is not a black box. You can inspect:
- which signals are active;
- how strong they are;
- how much they moved risk;
- which reasons and recommended actions were returned.
Incremental
A valid previousState contains the compact memory needed to continue scoring without replaying all events.
That is what makes track() useful in a backend receiving product events continuously.
Recoverable
When the incremental path is not safe, the SDK is designed to fail loudly.
For example, it rejects:
- incompatible config;
- out-of-order events;
- corrupted state;
- states from another user;
- time travel on a previously computed state.
The recovery path is explicit:
load raw events -> scoreUser() -> persist fresh stateWhy the state is compact
The state size depends on the number of configured signals, not the number of historical events.
That is the key scaling property.
A user with 50 events and a user with 50,000 events can have a state of the same shape if they use the same config.
This works because each signal stores a residue: the smallest piece of information needed to preserve that signal’s future behavior.