Metric referencing actionDecl-based metric returns empty timeseries


#1

Hi,

I have a type Service and a type RatePlan. Service references one RatePlan through the field ratePlan.

I have an actionDecl SimpleMetric defined on RatePlan:

SimpleMetric.make({
    name: "RatePlan_ToTimeseries",
    id: "RatePlan_ToTimeseries",
    srcType: "RatePlan", 
    actionDecl: {
        action: "toTimeseries",
        include: "chargePlan.id" // because RatePlan.chargePlan.id is needed in toTimeseries()
    }
});

If I execute RatePlan.evalMetrics on the object RATEPLAN_1, I get the expected results (the output of RatePlan.toTimeseries()).

Now when I declare the following metric on Service (to access the RatePlan_ToTimeseries metric through the Service type):

SimpleMetric.make({
    name: "Service_ToTimeseries",
    id: "Service_ToTimeseries",
    srcType: "Service",
    path: "ratePlan",
    expression: "RatePlan_ToTimeseries",
});

I get an empty Timeseries (100% missing) when I evaluate it on the Service that has ratePlan.id = ‘RATEPLAN_1’.
I suspect that service.ratePlan.chargePlan.id might not be loaded but I can’t find how to specify an include in a non-actionDecl metric.

I have tried many things but the Service metric is always empty. Is referencing another actionDecl metric supported? What’s the recommended pattern in that case?
My current workaround is to define another actionDecl-based SimpleMetric on Service such that the action function explicitly calls evalMetric on the Service.ratePlan object, but that’s cumbersome and defeats basic DRY principles.

Thanks for your advices!


#2

I suspect the issue is that you are trying to invoke a SimpleMetric from another SimpleMetric. If you want to evaluate a SimpleMetric on multiple source types, you will have to define it multiple times—so you will need to define a RatePlan_ToTimeseries metric for both RatePlan and Service (both of which will be actionDecl metrics).

{
    name: "RatePlan_ToTimeseries",
    id: "RatePlan_ToTimeseries_RatePlan",
    srcType: "RatePlan", 
    actionDecl: {
        action: "toTimeseries",
        include: "chargePlan.id"
    }
},
{
    name: "RatePlan_ToTimeseries",
    id: "RatePlan_ToTimeseries_Service",
    srcType: "Service", 
    path: "ratePlan",
    actionDecl: {
        action: "toTimeseries",
        include: "chargePlan.id"
    }
}

EDIT: See @rohit.sureka’s response below about the fact that path does not work with actionDecl. So in the above example, Service would have to have an implementation of toTimeseries as well.


#3

I have tried this but it does not work as “toTimeseries” is a function of RatePlan and not Service:

c3.love.exceptions.C3RuntimeException: function metadata was not found for action Target [tenant/tag/Service?action=toTimeseries]

It seems that the “action” field of actionDecl must point to a method of the srcType.


#4

Ah ok then I suppose you will have to implement toTimeseries on Service as well (and modify the include appropriately—ratePlan.chargePlan.id).


#5

That is what I am currently doing but it’s precisely what I try to avoid.
Imagine for instance than instead of trying to call ratePlan.toTimeseries I wanted to do ratePlan1.toTimeseries + ratePlan2.toTimeseries, it’s a little silly to implement that using 2 redundant evalMetrics and then manually add them…


#6

@matt - currently path does not have any effect on actionDecl metrics. All you can define is an include in the actionDecl and everything else needs to be handled by the developer


#7

@lerela - the function toTimeseries seems like you are trying to convert some objects toTimeseries? Have you considered using:

  1. tsDecl - its essentially converting arbitrary objs to time series
  2. metric function library function - this is essentially adding user defined functions in the engine. Benefit of this approach is that you can re-use it across types rather than defining actionDecl based metrics per source

#8

fyi - for using MetricFunctionLibrary, below is an example,

type MyFunctionLibrary mixes MetricFunctionLibrary {
  yourFunction: function(your parameters): Timeseries js server
}

But looks like tsDecl is the more proper solution if you just want to convert objs to timeseries


#9

tsDecl only works if my set of objects is in database, but here I am generating the time slots (start/end/value) on the fly (as I want to generate a periodic timeseries from spec.start to spec.end). My actionDecl generates the time slots and then calls Timeseries.fromObjs to turn them into a timeseries.

