Automating Pricing (rating) & Mgmt.

At the core of our Billing Cloud, built from the ground up to tackle the demands of real-time usage-based pricing and billing, is a highly flexible and scalable Pricing Machine State Engine (a.k.a rating engine).

We describe below some of the artifacts and how the pricing machine works.
This background will help you understand the unique and powerful capabilities and flexibility of the pricing engine and, we hope, will unleash your imagination in building and deploying the pricing models that best suit your products and customer base.

It provides the tools needed to create any pricing plan that you have in mind.

How the price machine works

Before delving into the different types of price machine nodes, we will provide a high-level description for how the price calculation works.


Amberflo invoice calculator is the logic which converts a specific meter's hourly usage aggregation for a given customer to an invoice.

658658

The exact input which the invoice calculator consumes is the meter hourly usage aggregation of a given customer partitioned by all of the official dimensions of the invoice.

Then the price calculator executes the following steps:
Step 1: Fetch the relevant price machine for the customer and the meter.
Step 2: Reduce the input to a smaller set of partitions as defined by the specific price machine.
Step 3: Execute the price machine on the reduce input.

The exact output of the price machine is set of what call an 'Item-Variant-Invoice'. An 'Item-Variant-Invoice' is a partition of the invoice by a sub-set of the meter official dimensions.


Given these we will define each price machine using two properties:

  1. Required dimensions
  2. Function

Example

Let's assume you have the following meter:

{
    "meterApiName": "api-calls",
    "dimensions": [ "region", "is-urgent-request" ],
    "meterType": "sum"
}

Now, let's assume your machine has the following definitions:

  1. Required dimensions: "region"
  2. Function: 2 usage = 1 usd

Last, let's assume we have the following meter-aggregation input:

[
    { "group": { "region": "US", "is-urgent-request": "true" }, "groupValue": 10 },
    { "group": { "region": "US", "is-urgent-request": "false" }, "groupValue": 67 },
    { "group": { "region": "CA", "is-urgent-request": "true" }, "groupValue": 3 },
    { "group": { "region": "CA", "is-urgent-request": "false" }, "groupValue": 14 }
]

Then the price machine will create the following 'Item-Variant-Invoice':

[
    { "variant": { "region": "US" }, "price": 33.5 }, // (10 + 67)/2
    { "variant": { "region": "CA" }, "price": 8.5 } // (14 + 3)/2
]

The different types of price machines

Now that we know something about how Amberflo's price machine works, we will introduce the different types of machines we offer.


Price Leaf Node

The leaf price machine is the simplest price machine one can think of, and yet as we will see next it by itself quite an advance price machine.

The properties of this machine are:

  1. Required dimensions: none
  2. Function: tier based price step function.

Bellow is the 'leaf price machine' schema:

type: "object"
description: "A simple leaf model to calc the price of a single meter usage."
properties:
  allowPartialBatch:
    type: "boolean"
    description: "Default to false. If true the LeafNode machine won't round up the usage to the next whole unit of
    batch, but instead calc the price per the exact usage consumed."
  tiers:
    type: "array"
    description: "This array represents the price step function of the given meter. The price is calculated using the
    price tiers as described below, where each tier starts at startAfterUnit (included) and ends at the
    startAfterUnit of the next tier (excluded)."
    items:
      type: "object"
      properties:
        batchSize:
          type: "integer"
          description: "Defines how much usage is included in a single batch. When calculating the price the Leaf-Node
          will round up the usage to next whole unit of batch and calc the price for the total amount of batches."
        pricePerBatch:
          type: "number"
          description: "This is your price for a single batch of usage in terms of the base currency unit of the
          account (usd in most cases)."
        startAfterUnit:
          type: "integer"
      required:
        - startAfterUnit
        - batchSize
        - pricePerBatch
  type:
    type: "string"
    default: "LeafNode"
required:
  - type
  - tiers

Unless you want to partition the invoice by a subset of the meter dimensions then this machine is likely all you need.


Let's look at a few examples of possible configuration for this machine.

Example 1.1:

A machine that calculates a price of $0.10 per 1 unit of usage.

For example the price of 12 units of usage is $1.20.

{
    "type": "LeafNode",
    "tiers": [
        {
            "startAfterUnit": 0,
            "batchSize": 1,
            "pricePerBatch": 0.1
        }
    ],
    "allowPartialBatch": true
}

Example 1.2:

A machine that calculates a price of $0.50 per 5 units of usage with no partial batching.

Unlike the previous example this machine doesn't allow partial batches, which means, the price usage will be rounded up to whole unit of the batch size, and the price will be determined based on the amount of batches.

