0.0
The project is in a healthy, maintained state
Turn facts into numbers. RollupEngine declares measures, rolls them up by time grain and dimension, and recomputes idempotently. Source-agnostic — feed it any facts (dimensions + measures); it does not depend on any particular event source.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Runtime

< 3
>= 7.1.6, < 9
 Project Readme

RollupEngine

A source-agnostic aggregation engine. Feed it facts — rows with dimensions and measurable values — and it turns them into numbers: declared measures, rolled up by time grain and dimension, recomputed idempotently.

RollupEngine depends on no particular data source. It doesn't know about events, an outbox, or any specific producer — anything that can hand it facts can use it. (In the EventEngine stack, an adapter feeds event_engine-store events in as facts; but that adapter lives elsewhere, not here.)

Installation

gem "rollup_engine"
$ bundle

Status

Early development. The first rollup works — RollupEngine::Rollup.count / .sum aggregate facts by time grain and optional dimensions:

RollupEngine::Rollup.count(facts, grain: :day, time: :occurred_at)
RollupEngine::Rollup.sum(facts, :amount, grain: :day, time: :occurred_at, by: [:channel])

Declare named measures once and compute by name:

RollupEngine.register_measure(:revenue, aggregation: :sum, field: :amount)
RollupEngine::Rollup.compute(facts, measure: :revenue, grain: :day, time: :occurred_at)

Persist rollups idempotently into the rollup_engine_datapoints table:

RollupEngine.recompute(:revenue, facts, grain: :day, time: :occurred_at, by: [:channel])
# upserts one RollupEngine::Datapoint per (measure, grain, period_start, dimensions);
# re-running updates values in place — no duplicates.

time, field, and dimensions accept a method symbol or a callable, so facts with nested data roll up without a wrapper — e.g. an event's JSON payload. A dimension read with a callable is passed in a hash under the name it is saved as:

RollupEngine.register_measure(:revenue, aggregation: :sum, field: ->(e) { e.payload["amount"] })

RollupEngine.recompute(:revenue, stored_events,
  grain: :day,
  time:  ->(e) { e.occurred_at },
  by:    { channel: ->(e) { e.payload["channel"] } })

This is what lets event_engine-store events feed rollup_engine without coupling the two.

Next: a recompute job + window scoping, then sketches/cohorts.

License

Available as open source under the terms of the MIT License.