KPI rule language

KPI rules contain first-level nodes of name and instructions. The full rule language is map, filter, query, dimension, window, sendEventsWhenNoData, and reduce.

Example rule

 
{
    "name":"kpi-docVolume-kpi-rule",
    "instructions":{
        "dimension":[
            {
                "fieldPath":"eventDetails.transactionType",
                "type":"String"
            }
        ],
        "window":{
            "timeWindows":[
                "day"
            ],
            "fieldPath":"timestampEventOccurred"
        },
        "sendEventsWhenNoData":"yes",
        "reduce":{
            "expression":{
                "operator":"sum",
                "fieldPath":"eventDetails.docVolume",
                "type":"long"
            },
            "output":{
                "resultAlias":"documentVolume",
                "resultType":"long"
            }
        }
    }
}

Rule structure overview

 
{    
    "name":"my KPI rule",
    "instructions":{ // required
        "query": [ // optional
            {
                "basedObjectType": "",  
                "select": [ "field1", "field2", "field3", ... ]
                "from" : "",
                "where" : {
                    "equals": [
                        {
                            "fieldPath": ["field2","string"]
                        },
                        {
                            "valueFrom": ["field1","string"]
                        }
                    ]
                }
            }
        ],
        "map":[ // optional
            {
                "output":{
                    "resultFieldPath":"fieldx",
                    "resultType":"int"
                },
                "expression":{
                    "DIVIDE":[
                        {
                            "MULTIPLY":[
                                {
                                    "SELECT":["field1","int"]
                                },
                                {
                                    "VALUE":[2,"int"]
                                }
                            ]
                        },
                        {
                            "VALUE":[2,"int"]
                        }
                    ]
                }
            }
        ],
        "filter":{ //optional
            "OR":[
                {
                    "AND":[
                        {
                            "EQUALS":[
                                {
                                    "SELECT":["field1","int"]
                                },
                                {
                                    "SELECT":["field2","int"]
                                }
                            ]
                        },
                        {
                            "GREATERTHAN":[
                                {
                                    "SELECT":["field3","int"]
                                },
                                {
                                    "VALUE":[100,"int"]
                                }
                            ]
                        }
                    ]
                },
                {
                    "EQUALS":[
                        {
                            "SELECT":["field4","int"]
                        },
                        {
                            "SELECT":["field5","int"]
                        }
                    ]
                }
            ]
        },
        "dimension":[ // optional
            {
                "fieldPath":"", // all fields about "fieldPath" cannot contain "-".
                "type":""
            }
        ],
        "window":{ // required
            "timeWindows":["HOUR"],
            "fieldPath":"timestampEventOccurred"
        },
	"sendEventsWhenNoData":"yes", // optional
        "reduce":{ // required
            "expression":{
                "operator":"sum",
                "fieldPath":"",
                "type":""
            },
            "output":{
                "resultType":"",
                "resultAlias":""
            }
        }
    }
}

KPI rules contain first-level nodes of name and instructions. Both nodes are required.

Property Description
Name The name of the KPI rule.
Instructions Operations on the data.

Instructions consist of map, filter, query, dimension, window, sendEventsWhenNoData, and reduce.

Map (Optional)

A map is a list of operations that define how to calculate the specified field with given expression. The following example shows the scope:

"map": [
    // the first
    {
        // the output field and type
        "output": {  
            "resultFieldPath": "eventDetails.totalValue",
            "resultType": "double"
	       },
	      // how to calculate the output
	      "expression": {   
	          "multiply": [        
	              { 	
                          "select": [
                               "eventDetails.productLines.unitPrice", // field
                               "double"  // type
                          ]
                     },
	             {		
		          "select": [
		      		"eventDetails.productLines.quantity",
		      		"double"
		          ]
	       	     }
	      ]
         }
    },
    // the second
    {	
	...
    }
]

Every map consists of required output and an optional expression.

Root level Second level Description
Output resultFieldPath The field of the calculated result. This value between elements in map is unique.
  resultType The type of the calculated result in one of the following values [int, long, double, float].
Expression Nested The definition for how to calculate the output.

The expression supports the multilevel nest. The following example shows a valid expression:

{
   operator:[	// root level
      {
          // second level
          operator: [ operand, operand ]
      },
      {
          // second level
          operator: [ operand, operand ]
      }
   ]
}