For example, the price of 12 units of usage is $1.50 (3 batches).

{
    "type": "LeafNode",
    "tiers": [
        {
            "startAfterUnit": 0,
            "batchSize": 5,
            "pricePerBatch": 0.5
        }
    ],
    "allowPartialBatch": false
}

Example 1.3:

A machine that calculates a price of $0.10 per each 1 unit of usage for the first 10 units of usage, and then drop the price to $0.05 for each unit afterwards.

For example, the price of 12 units of usage is $1.10 (0.1 10 + 0.05 2).

{
    "type": "LeafNode",
    "tiers": [
        {
            "startAfterUnit": 0,
            "batchSize": 1,
            "pricePerBatch": 0.1
        },
        {
            "startAfterUnit": 10,
            "batchSize": 1,
            "pricePerBatch": 0.05
        }
    ],
    "allowPartialBatch": false
}

Example 1.4:

A machine that calculates a price of $0 per each 1 unit of usage for the first 10 units of usage, and then increases the price to $0.05 for each unit thereafter.

For example, the price of 12 units of usage is $0.1 (0 10 + 0.05 2).

{
    "type": "LeafNode",
    "tiers": [
        {
            "startAfterUnit": 10,
            "batchSize": 1,
            "pricePerBatch": 0.05
        }
    ],
    "allowPartialBatch": false
}

Dimension Matrix Node

This type of machine allows you to create a different price logic for different combinations of dimensions values.

Basically you define a Price-Leaf-Node for each combination of dimensions values.
Notice that this price machine will drop any usage for which the dimension values are not defined in the price machine (we will share an example for this below).

The properties of this machine are:

  1. Required dimensions: as defined in the 'dimensionKeys' property.
  2. Function: A sum of a tier based price step functions.

---
type: "object"
description: "The DimensionMatrixNode allows us to define different meters for different combinations of dimensions
values. For example let's assume you want to bill your customers by the amount storage used over time. Also let's assume
you have two types of storage: hard-disk and in-memory, and you want to have a different price for each type of storage.
Then add the storage-type as a dimension of your meters, and have a different Price-LeafNode for each sort of storage."
properties:
  dimensionKeys:
    type: "array"
    description: "A list of dimensions which affect the price."
    items:
      type: "object"
  dimensionsPrices:
    type: "array"
    items:
      type: "object"
      properties:
        dimensionValues:
          type: "array"
          description: "A values - one per each of the dimension keys. The DimensionMatrixNode will partition the given
          usage by this dimensions (and drop any usage which doesn't have a corresponding dimensions values defined in
          this model). Then the DimensionMatrixNode will calculate the price of each partitioned usage "
          items:
            type: "object"
        leafNode:
          type: "object"
          description: "A simple leaf model which calc the price of a single meter usage."
          properties:
            allowPartialBatch:
              type: "boolean"
            tiers:
              type: "array"
              items:
                type: "object"
                properties:
                  batchSize:
                    type: "integer"
                  pricePerBatch:
                    type: "number"
                  startAfterUnit:
                    type: "integer"
                required:
                  - startAfterUnit
                  - batchSize
                  - pricePerBatch
            type:
              type: "string"
              default: "LeafNode"
          required:
            - type
            - tiers
  type:
    type: "string"
    default: "DimensionMatrixNode"
required:
  - type
  - dimensionsPrices
  - dimensionKeys

Example 2:

The machine below defines a price according to the following pricing plan:

Region

Memory

Function

us-west-1

1Gb

0.001$ per 1 batch of 1 unit

us-west-1

2Gb

0.002$ per 1 batch of 1 unit

us-west-1

4Gb

0.002$ per 1 batch of 1 unit

us-east-2

1Gb

0.0015$ per 1 batch of 1 unit

us-east-2

2Gb

0.003$ per 1 batch of 1 unit

us-east-2

4Gb

0.0045$ per 1 batch of 1 unit


Equivalent price machine:

{
    "type": "DimensionMatrixNode",
    "dimensionKeys": [
        "Region",
        "Memory"
    ],
    "dimensionsPrices": [
        {
            "dimensionValues": [
                "us-west-1",
                "1Gb"
            ],
            "leafNode": {
                "type": "PricePerUnitLeafNode",
                "tiers": [
                    {
                        "startAfterUnit": 0,
                        "batchSize": 1,
                        "pricePerBatch": 0.001000000
                    }
                ],
                "allowPartialBatch": false
            }
        },
        {
            "dimensionValues": [
                "us-west-1",
                "2Gb"
            ],
            "leafNode": {
                "type": "PricePerUnitLeafNode",
                "tiers": [
                    {
                        "startAfterUnit": 0,
                        "batchSize": 1,
                        "pricePerBatch": 0.002000000
                    }
                ],
                "allowPartialBatch": false
            }
        },
        {
            "dimensionValues": [
                "us-west-1",
                "4Gb"
            ],
            "leafNode": {
                "type": "PricePerUnitLeafNode",
                "tiers": [
                    {
                        "startAfterUnit": 0,
                        "batchSize": 1,
                        "pricePerBatch": 0.002000000
                    }
                ],
                "allowPartialBatch": false
            }
        },
        {
            "dimensionValues": [
                "us-east-2",
                "1Gb"
            ],
            "leafNode": {
                "type": "PricePerUnitLeafNode",
                "tiers": [
                    {
                        "startAfterUnit": 0,
                        "batchSize": 1,
                        "pricePerBatch": 0.001500000
                    }
                ],
                "allowPartialBatch": false
            }
        },
        {
            "dimensionValues": [
                "us-east-2",
                "2Gb"
            ],
            "leafNode": {
                "type": "PricePerUnitLeafNode",
                "tiers": [
                    {
                        "startAfterUnit": 0,
                        "batchSize": 1,
                        "pricePerBatch": 0.003000000
                    }
                ],
                "allowPartialBatch": false
            }
        },
        {
            "dimensionValues": [
                "us-east-2",
                "4Gb"
            ],
            "leafNode": {
                "type": "PricePerUnitLeafNode",
                "tiers": [
                    {
                        "startAfterUnit": 0,
                        "batchSize": 1,
                        "pricePerBatch": 0.004500000
                    }
                ],
                "allowPartialBatch": false
            }
        }
    ]
}

Distinct Resource Reducer

This machine counts the distinct values for a subset of dimensions and then calculates a price using a Price-Leaf-Node for the count.

The distinct values count function can be applied on:

  1. Each individual hour.
  2. Each day.
  3. For the entire invoice period.

The properties of this machine are:

  1. Required dimensions: as defined in the 'resourceDefiningDimensions' property.
  2. Function: A sum of a tier based price step functions which is applied on the result of the distinct count.
---
type: "object"
description: "Use this price model if you want to to charge you customer based on a distinct count of some set of
dimensions count. For example, let's assume you have a dimension called job-id, and you want to invoice your customers
based on the amount of jobs they ran, then you can use this price model to accomplish that."
properties:
  granularity:
    type: "string"
    description: "HOURLY, DAILY, ENTIRE_INVOICE_PERIOD"
  nextNode:
    type: "object"
    description: "A simple leaf model which calc the price of a single meter usage."
    properties:
      allowPartialBatch:
        type: "boolean"
      tiers:
        type: "array"
        items:
          type: "object"
          properties:
            batchSize:
              type: "integer"
            pricePerBatch:
              type: "number"
            startAfterUnit:
              type: "integer"
          required:
            - startAfterUnit
            - batchSize
            - pricePerBatch
      type:
        type: "string"
        default: "LeafNode"
    required:
      - type
      - tiers
  resourceDefiningDimensions:
    type: "array"
    description: "The dimension names to use for the distinct count."
    items:
      type: "object"
  type:
    type: "string"
    default: "distinct_resource_reducer"
required:
  - type
  - granularity
  - nextNode
  - resourceDefiningDimensions

Example 3:

Let's assume your company has a pool of machines and provides a service which allows your customers to use your machines to run all kind of tasks on-demand.

Let's also assume that you measure how long each task runs, but for simplicity you want to invoice customers by the distinct number of tasks that they run during the invoice period. In such a case you can define the following price machine:

{
    "type": "distinct_resource_reducer",
    "resourceDefiningDimensions": [
        "Country"
    ],
    "granularity": "ENTIRE_INVOICE_PERIOD",
    "nextNode": {
        "usageVariationsByTimeMap": null,
        "dimensions": ["job-id"],
        "type": "LeafNode",
        "tiers": [
            {
                "startAfterUnit": 0,
                "batchSize": 5,
                "pricePerBatch": 2
            }
        ],
        "allowPartialBatch": false
    }
}

This machine will have a distinct count for all of the jobs that ran during the invoice-period (regardless of how long they ran or if the runtime was spread over multiple hours or days), and charge the customer $2 per each batch of 5 jobs.


Max Reducer

Similarly to the 'Distinct-Resource-Reducer' this price machine node can also reduce the time granularity of your hourly usage. This price machine finds the max value to pick the value of each usage partition.

