kumulant

StatSpec

@Serializable(with = StatSpecSerializer::class)
sealed interface StatSpec(source)

Pure-data recipe for a com.eignex.kumulant.core.Stat. Each variant is a data class (or data object for parameter-less stats) whose fields are exactly the stat's configuration surface - no live cells, no locks, no com.eignex.kumulant.core.Concurrency.

JSON uses names for parameterless nodes, such as "mean", and operator keys for configured nodes, such as {"ewma":{"alpha":0.1}}. Decorators retain named fields, such as {"transform":{"expr":{"add":[{"input":"x"},1]},"inner":"mean"}}. Serialize through this root or a modality interface to preserve that grammar. Other serialization formats retain the generated sealed-class representation. Defaults match the corresponding stat constructors and follow the format's encodeDefaults setting.

Construction of the live stat happens externally via the materialize extension functions. Each specification tree is compiled into a construction plan that checks modalities and result capabilities before allocating accumulators, with one cast at the typed materialization boundary. Specs stay as pure data so the wire format doesn't drag behaviour through it. com.eignex.kumulant.core.Concurrency is not on the wire - it's a deployment knob passed in at materialize time.

The generic <R> on the modality-specific spec interfaces is a phantom marker: it carries the result type through the schema declarators (series(spec): StatKey<R>) and the materialize return type, but it does not appear in the wire format.

Samples

val spec: StatSpec = Mean
val json = SchemaJson.encodeToString(spec)
val decoded = SchemaJson.decodeFromString<StatSpec>(json)
val live = (decoded as SeriesStatSpec<*>).materialize(Concurrency.None)
live.update(1.0)

Inheritors