The operator in the root level of the expression must be one of the following types:

MULTIPLY, DIVIDE, ADD, SUBTRACT, MOD, MIN, MAX, ABS, SQRT, ROUND

The number of operand depends on the concrete operator in the preceding list. For example, MULTIPLY must have two operands, such as a * b, while ABS only needs one operand. Using the wrong operand number creates an invalid KPI rule.

The operator in the second level can support the following types:

MULTIPLY, DIVIDE, ADD, SUBTRACT, MOD, MIN, MAX, ABS, SQRT, ROUND, SELECT, VALUE

If you use an extra operator, you can use SELECT and VALUE as the end-operator, which can be defined to mark the end of nesting of map, for example,

{
    "MULTIPLY":[
       {
      	   "SELECT":[ "field1", "int"]
       },
       {
      	   "VALUE":[ 2, "int"]
       }
    ]
}

The root-level operator is MULTIPLY, the first operand is "SELECT field1", which the operator is SELECT and the second operand is "set value of 2". So this part means "field1 * 2".

In the structure of end-operand, the operand is an array whose size must be 2. The first object is the field name of the event schema or the value of a number, such as "field1" or 2 in the previous example. The second object is the type of the value, which should be in the list of [int, long, double, float].

Filter (Optional)

The filter is a nested JSONObject that is used for filtering data with a given expression. It uses the following structure:

