Velocity / Trend / Z-Score Aggregation Operators
The 9 velocity-family ops cover rate-of-change between adjacent events, behavioural cadence (inter-arrival statistics, burst-count peaks), value-trajectory (delta-from-prev, trend slope, trend residual), outlier flags (sigma-bound counts), value-flip counts, and entity-level z-scores. Together they answer "is this entity moving, drifting, spiking, or breaking from its own history?".
| Op | Required kwarg(s) | Returns | CPU tier | Notes |
|---|---|---|---|---|
bv.rate_of_change |
window |
f64 or null |
Tier 1 | Two-event delta divided by Δt_ms. |
bv.inter_arrival_stats |
window |
f64 or null |
Tier 1 | v0 emits mean_ms; v0.1+ widens to {mean_ms, stddev_ms, cv}. Field-less. |
bv.burst_count |
window, sub_window |
i64 |
Tier 1 | Max events in any 1 of 64 sliding sub-window slots. Field-less. |
bv.delta_from_prev |
— | f64 or null |
Tier 1 | Lifetime-only; absolute jump (no Δt). |
bv.trend |
window |
f64 or null |
Tier 1 | OLS slope of (now_ms, field); smoother than rate_of_change. |
bv.trend_residual |
window |
f64 or null |
Tier 1 | Latest value minus its trend-line prediction; shares state with bv.trend. |
bv.outlier_count |
window (sigma=3.0 default) |
i64 |
Tier 2 | Welford-baseline + sigma-threshold count; the only Tier 2 velocity op (sqrt() per event). |
bv.value_change_count |
window |
i64 |
Tier 1 | Counts adjacent flips, not net distinct values. |
bv.z_score |
baseline_window |
f64 or null |
Tier 1 | Current event's deviation against the entity's running (mean, stddev). |
All 9 are O(1) memory per entity — see the per-op page Complexity section for byte-level state shapes. Eight of nine are Tier 1; only bv.outlier_count is Tier 2 (one sqrt() per event for the threshold test). Per cost-class.md, the entire velocity family sits in the fast-update tier.
Key invariants
- Server processing-time only. Every windowed dispatch in this family bins events by
now_ms()perproject_redis_shaped_no_event_time_ever. Producers cannot influence binning via payload fields; there is no event-time concept. Late events (Δt ≤ 0) are clamped to0(ininter_arrival_stats) or skipped (inrate_of_change). window=is the canonical kwarg name for 7 of 9.bv.delta_from_previs lifetime-only (nowindow=— it just diffs the most recent two values regardless of elapsed time).bv.z_scoreuses the SDK ergonomic namebaseline_window=to make the "baseline-against-which-the-current-event-is-scored" intent explicit; the wire-formparamsfield is still"window".burst_countrequires bothwindow=ANDsub_window=.sub_windowpartitions the outer window into bucketed slots; the helper rejects missing or malformedsub_window=at SDK call time and the server returns structured erroraggregation_invalid_sub_windowif it reachesregister_validate.rs.outlier_countdefaults tosigma=3.0— the classic three-sigma rule. Tighten tosigma=2.0for ~5% tail; loosen tosigma=4.0for ~0.006% tail. The op also warms up forMIN_BASELINE_N = 5matching events before firing the outlier check, to avoid spurious early-stream increments.- Lifetime mode (
window="forever") is allowed for all 9. Percrates/beava-core/src/register_validate.rs(~line 439–449) every velocity-family op is classifiedOpLifetimeBound::O1— finite per-entity memory ceiling, register-time accepted in lifetime mode. For long-lifetime trend / z-score tracking, mind the FP-precision caveats noted on the per-op pages and prefer a fixedwindow=≤ 1d on busy entities, or fall back tobv.ewma/bv.ew_zscorewhich carry a bounded-magnitude state by design. - Cold-start returns
null(or0for the counters).rate_of_change,inter_arrival_stats,delta_from_prev,trend,trend_residual,z_scorereturnnulluntil enough events accumulate (typicallyn >= 2);burst_count,outlier_count,value_change_countreturn0and only ever increment. - Cold-entity eviction (
@bv.event(cold_after=...)) drops the underlying state per V0-MEM-GOV-01; velocity ops rebuild fresh on the next post-eviction matching event.
When to use which
- "Is the latest value out of line?" →
bv.z_score(magnitude) orbv.outlier_count(count). - "Is this signal accelerating?" →
bv.rate_of_changefor two-event reactive;bv.trendfor window-wide smoothed slope. - "Did the latest event break from a directional pattern?" →
bv.trend_residual. - "How big was the most recent jump?" →
bv.delta_from_prev. - "How busy is this entity?" →
bv.inter_arrival_statsfor cadence;bv.burst_countfor peak. - "How often does this value flip?" →
bv.value_change_count.
Note: per REQUIREMENTS.md,
z_scoreis familyAGG-Z-*— it lives here invelocity/per RESEARCH §3 directory layout (entity-level statistics that pair naturally with the velocity / trend / outlier ops).bv.rate_of_changeis the canonical Phase 9 velocity-family op per RESEARCH §5 — it lives here, not in decay/, because it computes a slope across two adjacent events rather than an exponentially-weighted statistic.
See also
- Operator catalog index — full 53-op catalogue (velocity is the 9-op family)
- cost-class.md — per-op CPU tier metadata (8 Tier 1 + 1 Tier 2 in this family)
- Decay family — sibling family for exponentially-weighted statistics;
bv.ew_zscoreis the drift-aware counterpart tobv.z_score, andbv.ewmais the smoothed counterpart to the underlying signals fed intobv.trend/bv.rate_of_change - Sketch family — sibling family for cardinality / quantile / categorical estimators;
bv.entropyandbv.n_uniqueare non-numeric-friendly anomaly primitives - Recency family — sibling family for "when did this entity last act?" —
bv.streakpairs naturally withbv.value_change_count(consecutive matches vs. flips) - shared.md window grammar — duration-string format (
\d+(ms\|s\|m\|h\|d)and the"forever"literal) - Per-operator memory governance: V0-MEM-GOV-02 — every lifetime aggregation operator declares a finite per-entity memory ceiling at register-time
- Pipeline DSL compilation rules — how
bv.<op>(...)calls compile to JSON wire form