kumulant

schema.runtime

Turns the pure-data specs of com.eignex.kumulant.schema into live, running accumulators. The specs in the parent package say what to build; this package builds it, groups the results, and fans every update out to the right stat. Nothing here goes on the wire: it is the materialization and grouping layer that sits between a decoded schema and the live stats it stands up.

Materialization

Every spec materializes through one of the modality-specific extension functions, each of which takes a Concurrency mode and returns a live stat of the matching modality. The concurrency mode is the only input that isn't already on the spec; it is the deployment knob passed in at build time, so the same decoded payload can run unsynchronized in a test and strictly synchronized in a contended hot loop.

Declaring and reading a group

StatSchema is the declarative entry point. A schema subclass registers its stats through the series, paired, vector, and discrete delegates, and each delegate hands back a com.eignex.kumulant.schema.StatKey carrying the result type. The schema materializes into a StatGroup, which is itself a series stat over com.eignex.kumulant.schema.GroupResult: it forwards every update to all of its entries and collects their snapshots into one result, read back by typed key rather than by string. The PairedStatGroup, VectorStatGroup, and DiscreteStatGroup variants fan updates only to entries of their own modality.

A schema flattens to its pure-data form as a StatSchemaDef, which is the part that crosses the wire. A coordinator can stand up the same group from a definition, run it independently, and fold workers' snapshots back in through merge.

Building groups by hand

When an aggregation isn't wire-expressible, for instance a filter-wrapped stat that depends on a live lambda, the group can be assembled directly from name-to-stat pairs rather than from a schema. The list-stat builders behind ListStats and its modality variants do exactly this, and BoundStat is the name-plus-stat pairing they collect. This bypasses the schema layer entirely, trading wire portability for the freedom to hold a live stat the spec vocabulary can't describe.

Types

Link copied to clipboard
sealed class AbstractStatGroup<S : Stat<*>> : GroupedStat

Internal base shared by StatGroup, PairedStatGroup, and VectorStatGroup. Holds the spec list and provides the modality-agnostic read / merge / reset implementations.

Link copied to clipboard
data class BoundStat<R : Result, S : Stat<R>, K : StatKey<R>>(val key: K, val stat: S)

Pairs a StatKey with the live Stat that produces results for that slot. The combination is what a StatGroup / ListStats actually holds; the key for typed lookup, the stat for accumulation. Carries three type parameters so the key's R / S / K match without erasure shenanigans at the call site:

Link copied to clipboard

StatGroup variant over discrete (Long) inputs.

Link copied to clipboard

Marker interface for stats whose result is a GroupResult. Implemented by StatGroup and its modality variants. Used in the group declarator below to enforce that nested-group entries actually produce GroupResult rather than some other Result shape.

Link copied to clipboard
class PairedStatGroup(stats: List<BoundStat<*, out PairedStat<*>, *>>, concurrency: Concurrency? = null) : AbstractStatGroup<PairedStat<*>> , PairedStat<GroupResult>

StatGroup variant over paired (x, y) inputs.

Link copied to clipboard
class StatGroup(stats: List<BoundStat<*, out SeriesStat<*>, *>>, concurrency: Concurrency? = null) : AbstractStatGroup<SeriesStat<*>> , SeriesStat<GroupResult>

Fans each update out to a heterogeneous list of SeriesStats and reports their results keyed by name.

Link copied to clipboard
abstract class StatSchema(val concurrency: Concurrency = Concurrency.None) : Schema<StatSpec>

Declarative, typed schema for a group of stats, layered on top of com.eignex.skema.Schema<StatSpec> so the entries map is wire-serializable.

Link copied to clipboard
@Serializable
data class StatSchemaDef(val stats: Map<String, StatSpec>)

Pure-data, serializable form of a StatSchema. The wire field is stats (kumulant convention, customised over skema's default entries). Decode an incoming wire payload into this type and rehydrate a live group via materializeSeries (or one of the modality-specific variants).

Link copied to clipboard
class VectorStatGroup(stats: List<BoundStat<*, out VectorStat<*>, *>>, concurrency: Concurrency? = null) : AbstractStatGroup<VectorStat<*>> , VectorStat<GroupResult>

StatGroup variant over vector inputs.

Functions

Link copied to clipboard
inline fun <K, S : GroupedStat> group(name: String, keys: K, build: (K) -> S): BoundStat<GroupResult, S, GroupStatKey<K>>

Build a nested-group BoundStat. build is invoked with keys (the sub-schema's key handle) and must return a GroupedStat; typically a StatGroup constructed against that sub-schema. The resulting BoundStat uses a GroupStatKey so dotted lookup result[outerKey][innerKey] compiles.

Link copied to clipboard
fun StatSchemaDef.materialize(concurrency: Concurrency = Concurrency.None): List<BoundStat<*, *, *>>

Materialize every entry, regardless of modality. Caller filters by stat type.

fun <R : Result> DiscreteStatSpec<R>.materialize(concurrency: Concurrency = Concurrency.None): DiscreteStat<R>
fun <R : Result> PairedStatSpec<R>.materialize(concurrency: Concurrency = Concurrency.None): PairedStat<R>
fun <R : Result> RegressionStatSpec<R>.materialize(concurrency: Concurrency = Concurrency.None): RegressionStat<R>
fun <R : Result> SeriesStatSpec<R>.materialize(concurrency: Concurrency = Concurrency.None): SeriesStat<R>

Construct a live SeriesStat from a SeriesStatSpec. One when per modality, one cast at the boundary - sealed-hierarchy exhaustiveness keeps the cast safe.

fun StatSpec.materialize(concurrency: Concurrency = Concurrency.None): Stat<*>

Construct a live stat from any StatSpec, dispatching on its modality. Useful for code paths (like StatSchemaDef.materialize) that iterate over an erased Map<String, StatSpec> and don't statically know the modality.

fun <R : Result> VectorStatSpec<R>.materialize(concurrency: Concurrency = Concurrency.None): VectorStat<R>
Link copied to clipboard
fun StatSchemaDef.materializeDiscrete(concurrency: Concurrency = Concurrency.None): List<BoundStat<*, out DiscreteStat<*>, *>>

Materialize discrete-modality entries only; throws if any entry isn't discrete.

Link copied to clipboard
fun StatSchemaDef.materializePaired(concurrency: Concurrency = Concurrency.None): List<BoundStat<*, out PairedStat<*>, *>>

Materialize paired-modality entries only; throws if any entry isn't paired.

Link copied to clipboard
fun StatSchemaDef.materializeSeries(concurrency: Concurrency = Concurrency.None): List<BoundStat<*, out SeriesStat<*>, *>>

Materialize series-modality entries only; throws if any entry isn't series.

Link copied to clipboard
fun StatSchemaDef.materializeVector(concurrency: Concurrency = Concurrency.None): List<BoundStat<*, out VectorStat<*>, *>>

Materialize vector-modality entries only; throws if any entry isn't vector.

Link copied to clipboard
fun <R : Result, S : Stat<R>, K : StatKey<R>> stat(key: K, value: S): BoundStat<R, S, K>

Build a BoundStat from an existing StatKey and a live stat. Used when the key was created elsewhere (e.g. via a StatSchema declarator) and you want to pair it with a specific live instance; common in tests and in materializer code paths.

fun <R : Result, S : Stat<R>> stat(name: String, value: S): BoundStat<R, S, StatKey<R>>

Build a BoundStat from a string name and a live stat. Convenience for ad-hoc groups: BoundStat(StatKey(name), value) with type inference.