"filter": {		
    // root-level operator
    "or": [		
	{
	    // second-level operator, first operand of root-level
	    "greaterthan": [		
	        {
                   "select": ["eventDetails.totalValue","double"]
                },
	        {
		   "value": [300, "double"]
                }
            ]
	},
	{
	    // second-level operator, second operand of root-level
            "equals": [		
             {
                "select": ["eventDetails.owningOrganization", "string"]
             },
             {
                "value": ["ibm", "string"]
             }
        }
    ]
}

It operates like the expression. The root level operator must be in:

GREATER_THAN, GREATER_EQUALS, LESS_THAN, LESS_EQUALS, EQUALS,UNEQUALS, OR, AND

And the other level operator can match SELECT and VALUE. It is almost the same as an expression.

When the operator matches EQUALS, there must be one SELECT operand, for example,

// The following code snippet is not in the expected format
{		
    "EQUALS":[
        {
            "VALUE":[2,"int"]
        },
        {
            "VALUE":[2,"int"]
        }
    ]
}

// The following code snippet is in the expected format
{	
    "EQUALS":[
        {
            "SELECT":["field","int"]
        },
        {
            "VALUE":[2,"int"]
        }
    ]
}

// The following code snippet is in the expected format
{		
    "EQUALS":[
        {
            "SELECT":["field","int"]
        },
        {
            "SELECT":["field2","int"]
        }
    ]
}

Query (Optional)

In a KPI business rule, query implements the ability to use the result of that query as input into the KPI processing, in addition to the input events. For example, when you want a KPI for order lines, you probably also want information from the order. You can use query to select the field you want.
{
    "query":[
        {
            "basedObjectType":"order",
            "select":[
                "sourceId",
                "eventDetails.order",
                "eventDetails.product",
                "eventDetails.quantity",
                "eventDetails.productValue",
                "eventDetails.value"
            ],
            "from":"orderLine",
            "where":{
                "and":[
                    {
                        "equals":[
                            {
                                "fieldPath":[
                                    "eventDetails.order", // based event
                                    "string"
                                ]
                            },
                            {
                                "valueFrom":[
                                    "eventDetails.orderId", // from event
                                    "string"
                                ]
                            }
                        ]
                    },
                    {
                        "greaterthan":[
                            {
                                "fieldPath":[
                                    "eventDetails.quantity",// based event
                                    "integer"
                                ]
                            },
                            {
                                "value":[
                                    5,
                                    "integer"
                                ]
                            }
                        ]
                    }
                ]
            }
        }
    ]
}

Query structure

A query consists of the following required elements:

Root level Description
basedObjectType The based event that needs its field as condition to run the query.
select The fields that you want to query.
from The event to be queried.
where Query conditions, its usage is the same as filter
Note: The fieldPath means that its value comes from the target object, and there are two ways to get the condition value:
  • valueFrom , the condition value comes from the basedObjectType event.
  • value, the condition value is obtained directly.

For example, the where condition means that there two query conditions. The first is that the eventDetails.order in order event should be equal to the value of eventDetails.orderId in orderLine event, and the second condition is that the eventDetails.quantity should be greater than 5.

The other usage of the where condition is same as the filter expression.

Dimension (Optional)

"dimension": [
    {
      "fieldPath": "tenantId",
      "type": "String"
    },
    {
      "fieldPath": "sourceId",
      "type": "String"
    },
    {
      "fieldPath": "objectType",
      "type": "String"
    }
]

It is an array, where every element object contains a fieldName and a type, and both properties are required.

Property Description
fieldPath The name of the dimension field in the event.
type The type of value for the field in the conditionFields object element.

Window (Required)

"window": {
	"timeWindows": [
		"day",
		"month",
		"year"
	],
	"fieldPath": "timestampEventOccurred"
}

It mainly contains timeWindows and fieldPath. Both properties are required.

Property Description
timeWindows An array to describe aggregation granularity.
fieldPath The name of the dimension field in the event schema

The value of TimeWindows must be in the list of:

SECOND, MINUTE, HOUR, DAY, MONTH, YEAR

These values can be existent at the same time and split by using a comma (,), such as day,month.

SendEventsWhenNoData (Optional)

The sendEventsWhenNoData Boolean field governs whether to fire a window-event at the end of the window period none of the ingest data matches the rule within that window.

The default value for this field is false, and the lack of an event within that window means that the value is '0' for all dimensions.

The value of sendEventsWhenNoData must be in the list of:

yes, no

Reduce (Required)

"reduce": {
    "expression": {
        "operator": "sum",
        "fieldPath": "eventDetails.totalValue",
        "type": "double"
    },
    "output": {
        "resultAlias": "totalValue",
        "resultType": "double"
    }
}

The following properties are required for reduce:

Property Nest Description
expression operator The math operation
  fieldPath The name of field that will be calculated in event
  type The type of value for 'field'
output resultAlias The alias of calculated result
  resultType The type of calculated result

The operator must be in list of "COUNT", "COUNT_DISTINCT", "SUM".

The type must be in list of int, long, double, float. Also, the resultType must be the same as the type.

Data flow of event to ingest service

Diagram of data flow events
Original event
The KPI service retrieves event data from Kafka and then aggregates data from the dimension of time. The following example shows original data, which is produced in the upstream service:
{
    "eventCode":"objectUpsertEvent",
    "objectType":"com.ibm.infohub.businessEventModel.flowsummary",
    "infoHubObjectId":"15cfd5a6-9819-11e7-bc43-526af7764f69",
    "sourceId":"com.ibm.infoHub.kpiService",
    "tenantId":"3fed53d0-9817-11e9-bc42-526af7764f64",
    "timestampEventOccurred":"2016-01-01T13:40:16+0000",
    "timestampEventReceived":"2016-06-21T13:40:16+0000",
    "eventDetails":{
        "businessObject":{
            "id":"filter-mathOpt_1",
            "type":"Order",
            "seller":{
                "name":"filter-mathOpt-seller0"
            },
            "buyer":{
                "name":"LENOVO_1"
            },
            "owningOrganization":{
                "name":"1111:RETAIL"
            },
            "billToOrganization":{
                "name":"AIG-9"
            },
            "orderReferenceId":"100013204",
            "lineCount":2,
            "createdDate":"2019-09-27T13:40:16+0000",
            "requestedShipDate":"2019-09-27T13:40:16+0000",
            "requestedDeliveryDate":"2019-09-27T13:40:16+0000",
            "plannedShipDate":"2019-09-27T13:40:16+0000",
            "plannedDeliveryDate":"2019-09-27T13:40:16+0000",
            "lastModifiedDate":"2019-09-27T13:40:16+0000",
            "quantity":-6,
            "quantityUnits":"ea",
            "unitPrice":20.19,
            "unitPriceCurrency":"USD",
            "orderValue":-120.14,
            "orderValueCurrency":"USD",
            "totalAmount":-91.46,
            "totalAmountCurrency":"USD",
            "orderLine":[
                {
                    "id":"ABCD-1",
                    "order":{
                        "id":"filter-mathOpt_1"
                    },
                    "product":{
                        "description":"HMC CR1"
                    },
                    "quantity":1,
                    "quantityUnits":"ea",
                    "productValue":111.1,
                    "value":110,
                    "valueCurrency":"USD",
                    "orderLineNumber":"100",
                    "shipmentCount":1,
                    "createdDate":"2019-09-27T13:40:16+0000",
                    "requestedShipDate":"2019-09-27T13:40:16+0000",
                    "requestedDeliveryDate":"2019-09-27T13:40:16+0000",
                    "plannedShipDate":"2019-09-27T13:40:16+0000",
                    "plannedDeliveryDate":"2019-09-27T13:40:16+0000",
                    "lastModifiedDate":"2019-09-27T13:40:16+0000"
                },
                {
                    "id":"ABCD-2",
                    "order":{
                        "id":"filter-mathOpt_1"
                    },
                    "product":{
                        "description":"HMC CR2"
                    },
                    "quantity":5,
                    "quantityUnits":"ea",
                    "productValue":20.1,
                    "value":100,
                    "valueCurrency":"USD",
                    "orderLineNumber":"100",
                    "shipmentCount":5,
                    "createdDate":"2019-09-27T13:40:16+0000",
                    "requestedShipDate":"2019-09-27T13:40:16+0000",
                    "requestedDeliveryDate":"2019-09-27T13:40:16+0000",
                    "plannedShipDate":"2019-09-27T13:40:16+0000",
                    "plannedDeliveryDate":"2019-09-27T13:40:16+0000",
                    "lastModifiedDate":"2019-09-27T13:40:16+0000"
                }
            ]
        }
    }
}
Delta event
The KPI service makes a preliminary aggregation under the KPI rule. If the preceding KPI rule defines the time window of day, month, year, then the KPI service generates three level events that correspond to the three time levels. The following example shows a simple delta event with day-level:
{
    "eventCode":"businessEvent",
    "objectType":"com.ibm.infohub.kpi.service.deltaEvent",
    "sourceId":"com.ibm.infoHub.kpiservice",
    "tenantId":"3fed53d0-9817-11e9-bc42-526af7764f64",
    "timestampEventOccurred":"2019-06-26T13:40:16+0000:00",
    "timestampEventReceived":"2019-08-21 09:33:00",
    "eventDetails":{
        "rule":{
            "id":"kpi-docVolume-kpi-rule",
            "name":"kpi-docVolume-kpi-rule"
        },
        "dimensionValues":[
            {
                "name":"entityType",
                "value":"document"
            },
            {
                "name":"dateKey",
                "value":"2019-06-26"
            }
        ],
        "values":[
            {
		"name":"documentVolume",
              	"value":"27"
            }
        ]
    }
}
Window event
The preceding delta event can be aggregated to generate a longer period event, which is called a window event. The window events are recorded in ES according to different dimension keys. If sendDataWhenNoData=yes is set in the KPI rule, then these window events can be queried according to the window type of this KPI rule and then sent out.
  • For hour window type, it runs immediately as the service started. And it queries the per-hour window results then sends to Kafka.
  • For day window type, it will run after an initial delay (to 01:00:00). And then it will run in a one-day cycle.
  • For month and year window type, it runs the same way as the day window type. However, it will only do the real task when it is the first day of that per-month. Or the first day of that per year.
Note: Currently, if it has one tenantId dimension and the sendEventsWhenNoData=yes, then it sends the empty events for these tenants.
  • If the current KPI rule does not specify the tenant, then it sends empty events for all tenants.
  • If the current KPI rule specifies the tenants, then it sends only empty events for these tenants. The following example shows the demo empty window event:
{
    "eventCode":"businessEvent",
    "objectType":"com.ibm.infohub.kpi.service.windowEvent",
    "sourceId":"com.ibm.infoHub.kpiService",
    "tenantId":"4a87c4b2-b38a-4270-85cf-1ea6fb7e9d63",
    "timestampEventOccurred":"2019-10-18T13:00:00",
    "timestampEventReceived":"2019-10-18T14:24:50",
    "eventDetails":{
        "rule":{
            "id":"a67b015e-106c-4336-b30d-eba888888",
            "name":"kpi-count-test-kpi-rule"
        },
        "dimensionValues":[
            {
                "name":"hour",
                "value":"2019-10-18T13"
            }
        ],
        "values":[
            {
                "name":"quantityValue",
                "value":0
            }
        ]
    }
}