A metric function would need to be parametrized with the type (here, RatePlan) to fetch the data used to generate the time slots on the fly, so I’m not sure it’s really an option…


#10

you can pass the input needed in the metric function library function as a separate metric. E.g.

type MyFunctionLibrary mixes MetricFunctionLibrary {
yourFunction: function(sourceSpecificArg: Timeseries, x: int, y: int): Timeseries js server
}

now sourceSpecificArg can be a separate metric per source getting the data that you care about. This can also be a Timeseries containing complex objects


#11

Custom metric functions seem powerful, yet I’ve not been able to find out:

  • How to access the metric eval spec within the function (I could try to pass it as function argument but it does not make much sense) to get start, end & interval,
  • How to access the current object within the function (for instance, the ExpressionEngineFunction id() called without argument returns the id of the object pointed by path). Workaround is to pass the object id and then fetch it again from within the function but it does not seem optimal since object is fetched in the first place.

I was expecting to do something like:

 function yourFunction() {
     // with `this` referencing the object pointed to by the simple metric path
     // and `spec` being the metric spec
     return MyType.toTimeseries(this, spec);
}

Thanks


What is the MetricFunctionLibrary?
#12

Metric functions cannot access the spec, instead they take Timeseries as their input and produce Timeseries as their output. (If you are talking about ActionDecl metrics, which are a different feature, they DO look like what you’re talking about)

An example of a metric function which calculates resource spending based on consumption, demand, and the customer’s rate plan is as follows:

type RatePlanMetricLibrary mixes MetricFunctionLibrary {
  
  /**
   * Uses the Genability API to calculate the amount of money spent on electricity.
   *
   * @param ratePlans
   *           Information about how to calculate cost from consumption and demand.
   * @param consumption
   *           Electricity consumption data used in calculation. Units are kilowatt hours.
   * @param demand
   *           Electricity demand data used in calculation. Demand is the rate (first derivative with respect to
   *           time) of electricity consumption. Units are kilowatts.
   * @return the cost of electricity for each point in time. Aggregating this will yield the total cost.
   */
  calculateElectricitySpending: function(ratePlans: Timeseries,
                                         consumption: Timeseries,
                                         demand: Timeseries): Timeseries js server
}

The js file looks like this (just the top level function, internally called functions are not here):

function calculateElectricitySpending(ratePlans, consumption, demand) {
  var results;
  try {
    var specs = RatePlan.generateGenabilitySpecs(ratePlans, consumption, demand, 'electricity');
    results = _.flatten(_.map(specs.toArray(), function (spec) {
      return GenabilityService.calculateTariff(spec).toArray();
    }));
  } catch (e) {
    log.error(e);
    return unavailable(consumption);
  }

  return transformToTimeseries(results, consumption);
}

and a metric calling this function looks like this:

{
  "id" : "CalculatedBilledElectricitySpending",
  "name" : "CalculatedBilledElectricitySpending",
  "expression" : "calculateElectricitySpending(ElectricityRate, BilledElectricityConsumption, EstimatedBilledElectricityDemand)"
}

Where ElectricityRate, BilledElectricityConsumption etc. are themselves metrics.


Missing in eval metric result
Python Equivalent for returning time series object
Pearson Correlation, Metrics or Expression Engine Functions
Python Equivalent for returning time series object
Univocal extraction operator?
#13

Thanks for this detailed example. Those are handy indeed but do not meet my needs in this precise case.

I do not understand why the metric provided in the initial post:

{
    name: "Service_ToTimeseries",
    id: "Service_ToTimeseries",
    srcType: "Service",
    path: "ratePlan",
    expression: "RatePlan_ToTimeseries",
}

returns an empty Timeseries even though the metric RatePlan_ToTimeseries is returning the correct result when called directly on Service.ratePlan. Why doesn’t the metric engine properly follow the path?
Surely, implementing an actionDecl to wrap this metric is cumbersome and very redundant, especially if it has to be done on each type that could be an endpoint. It can’t be the recommended solution, can it? Isn’t it a bug of the metric engine?

Thanks


#14

Are MetricFunctionLibrary considered pure for the purpose of MetricEngine optimizations? (Or are they as problematic as actionDecl in this sense?)

Thanks