we previously built a model using analytics builder for one worker calculating the time spent in a productive zone my measuring the duration between in event and out event for this worker
now we would like to create the same model for all workers in a group. Would this model work accurately for every worker in this group separately? or there is a risk that the calculated duration would be messed up for example: duration calculation triggered between worker#1 in event at 10:00 and the next worker out event which was by chance for a different worker in the same group (ex: worker#25)?
When a model consumes data from a group, the model behaves as if multiple instances of the model are running. Each instance processes data for a different device independently but all instances share the same blocks and block parameters.
thank you, and what if the initial model calculates a measurement output for every group member (via trigger device setting) as a first stage, and we need at the second stage to sum up all the outputs for all members of this group into a measurement (or any other way) in order for example to show the working hours for a specific group. What is the best practice to achieve it? How to associate this specific new measurement (called for example group aggregate) to the whole group?
Currently we do no have a block in Analytics Builder that does a sum of current values of inputs from a group but we do intend to provide this functionality in near future.
For the second stage or both the stages, you could use a custom EPL App which listens for the events generated by the first stage and then generates a sum of values for all workers in a particular group and outputs it either on each new input it receives or periodically depending on your requirement.
Please let us know if this approach suits you.
Thanks
thanks, is there an ETA or road map for the analytics builder block that sums up values for all group?
and by the way, If I wanna start the model using analytics builder if a parent condition followed by a nested child condition is it possible? for example if a geofence alarm is true (or active) which is the parent condition, then we would check for specific events which is the nested child condition
and by the way, If I wanna start the model using analytics builder if a parent condition followed by a nested child condition is it possible? for example if a geofence alarm is true (or active) which is the parent condition, then we would check for specific events which is the nested child condition
and for the EPL for grouping the measurements of the whole group of members, do you have any EPL code which is close to our requirements that we can upload and modify instead of writing everything from scratch?
Yes, the nested conditions can be implemented in various ways. For the scenario you have described an AND block can be used which takes 2 inputs, one the Alarm input (true) and other a pulse input which is generated when the event is received. An output(pulse) will be generated from the AND block when Alarm is true and an event is received.
Also, see the “On geofence create alarm” sample under the Samples tab in Analytics Builder which, I think, might be useful for your use case.
Regarding the EPL app, I can write something as an example and you can then perhaps modify it as per your requirements.
Sorry it’s taken some time but here is a possible EPL solution for your scenario.
Please note this is a very basic solution to give you an idea. Depending on your use case, number of workers, number of groups, expected number of events etc. you will have to think about some performance improvements etc.
using com.apama.cumulocity.Event;
using com.apama.cumulocity.Measurement;
using com.apama.cumulocity.MeasurementValue;
using com.apama.cumulocity.ManagedObject;
using com.apama.cumulocity.Util;
using com.apama.cumulocity.FindManagedObject;
using com.apama.cumulocity.FindManagedObjectResponse;
using com.apama.cumulocity.FindManagedObjectResponseAck;
monitor PeriodicSumOfWorkTime {
// Managed Object Id, time
dictionary<string, float> workerTime;
// Group Id, time
dictionary<string, float> groupTime;
// Group, workers
dictionary<string, sequence<string>> workerGroups;
action onload() {
// Subscribe to Event.SUBSCRIBE_CHANNEL to receive all events
monitor.subscribe(Event.SUBSCRIBE_CHANNEL);
// Get all groups
getAllGroups();
// Send sum periodically
sendPeriodicSum();
}
/* Set up listeners for events . */
action setupListenersForWorkers(string group, string worker){
// Listen for event when the worker enters the zone
on all Event(source=worker, type="Worker_in_the_zone") as in_event {
on Event(source=worker, type="Worker_out_of_the_zone") as out_event {
// Calculate the time spent in zone by this worker
float time_in_zone := out_event.time - in_event.time;
// Save the individual worker time
if workerTime.hasKey(worker) {
workerTime[worker] := workerTime[worker] + time_in_zone;
}else{
workerTime.add(worker, time_in_zone);
}
}
}
}
/* Get all groups from inventory*/
action getAllGroups(){
FindManagedObject findManagedObject := new FindManagedObject;
findManagedObject.reqId := Util.generateReqId();
findManagedObject.params.add("pageSize", "2000");
findManagedObject.params.add("query", "$filter=( has(c8y_IsDeviceGroup))");
monitor.subscribe(FindManagedObjectResponse.SUBSCRIBE_CHANNEL);
on all FindManagedObjectResponse(reqId=findManagedObject.reqId) as fmo and not FindManagedObjectResponseAck(reqId=findManagedObject.reqId) {
ManagedObject mo := fmo.managedObject;
string workerId;
for workerId in mo.childAssetIds {
if workerGroups.hasKey(mo.id) {
workerGroups[mo.id].append(workerId);
}else{
workerGroups.add(mo.id, [workerId]);
}
}
}
on all FindManagedObjectResponseAck(reqId=findManagedObject.reqId) and not FindManagedObjectResponse(reqId=findManagedObject.reqId) {
//setup listeners for worker events
string group;
for group in workerGroups.keys() {
string worker;
for worker in workerGroups[group] {
setupListenersForWorkers(group, worker);
}
}
}
send findManagedObject to FindManagedObject.SEND_CHANNEL;
}
/*Listen for group changes*/
action groupChanges(){
ManagedObject mo;
on all ManagedObject(type="c8y_Group"): mo{
if mo.isCreate(){ // new group has been created
groupTime.add(mo.id, 0.0);
}else if mo.isUpdate(){ // new device has been added to the group
string worker;
for worker in mo.childDeviceIds{
if not workerTime.hasKey(worker) {
workerGroups[mo.id].append(worker); // add the new worker to the group
setupListenersForWorkers(mo.id, worker); // add a listener for the events from new worker
}
}
}
}
}
action sendPeriodicSum(){
// on every hour send the measurements
on all wait (3600.0){
// send individual worker time in zone
string worker;
for worker in workerTime.keys(){
send Measurement("ID","worker_time_sum","source",currentTime,{"Worker_time_in_zone":{"V":MeasurementValue(workerTime[worker],"float", new dictionary<string, any>)}},new dictionary<string,any>) to Measurement.CHANNEL;
}
string group;
// send group worker time in zone
for group in groupTime.keys(){
send Measurement("ID","group_time_sum","source",currentTime,{"Group_time_in_zone":{"V":MeasurementValue(groupTime[group],"float", new dictionary<string, any>)}},new dictionary<string,any>) to Measurement.CHANNEL;
}
// TODO - Also reset the time, i.e clear out the dictionaries periodically
}
}
}
I started checking the EPL provided, please find below some clarifications:
for the individual measurements of every worker, they are already calculated by a model in analytics builder so each worker has a group of measurements (ex: measurement#1-> measurement#N)
the purpose of the EPL is to check for members in each specific group, to get their existing measurements value (point#1) which and to sum it up the group value for each measurement and to share the sum to a group measurement (ex: group_measurement#1-> group_measurement#N)
of course EPL needs to listen to group members additions and to update accordingly
for resets, we are already resetting each individual measurement for example daily or weekly or monthly or yearly so probably no need to reset the corresponding group measurement in code
would you share the adjusted version of EPL code which would do those requirements?