Recursive Services for Metric Roll-ups

    This is a useful trick for rolling up metrics in Thingworx across various levels of a hierarchy by using Networks, ThingShapes, and recursive service definitions.

     

    Say that you have a hierachy of Things in your model such as a Global view, a Region view, and a Store view -- this could be a Mfg Plant, a Building, an Asset, etc; whatever the core metric producing Thing is in your model -- where your Store has KPIs that you want to roll up across regions and globally.

     

    First, create a template for each of your hierarchical levels. In my case it is a GlobalTemplate, RegionalTemplate, and StoreTemplate. Add a property to your StoreTemplate that will be the KPI.

     

    Now, create a Thing for the Globe, and each of your Regions and Stores. Add them to a hierachical Network as such:

     

     

     

    Now, we need to create a ThingShape to aggregate our KPIs and apply it to the Global, Regional, and Store template.

     

    Now we will define a recursive funciton on our ThingShape called GetKPI and define it with the following:

     

     

    //define our base case, when the thing template we are on is the lowest level of our hierarchy, in this case the StoreTemplate
    if (me.thingTemplate == "StoreTemplate") {
        //in our base case, the result is just the property for the metric we want to aggregate 
        result = me.someMetric
    } else {
        //otherwise, we are at some other level in the hierarchy and we need to get our child connections from the network
        //this gets all the things below us in the network
        var params = {
            name: me.name /* STRING */
        };
    
    
        // result: INFOTABLE dataShape: NetworkConnection
        var network = Networks["Network"].GetChildConnections(params);
    
    
        //loop through each of the things below us in the hierarchy and recursively add the result of GetKPI() to our result
        result = 0;
        for each (var row in network.rows) {
         
            result += Things[row.to].GetKPI();
        }
    
    
    }
    

     

    This is a simple case of just summing up a single property, but we can take this further using the Union and Aggregate snippets provided by thingworx to do other kinds of summarization. First add a new property called someAvgMetric to our StoreTemplate, and define a new service GetKPIProperties as such, with an InfoTable result, on the StoreTemplate

    varparams = {
    
        propertyNames: {"items": ["someMetric", "someAvgMetric"]} /* JSON */
    };
    
    // result: INFOTABLE dataShape: "undefined"
    var result = me.GetNamedProperties(params);
    

     

     

    Now, define a new service on our ThingShape to utilize this service as our base case, and aggregate the resulting InfoTable when necessary. We'll call this service GetKPIAggregates:

    //define our base case
    if (me.thingTemplate == "StoreTemplate") {
        //this function will be on the StoreTemplate, and returns the base infotable
        result = me.GetKPIProperties()
    } else {
        //grab our network
        var params = {
            name: me.name /* STRING */
        };
    
    
        // result: INFOTABLE dataShape: NetworkConnection
        var network = Networks["Network"].GetChildConnections(params);
    
    
        //need to create an empty infotable to union into. I glossed over this, but you'll need a datashape here
        //create empty infotable
        var result = Resources["InfoTableFunctions"].CreateInfoTableFromDataShape({ infoTableName: "InfoTable", dataShapeName: "KPIDataShape" });
    
    
        //loop through and union each of our results to our new infotable
        for each (var row in network.rows) {
            var params = {
                t1: result /* INFOTABLE */,
                t2: Things[row.to].GetKPIAggregates() /* INFOTABLE */
            };
            var result = Resources["InfoTableFunctions"].Union(params);
    
    
        }
    
    
        //aggregate each of our fields
        var params = {
            t: result /* INFOTABLE */,
            columns: "someMetric,someAvgMetric" /* STRING */,
            aggregates: "SUM,AVERAGE" /* STRING */,
            groupByColumns: undefined /* STRING */
        };
    
    
        // result: INFOTABLE
        var result = Resources["InfoTableFunctions"].Aggregate(params);
    
    
        //need to loop through each of our field names and make them match our base infotable
        // infotable datashape iteration
        var dataShapeFields = result.dataShape.fields;
        for (var fieldName in dataShapeFields) {
            var stringName = dataShapeFields[fieldName].name;
    
    
            var params = {
                t: result /* INFOTABLE */,
                from: stringName /* STRING */,
                to: stringName.split("_")[1] /* STRING */
            };
    
    
            // result: INFOTABLE
            var result = Resources["InfoTableFunctions"].RenameField(params);
    
    
        }
    
    
      
    
    
    }
    

     

     

    Now, in our mashups, we can use a DynamicThingShape and call our GetKPIs service at any level in our network, and our data will be aggregated correctly for whatever level we are at in the hierarchy!