This model allows you to charge your customers based on the peak hourly usage over:

  1. Each day.
  2. The entire invoice period.

The properties of this machine are:

  1. Required dimensions: Inherit the 'Required dimensions' of the next node.
  2. Function: apply the next node function over the hourly, daily or entire_invoice max usage.

NOTICE: you can plug this price-machine-node to any of the price machines described above.


---
type: "object"
description: "Hours is the most granular time unit in Amberflo. Amberflo calculates hourly aggregation of each of your
meter, where the aggregations are partitioned according to the meter's official dimensions values. In other words
all of the price machines takes hourly meter aggregation partitioned by the dimension values.
You can use this node if you want to have your price model based on time unit which is less granular than a single hour.
For example if you want to have your price model based on the daily max value of any hourly aggregation of the same day,
you can use this model to reduce the time granularity of the aggregations."
properties:
  granularity:
    type: "string"
    description: "HOURLY, DAILY, ENTIRE_INVOICE_PERIOD"
  nextNode:
    type: "object"
    description: "Next node can be any sort price node."
  type:
    type: "string"
    default: "max_reducer"
required:
  - granularity
  - nextNode
  - type

Example 4.1:

Let's use the previously mentioned example and assume your company has a pool of machines and provides a service that allows your customers to use your machines to run all kind of tasks.

But this time, let's assume you want to charge your customers according to the max hourly usage rate they incurred during the invoice period.

You can create a map_reducer similar to that described below.

{
    "type": "max_reducer",
    "granularity": "entire_invoice_period",
    "nextNode": {
        "type": "LeafNode",
        "tiers": [
            {
                "startAfterUnit": 0,
                "batchSize": 5,
                "pricePerBatch": 40
            }
        ],
        "allowPartialBatch": false
    }
}

Example 4.2:

Let's assume we take Example 2, mentioned above (for the dimensions matrix), but this time we want to calculate based on the max daily value.

As mentioned, we can have any node as the next node of the max_reducer price machine. Therefore we can just wrap the aforementioned 'DimensionMatrixNode' inside of a max_reducer.

{
    "type": "max_reducer",
    "granularity": "daily",
    "nextNode": {
        "type": "DimensionMatrixNode",
        "dimensionKeys": [
            "Region",
            "Memory"
        ],
        "dimensionsPrices": [
            {
                "dimensionValues": [
                    "us-west-1",
                    "1Gb"
                ],
                "leafNode": {
                    "type": "PricePerUnitLeafNode",
                    "tiers": [
                        {
                            "startAfterUnit": 0,
                            "batchSize": 1,
                            "pricePerBatch": 0.001000000
                        }
                    ],
                    "allowPartialBatch": false
                }
            },
            {
                "dimensionValues": [
                    "us-west-1",
                    "2Gb"
                ],
                "leafNode": {
                    "type": "PricePerUnitLeafNode",
                    "tiers": [
                        {
                            "startAfterUnit": 0,
                            "batchSize": 1,
                            "pricePerBatch": 0.002000000
                        }
                    ],
                    "allowPartialBatch": false
                }
            },
            {
                "dimensionValues": [
                    "us-west-1",
                    "4Gb"
                ],
                "leafNode": {
                    "type": "PricePerUnitLeafNode",
                    "tiers": [
                        {
                            "startAfterUnit": 0,
                            "batchSize": 1,
                            "pricePerBatch": 0.002000000
                        }
                    ],
                    "allowPartialBatch": false
                }
            },
            {
                "dimensionValues": [
                    "us-east-2",
                    "1Gb"
                ],
                "leafNode": {
                    "type": "PricePerUnitLeafNode",
                    "tiers": [
                        {
                            "startAfterUnit": 0,
                            "batchSize": 1,
                            "pricePerBatch": 0.001500000
                        }
                    ],
                    "allowPartialBatch": false
                }
            },
            {
                "dimensionValues": [
                    "us-east-2",
                    "2Gb"
                ],
                "leafNode": {
                    "type": "PricePerUnitLeafNode",
                    "tiers": [
                        {
                            "startAfterUnit": 0,
                            "batchSize": 1,
                            "pricePerBatch": 0.003000000
                        }
                    ],
                    "allowPartialBatch": false
                }
            },
            {
                "dimensionValues": [
                    "us-east-2",
                    "4Gb"
                ],
                "leafNode": {
                    "type": "PricePerUnitLeafNode",
                    "tiers": [
                        {
                            "startAfterUnit": 0,
                            "batchSize": 1,
                            "pricePerBatch": 0.004500000
                        }
                    ],
                    "allowPartialBatch": false
                }
            }
        ]
    }
}

