Moving beyond basic formulas and expressions, Summit Event Langauge (SEL) includes a growing number of event classes and associated functions. These classes give you powerful control over how values move throughout your model, a concept we call flow.
A Generator contributes value to other events. The benefit of using a Generator over a List is the ability to attach a growth rate. Lists may only contain constants (unchanging) values, whereas the values returned from a Generator can change over time using a Growth Rate, or a list of growth rates.
A good example is a source of revenue (i.e. a revenue generator). If my business earns $100,000 per month, growing at 3% per month, I could use the Source SEL function to represent this income stream as:
=Source(100k @ 3%) This event will send a steady stream of one hundred thousand (growing at 3%) to downstream events.
Source with Maximum Value
You can also add a second argument to a Source to set a maximum value. For example:
=Source(100k @ 3%, 110k)
This formula says that 100,000 will grow by 3% until it reaches 110,000. At this point, it will continue to generate 110,000 as the output value.
If you send a value to a source, the size of the source will increase by the received value. For example, you can simulate an annual pay increase by sending an annually recurring raise to a monthly salary source.
You can also express a maximum or minimum value for a source -- the spread operator:
=Source(10k...100k @ 5k)means 10,000 will grow by 5,000 each occurrence until it reaches 100,000 and then will repeat in subsequent periods.
=Source(100k...90k @ -10%)means 100,000 will decrease to 90,000 in increments of -10%.
=Source(-2k...-5k @ 10%)means -2,000 will "grow" by 10% per occurrence until it reaches -5,000.
=Source(10k @ 10...50% @ 20%)means 10,000 will increase by a percentage each occurrence, beginning at 10%. The 10% growth rate will increase by 20% each period until it reaches 50% where it will remain.
=Source(10k @ 50...10% @ -15%)means 10,000 will increase by a percentage each occurrence, beginning at 50%. The 50% growth rate will shrink by 15% each period until it reaches 10%.
The Curve function generates values based on a specified curve shape, which makes this event type useful for modeling growth when a precise mathematical model (i.e. growth rates) is unknown.
For example, most growth in nature and business follows an S-curve (short for sigmoid), meaning growth slowly accelerates then decelerates (i.e. quadratic), which is common in early-stage financial plans. These plans would be a lot more realistic (and useful) if modelers used S-curves instead of guessing at (%) growth rates.
A curve function can be written in Summit like so:
=Curve('s', [365, 1k])
The first argument is the type of curve. The supported curve types are
s (S-curve) and
m (straight line or linear).
The second argument is a list where the first value (
365) represents the number of days until the maximum value is reached. The maximum value is defined by an optional second element in the list, here
1k (1,000). If the maximum is omitted, the maximum will default to
🔔 Note: More curve types are coming soon!
Like the source functions above, a Trend generates a series of values and sends them downstream to other events; however, rather than sending an initial value (and optional growth rate), a trend takes a series of historical values, fits a trendline to this series, and returns the values in a sequence.
For example, using the formula
=Trend([12.1k,15.5k,13.6k,14.4k]), the output values generated are:
15,150; 15,650; 16,150; 16,650 ...
True to its name, a Trend is just a trend. The purpose of the function is to offer directionality to its results. Therefore, this generator of values is useful when historical values bounce around, and the most important element is understanding the general movement of these values, rather than the specific value in any given future month.
The Extend function takes a series of values and extends them using the difference in the last two values in the sequence. The extend event is useful for drawing a straight line when the most recent values are an indication of the future.
17, 24, 31, 38 ... because the amount of change between the final two values is 7 (see above).
Containers are an event type that stores value, i.e. the results received from other events. You may think of containers as assets, like a bank account or investment. Containers have associated functions in SEL that dictate the value stored in a container and how those values should move to/from other events when triggered. See below for a list of available container functions in Summit:
A Pool is a container that collects the values it receives and has no limit to the amount it can store.
You can create a basic pool with a formula like
0 is the amount of value the pool contains at the start of the simulation. A common use for a pool could be a bank account or storage system that has no limits.
🔔 Note: When connected to a downstream event, a Pool "reports" its balance, but the Pool does not lose any value.
Pool with Halt Condition
Optionally, a pool can take on a second argument, for example:
We refer to this second argument as a halt condition. The internal value of the event will be tracked and if the balance crosses below
0 (second argument), the simulation will halt. This can be useful when the value of an event represents solvency or a minimum success criterion which you are controlling for while building your board.
A Pipe sends any and all values it receives downstream. A Pipe simply expresses the amount of value flowing through it. This can be helpful for displaying subtotals or totals for a series of upstream events. A Pipe does not have a balance.
A Pipe is commonly defined like so:
The first and only required argument sets an initial value. If, for example, you were simulating a sales pipeline, you'd use this argument to indicate how many deals are at this stage when the simulation begins. In many cases, this will simply be zero.
A pipe can take a second, optional argument, like so:
This second argument sets a constraint on the amount that will be passed along to any connected (downstream) events. In this case, this pipe will only pass along
100, even if it receives a value such as
You can also add a halt condition
! to this second argument, to indicate that the simulation should halt if this value is exceeded. You can think of this as a pipe burst. Here's an example:
This pipe will begin by immediately sending a
$1,000 to any downstream events (in addition to the values it receives), and will limit any other values to a maximum of
$15,000. If this pipe receives a value greater than
$15,000, the simulation will stop.
A Bucket is an event that collects all of the value it receives up to a specified limit. Beyond that limit, the excess value is passed to downstream events. A bucket could represent things with a finite capacity, or things that get saturated.
An example of a bucket in practice could a checking account where you want any funds over a threshold to flow into an investment account.
In Summit, a Bucket could be written as:
The first argument
0 is the starting value of the bucket. The second argument
100 tells the event to store up to
100 units of value only. Any amount above
100 should be sent to a downstream event. We refer to this limit as the event's threshold value.
Bucket with Halt Condition
Adding a halt condition to a Bucket can be useful when bucket size is used as a success or failure criterion. To add a halt condition to a Bucket, attach a
! to the second argument:
! means the simulation will stop once the value of the bucket reaches the specified threshold (in this example,
🔔 Note: The halt condition on a bucket may not be combined with uncertainty, e.g.
~100! will cause a parsing error.
A Tipping Bucket is an event that collects value up to a threshold and then empties all of its value into and downstream events once the threshold is reached or exceeded. Tipping buckets are useful to represent savings goals where you want to simulate action as soon as the goal is reached. For example, you may want to set aside a portion of your savings for a planned trip to Hawaii, and then use those funds as soon as the total expense of the trip is reached.
In the expression
=Tippingbucket(0, 100) once
100 is reached as the value stored in the event, the tipping bucket will reset to
0 and pass all of its value to a downstream event.
Tipping Bucket with Halt Condition
Adding a halt condition to a Tipping Bucket can be useful when you are trying to find the earliest date that the bucket tips. To add a halt condition to a Bucket, attach a
! to the second argument:
! means the simulation will stop once the bucket tips, as specified by the threshold (in this example,
🔔 Note: The halt condition on a bucket may not be combined with uncertainty, e.g.
~100! will cause a parsing error.
A Funnel is an event that releases a specific amount of value from its total value to downstream events each time it's triggered. A funnel represents a container that depletes over time but is mined or drawn from in a steady manner. This could be something like a grain silo, an oil well, straight-line depreciation, or a trust fund.
In the formula
=Funnel(100, 10) the funnel will release 10 from the total each time the event is triggered and send that amount to downstream events. So after the first execution, the funnel will contain 90. Once the funnel reaches 0, it stops sending value downstream. You can replenish a funnel by sending it additional values from events upstream.
A Funnel can also extract, or pull, value from a downstream event like so:
When connected to a downstream event that carries a balance, like a Pool, 10k will be removed from the Pool's balance each time the event is triggered. The Funnel will then track the amount subtracted over time.
A Silo is a container with a minimum (required) and/or maximum (optional) capacity. It fills up with value and its balance can be drained, and, like a bucket, any excess value is passed to downstream events. However, unlike a bucket, if the silo is empty, any negative values will also be passed downstream.
A silo with a minimum and maximum capacity can be written like so:
=Silo(25, [-100, 500])
This means the silo has a starting value of 25, a minimum capacity of -100, and a maximum capacity of 500. Any value sent that results in the event balance falling below -100 or above 500 will be sent to downstream events.
A silo could also be written without a maximum like so:
If the maximum is omitted, the silo will act like a pool, albeit with a minimum value.
This makes a silo ideal for models involving debt and loan amortization since a silo can store accrued interest--once the interest amount empties, any value left over from the payment can then be passed along to an event storing the principal balance. See an example below:
In the example above, to ensure the timing of the flow of value is accurate, the recurring pattern on the Principal Balance is set to
Monthly, while the Payment is set to recur
End of Month.
The Principal Payment event has a display value of
Flow + Endingto show the excess value emptying from the silo to Principal Balance in the output table.
A Gate operates on a schedule (defined by its recurring pattern, and start/end dates, if applicable) that releases all of the value it has stored since its last execution date. A practical example of a gate is a profit-sharing program, where profits are stored until the same date every year, and then released in full. Gates are also useful for payroll cycles, where you want to store or accrue all of the payments owed to contractors or employees until payroll is run (e.g. biweekly or semimonthly or monthly).
A gate can be written as
=Gate(0). Values are then passed into the gate and stored until they are released according to the recurring pattern set on the event.
A Gate always opens the first time at midnight (00:00:00) of its start date. This means it will only include any values provided in the initial construction of the Gate. Any values passed to the Gate on its start date will therefore be included in its first release date after its start date.
In the example below, employee and contractor expenses are accrued and then paid from the bank account on a biweekly basis.
🔔 Note: For the balance to reset, make sure the Gate is connected to a downstream event. The collected values must have a destination where they can be "emptied".
A Base stores the values it receives into a list and modifies the sum of all the values by stated percentages in subsequent periods. This is ideal for storing cohorts -- batches whose sizes change over time.
A simple base could be written as
=Base([100, 50] @ %) where the sum of the first two numbers
150 will be reduced to 90% of their total in all subsequent periods. Assuming this event represents a customer cohort (A), a second base (B) could then be created to compare results for a separate cohort with alternative retention assumptions
=Base([100,25] @ %) as shown below.
The list of values in the first argument of the Base can also be written as empty
 so it can receive values from a Source. The list of percentages can also vary by period so the base total changes dynamically over time. This could be used to model the retention assumptions for a recurring customer acquisition event.
For example, you can write the formula as:
=Base( @ [95, 65, 54, 32, 23]%) This event could represent a customer cohort that grows based on a customer acquisition/conversion event, e.g.
=Source(100 @ 5%), but also decreases in size due to expected monthly customer churn. In this scenario, in month one, 100 goes to the base. In month two, 105 goes to the base and the original 100 is worth 95 (105 + 95 = 200), and the two numbers are summed together. In month three, 110 goes to the base, the original 100 is now worth 65, the 105 is worth 99.75 (0.95 * 105), so 99.75 + 65 + 110 = 275. With the acquisition event set to recur monthly, new customers will continue to be added to the total each month and reduce periodically according to the list of percentages provided in the formula. In this example, after five periods, each value added to the base will eventually be reduced to 23% of its original amount and remain at that value while contributing to the total.
💡 See an example of Base in practice with this Churn & Retention How-to Guide.
A Wheel stores values in a rotating list. Each time the event is triggered, it sends the sum of values stored in the active position downstream.
A wheel is particularly helpful for modeling cash receipts for non-monthly, recurring invoices, such as those that occur on an annual or quarterly basis.
The number of periods in the rotating list is determined by the first (required) argument, like so:
In this case, this is a rotating list with a length of 12 periods, each with a starting value of 0, so the stored values in each position begin as:
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
Each time the wheel is triggered, it will report the value in the list at its active position. The active position is based on the number of times the event has occurred. So the first time it occurs it will report the value at position 1 (starting with 0). The second time, position 2, and so forth. On its 13th time, it will report the value at position 1 again. This is what makes the wheel a wheel.
This makes a wheel a great event type to store information that changes over time-based on a daily, monthly, weekly, quarterly, or annual calendar. For example, I could use a wheel to model cash receipts for annual subscriptions by having a length 12 wheel recur monthly. Each month you can send the wheel the total value of new annual subscriptions starting in a given month and the wheel will keep a running monthly total for those subscriptions internally distinct.
For example, if the first position in the wheel list is 0, and the active position is the first position, and an upstream event sends it a value of $1,000, the result will be $1,000. Then (assuming the recurring pattern on the wheel is monthly), after 12 months, if the event then receives a value of $3,138 the following year, the result in the first position will now be $4,138.
In the example below, we use quarterly totals to more easily show what's happening over the course of the year. In Q1 of year 1, a total of 3,310 annual subscriptions were added (1,000 + 1,100 + 1,210), then in Q1 of year 2, 10,388 were added. The Wheel is able to track and combine the annual subscriptions from Q1 of year 1 and year 2, showing how the total annual subscriptions at the end of Q1 in year 2 are 13,698 (3,310 + 10,388).
A second argument can also be used on a wheel to pre-populate values in each period of the list. For example:
=Wheel(12, [300, 400, 150, 200, 250, 700, 550, 600, 350, 400, 225, 110])
Growth rates can also be applied to the values stored in a wheel (either with or without a list). A growth rate on a wheel with 12 periods without a list can be written like so:
=Wheel(12,  @ 5%)
This means the value stored in a given period (i.e. month) will increase by 5% each subsequent year. For example, if the value in the period representing January is
100 in the first year of the simulation, the value will return
105 in the following year.
The values in the second argument represent the starting values in a list as in a calendar. For example, if you apply a second argument to a monthly-recurring wheel with length 12, the first position always represents January. The second, February, etc.
The active position (i.e. the starting value in the simulation) will be determined by the current month. If the wheel is annually recurring with a length of 12, the active position is the current month of the year. If the wheel is quarterly recurring with length 4, the active position will be the current quarter. Weekly recurring of length 52 (phew!), the current week of the year.
💡 See an example of Wheel in practice with the Multiple Billing Frequencies How-to Guide.
Tiering & Scale
Certain business operations take a numeric value, like
500, and break it apart into smaller pieces, or arrays. Each piece could represent the amount of demand for a certain item, or the number of units requested at a particular price.
💡 These are particularly useful for modeling Tier-based and Volume-based subscription plans and pricing models.
Like the name implies, Tiering defines a set of tiers, using breakpoints, like a List:
=Tiering([5, 10, 20])
This example establishes 4 tiers: 0 to 5, 6 to 10, 11 to 20, and 20+
When this function receives a number, for example,
13, it will find the tier that
13 belongs to--in this case, the third tier: 11-20.
13 belongs in the third tier, this function will return:
[0, 0, 1, 0]
1 points to the tier the input value belongs in.
From here, we can send this
[0, 0, 1, 0] to an expression, like
* [49, 99, 149, 249], which could represent the price of each tier. This multiplication expression would return the value
149 because 1 * 149 = 149.
Similar to Tiering the Scale function defines a set of ranges with breakpoints, like so:
=Scale([150, 250, 500])
This establishes 4 ranges: 0 to 150, 151 to 250, 251 to 500, and 501+
If the value
1000 were sent to this function, it would return:
[150, 100, 250, 500]
150 units fit inside the first range, another 100 inside the second, then 250, and then the remaining 500 into the final (highest) range of 501+.
From here, we can send this list to an expression like
* [1.99, 1.49, 1.25, 0.99], which could represent the value or price of each unit in each range.
When the output of the Scale, a list, is multiplied by another list, SEL returns the single numeric dot-product of the two lists. In this case:
(1.99 x 150) + (1.49 x 100) + (250 x 1.25) + (500 x 0.99) = 1255.
🔔 Lists vs. Single Values:
Tiering and Scale are special insofar as they return a list, which can only be sent to event types capable of receiving a list. At the time of this writing, the only events capable of receiving a list are math expressions that begin with
* and end with a number or list. To resolve a list into a single number, you must return a dot product.
Events send numeric values. Sometimes you may want to test these values before proceeding with some additional logic. The Conditional event type allows you to test values along your chain of events before they are sent downstream.
An If function will test if a value being sent to this event matches the conditions set inside the parentheses.
=If('=', 0) will evaluate whether a received value from another event is equal to zero. If true, the value will be passed along, unaltered, to any events downstream from the If event. If not, the value will not flow through the conditional event.
The first argument is an operator which can be one of:
>=. You may also use string representations:
eq (equal to),
lt (less than),
lte (less than or equal to),
gt (greater than), and
gte (greater than or equal to).
Once is a special type of If function that only occurs once. This is helpful when you want the first and only the first instance of a true result to flow to downstream events.
The operator arguments for Once are the same as If. For example,
will test if the value received by the event is greater than 100,000. If true, the value will be passed downstream. If not, no value will pass through. Any future occurrences of this event will terminate as false, regardless of the value passed in.
Sometimes you may want to transform a value before passing it along to other events. This can be done with a Transform that modifies values using math functions.
=T('ceil') will transform a numeric value like 1.1 into 2.0.
=T('floor') will transform 1.1 into 1.0.
You can transform values using the options:
ceil (round up),
floor (round down),
abs (absolute value),
log (log function), and
You can also use Transform to convert units, for example
The event with this formula will take in the value it receives as liters and output the result in gallons!
Summit has a very large list of available units it can convert. See below for some of the most common (and fun) types. The full list can be accessed from our technical documentation here.
Mass & Volume
Energy & Power
Speed & Velocity
🔔 Fun fact: Parsecs are a measure of distance, not time! In Star Wars, the Kessel Run is 20 parsecs (source: Wookiepedia), so when Han Solo says that the Millennium Falcon can complete the Kessel Run in less than 12 parsecs, he's saying he could maneuver the ship through black holes to shorten the distance! ...by 162 trillion miles, according to Summit 😉
💡 See an example of Transforms in practice with the Sales & Support Staff Hiring Calculator.
Timers allow you to manipulate time in your simulation and delay the flow of value between events in your model.
A Block closes off the flow of value to downstream events for a specified amount of time. You can think of a Block like a valve that opens after a desired number of days.
The "timer" begins the first time the event triggers (i.e. when an upstream event attempts to send value through the block). A Block does not have a balance.
The formula for Block is written as:
In this example, this event will block the passage of any values it receives for 90 days. After that point, it will allow any new values to pass through to downstream events.
A Wait event receives value from another event, inserts a delay, then passes the value downstream unchanged. The value of a wait is measured by the sum of its flow in a given period. A Wait does not have a balance.
A Wait is useful to represent situations like a trial period, pipeline stage, or delay in receivables. Expressed as
60 is the number of days that will pass before the event executes.
You may also use a second (optional) argument to express the delay as
years like so:
The web is all about fetching data from remote sources. Fortunately, SEL provides an event type to make these requests that support common data formats.
Request fetches data from a remote location such as a URL. For example, if you want to pull in the contents of a published Google Sheet into your Summit model, you can make a GET or POST request like so:
This event will fetch the contents of this URL and store them in the event. The value of the event in the output table and log view will be set to the status code of the response. All responses less than
400 are considered successful. Let's look at this example in context:
🔔 Note: This image shows two Request events with response data being sent to Parser events. More info on the Parser SEL event type below.
In this example, two web requests being run--one to fetch some comma-separated values data (CSV Request; see above) and another to fetch data from an endpoint that returns JSON (JSON Request). Each event points to a separate Parser event which processes the response data (see below for more details on Parser).
For each Request event in the output table, the results display the status code associated with the response. The CSV Request event failed, returning a
400 (Bad request), while the JSON Request event succeeded, thus returning a
In order to make a GET or
POST request, you may also need to pass in a series of parameters, called a query string. This list of arguments can be defined as a Querystring event using the following formula as an example:
Sending this event's output to a Request event will pull these arguments into the web request as parameters.
If you want to use the response of your Request in your model, you'll need to send the results to a Parser event. Parser takes a JSONPath argument that defines a search to retrieve the value you want from the response data, like so:
The parser event itself will extract and pass along the value it retrieves using your expression, but the value will not be stored in the parser event (it is not a Container).
To store the value (to reference it elsewhere in your model), we recommend sending the output of the parser event to a container such as a Pipe or Pool. Keep in mind, these containers expect individual numbers as input, so your JSONPath expression should be extracting a single numeric value from the response data.
Keep Reading: Event References in Summit Event Language (SEL)