Avg Usage Reducer

This node has a similar logic to the "Max Reducer" described above with two main differences:

  1. It calculated the avg usage instead of a max - although notice that similarly to the "Max Reducer" we can split the usage into hours (hourly avg), days (an avg value for each day of the invoice), or calc one avg value for the entire invoice period.
  2. The avg is being calculated over the ORIGINAL time period of invoice.

Regarding #2:
Avg is calculated using the following formula:

Avg of usage from time t1 to time t2 =  [Usage during t1 to t2]  / [t2 - t1]

Now, let's look at the following example and ask ourselves what is the denominator, meaning what are the t2, and t1 values.

  1. Let’s assume today is 7/22/2022.
  2. We started an invoice at 7/15/2022.
  3. The end time of the invoice is 8/15/2022

To make things simple let's assume our avg price state machine granularity is 'entire_invoice_period':


So, at 7/22 we have two options for how to calc the denominator:
Option 1 - from 7/15/2022 to 7/22/2022, using current usage
Option 2 - from 7/15/2022 to 8/15/2022, using current usage


When we said that the avg is being calculated over the ORIGINAL time period of invoice we meant is that we follow option 2. Option 2, has some big advantages over option 1 among them are:

  1. Using option 2 the price only goes up and can't really fluctuate.
  2. Option 2 is a more fair pricing mechanism for scenarios where a customer changes it plan or offboards from a service.
---
type: "object"
description: "Hours is the most granular time unit in Amberflo. Amberflo calculates hourly aggregation of each of your
meter, where the aggregations are partitioned according to the meters official dimensions values. In other words
all of the price machines takes hourly meter aggregation partitioned by the dimension values.
You can use this node if you want to have your price model based on time unit which is less granular than a single hour.
For example if you want to have your price model based on the daily avg value of any hourly aggregation of the same day,
you can use this model to reduce the time granularity of the aggregations."
properties:
  granularity:
    type: "string"
    description: "HOURLY, DAILY, ENTIRE_INVOICE_PERIOD"
  nextNode:
    type: "object"
    description: "Next node can be any sort price node."
  type:
    type: "string"
    default: "average_reducer"
required:
  - granularity
  - nextNode
  - type

Resource Groups Reducer

The main reason to use this node is if you want to partition your usage by a certain set of dimension
values which is not included in the next nodes, without specifying a unique leaf-price machine as done in the dimensions-matrix-node.

For example, let's assume you operate on many regions but you have the same pricing logic for all regions. Having said that, let's assume that when generating the invoice you want to calculate the price-per-region using the same leaf-node. You can use this method to have such logic.

The properties of this machine are:

  1. Required dimensions: A unified set as defined in 'resourceDefiningDimensions' + the 'Required dimensions' of the next node.
  2. Function: apply the next node function over the partitioned usage.

---
type: "object"
description: "The main reason to use this node is if you want to partition your usage by a certain set of dimension
values which isn't included in the next nodes. For example, let's assume you operate on many regions but you have the
same pricing logic for all regions. Having said that let's assume when coming up with the invoice you want to
calculate the price per region using the same leaf-node (let's say). You can use this method to have such logic.
"
properties:
  aggregationType:
    type: "string"
    description: "SUM, MAX"
  nextNode:
    type: "object"
    description: "Use any price machine us next node."
  resourceDefiningDimensions:
    type: "array"
    description: "The partitioning dimensions."
    items:
      type: "object"
  type:
    type: "string"
    default: "resource_groups_reducer"
required:
  - aggregationType
  - nextNode
  - resourceDefiningDimensions
  - type

Example 5:

We can implement the example mentioned above (partition by region but keep the same price logic for all regions) using a configuration similar to the one below:

{
    "type": "resource_groups_reducer",
    "resourceDefiningDimensions": [
        "Region"
    ],
    "nextNode": {
        "type": "LeafNode",
        "tiers": [
            {
                "startAfterUnit": 0,
                "batchSize": 5,
                "pricePerBatch": 0.1
            }
        ],
        "allowPartialBatch": false
    },
    "aggregationType": "sum"
}

Endpoints

Two ways to define a price machine:

  1. Via Amberflo's console - Amberflo's console contains a wizard that allows you to define most of the models mentioned above. It's an easy and safe way to do it.
  2. If the UI doesn't support the exact configuration you want to have then you can always use our APIs. The URL is: https://app.amberflo.io/payments/pricing/amberflo/account-pricing/product-item-price (just notice that the price-machine is one attribute of the entire item-price object - example)