QPR ProcessAnalyzer Expression Examples

From QPR ProcessAnalyzer Wiki
Jump to navigation Jump to search

The following examples assume that there is a filter with id 1. point. All KPI analyses also have an EventLog as a starting point, so expression written after the "EventLogById(1)." is a valid expression for KPI Analyses.

Get all event types that appear among the events:

EventLogById(1).EventTypes

Same as previous, except return event type names (Strings):

EventLogById(1).EventTypes.Name

Get a hierarchical array, where the upper level are EventTypes and the leaf level are Events:

EventLogById(1).EventTypes.Events

Get a hierarchical array, where the upper level are EventTypes and the leaf level are also EventTypes (all branches contain same EventTypes):

EventLogById(1).EventTypes:Events.Type
EventLogById(1).EventTypes:Events.TimeStamp

List all Events as leaf nodes which timestamp is after 2012-01-01:

EventLogById(1).EventTypes:Events.Where(Timestamp > DateTime(2012,1,1))

Calculates count of Events in the leaf nodes:

Count(EventLogById(1).EventTypes:Events.Where(Timestamp > DateTime(2012,1,1)))

Events are ordered beginning from the last one:

EventLogById(1).EventTypes.OrderByDescending(Events, Timestamp)

Same as the previous but with explicit context specified:

EventLogById(1).EventTypes.OrderByDescending(_.Events, _.Timestamp)

Shows all event types in a single comma separated string list:

EventLogById(1).(StringJoin(", ", EventTypes.Name))

Same as the previous, except in addition the EventTypes are ordered by name:

EventLogById(1).(StringJoin(", ", OrderByDescending(EventTypes, Name).Name))

List of all the Starter flows in a model from filterId=1:

RemoveLeaves(EventLogById(1).Flows:From.Where(IsNull(_)))

Go through the model recursively using the flows:

RemoveLeaves(EventLogById(1).Flows:From.Where(IsNull(_))).To.OutgoingFlows.To.OutgoingFlows.To

Traverse the model using events:

RemoveLeaves(EventLogById(1).Flows:From.Where(IsNull(_))).FlowOccurrences.To.NextInCase.NextInCase.NextInCase

All case attribute values within the model grouped by case attribute types:

Let("cases", EventLogById(1).Cases, EventLogById(1).CaseAttributes:(Let("attribute", _, cases.Attribute(attribute))))

All event attribute values within the model grouped by event attribute types:

Let("events", EventLogById(1).Events, EventLogById(1).EventAttributes:(Let("attribute", _, events.Attribute(attribute))))

Define a new user defined functions named "First", which takes the first item in an array:

Def("First", "a", GetAt(0, a));

The defined array can be used as follows:

First(Array(1, 2, 3))
returns: 1
Def("x", "a", Distinct(RecurseLeaves(OrderBy(EventLogById(a).Cases.Events, Type.Name), Type.Name)))

Function definition that uses itself, i.e. recursive functions:

Def("Fib", "a", If(a < 2, 1, Fib(a - 1) + Fib(a - 2)));
For("i", 0, i < 10, i + 1, Fib(i));

Takas all models, and show them in "id:name" format:

Models.StringJoin(":", [Id, Name])
Models.[Id, Name]

How many times activity "Sales Order Changed" occurs on average in cases?

Average(Count(Cases.Events.Where(Type.Name == "Sales Order Changed")))

What's the average cost per case of all sales order changes? (assumption Sales Order Changed activity has attribute Cost)

Average(Sum(Cases.Events.Where(Type.Name == "Sales Order Changed").Cost))

What's the total cost of all events that occured this year?

Sum(Sum(Cases.Events.Where(TimeStamp.Year == Now.Year).Cost))

What the total cost calculated from events, where cost is calculated by "unit cost" times "number of units"?

Sum(
    Cases.Sum(
        Events.(
            UnitCost * NumberOfUnits
        )
    )
)

In how many cases duration between "Invoice Sent" and "Payment Received" is more than 30 days?

Count(
  Cases.Where(
    (
      Max(
        Events.Where(
          In(Type.Name, "et1", "et2")
        ).TimeStamp
      ) -
      Min(
        Events.Where(
          In(Type.Name, "et1", "et2")
        ).TimeStamp
      )
    ).TotalMinutes
    > 60
  )
)

In how many cases "Invoice Sent" is not directly followed by "Payment Received"?

Count(
  RemoveLeaves(
    (
      Cases.Events.Type.Name
    ).Where(
      Count(
        IndexOfSubArray(
          Array("et2", "et3")
        )
      ) <  Count(
        IndexOfSubArray(
          "et2"
        )
      )
    )
  )
)

In how many cases "Payment Received" is before "Invoice Sent"?

Count(
  RemoveLeaves(
    (
      Cases.Events.Type.Name
    ).Where(
      Catch(
        Max(
          IndexOfSubArray("et3")
        )
        < Min(
          IndexOfSubArray(
            "et1"
          )
        ),
        false
      )
    )
  )
)

In how many cases, invoice wasn't paid within the due date? (i.e. invoice is due and it's not paid, or invoice was paid after due date)

Count(
  Cases.Where(
        (
            (Count(
                Events.Where(
                    Type.Name == "Invoice Paid"
                )
            ) == 0)
            &&
            (
                Catch(
                    GetAt(0, Events.[Invoice Sent].StopOnNull()) < Now
                , false)
                ||
                Catch(
                    GetAt(0, Events.[Due Date].StopOnNull()) < Now
                , false)
            )
        )
        ||
        (
            Catch(
                GetAt(0,
                    Events.Where(
                        Type.Name == "Invoice Paid"
                    ).TimeStamp
                )
                >
                Coalesce(
                    Catch(GetAt(0, Events.[Invoice Sent]), null),
                    Catch(GetAt(0, Events.[Due Date]), null)
                ),
            null)
        )
    )
  )
)

Cases average duration between the first occurrence of event A and last occurrence of event B. Average is calculated only for those cases that contain both events A and B. Usecase: How long it takes to receive payment on average?

Average(
  Sum(
    Cases.Events.Where(
      Type.Name == "Payment Received"
    ).(
      TimeStamp - GetAt(
        0,
        Case.Events.Where(
          Type.Name == "Invoice Sent"
        )
      ).TimeStamp
    )
    .TotalMinutes
  )
)

What is the % of POs (cases) in a certain price range (case attribute "price")?

100 * Count(Cases.Where(Price > 1 && Price < 4)) / Count(Cases)

What is the average duration from event A directly followed by event B for those cases that directly continue to event C after B? (take the first occurrence if there are many)

Average(
  Cases.Let(
    "indexes",
    Box(
      Events.Type.Name
    ).IndexOfSubArray(
      Array("et2", "et3", "et4")
    )
  ).Where(
    Count(indexes) > 0
  ).(
    GetAt(
      GetAt(0,
        GetAt(0, indexes)
    ),
    Events
    )
  ).(
    NextInCase.TimeStamp - TimeStamp
  ).TotalMinutes
)

What is the average duration of completely received orders?

Average(
    Cases.Where(
        Count(
            Events.Where(
                Type.Name == "et3"
            )
        ) > 0
    ).(
        GetAt(0,
            Events.Where(
                Type.Name == "et2"
            ).TimeStamp
        )
        -
        GetAt(0,
            Events.Where(
                Type.Name == "et1"
            ).TimeStamp
        )
    ).TotalMinutes
)

Basic model usage

Models;
Models[0].EventLog;
Let("el", Models[0].EventLog);
el.Cases
el.Cases.Events
el.Cases:Events
el.Cases:Events.Type.Name
el.Cases.StringJoin("->", Events.Type.Name)
Def("show", "cases", cases.(CalcId + ": " + StringJoin("->",  Events.Type.Name)))
show(el.Cases)

Arrays

[1,2,3][0]
[1,2,3][Count(_)-1]
[1,2,3][[0,Count(_) - 1]]
[1,2,3][NumberRange(0, Count(_)-1)]

Calculating median

Def("Median", "array", (Let("a", OrderByValue(array)), If(Count(a) % 2 == 1, a[(Count(a) - 1) / 2], (a[(Count(a) / 2) - 1] + a[Count(a) / 2]) / 2)));
Median([4,1,3,2])
Median([1,2,3])

Remove tandem repeats of length 1

Def("RemoveTandemRepeatsOfLength1", "a", (Let("prev", null),ForEach("item", a, If(item == prev, _remove, (Set("prev", item), item)))._));
RemoveTandemRepeatsOfLength1([1,2,2,3,5,5,5,5,2,5])

Aggregation functions

Percentage of cases having event type named "Invoice"
Average(Count(el.Cases.Events.Where(Type.Name == "Invoice")))

Average flow durations for non-starter/-finisher flows
Average(el.Flows:FlowOccurrences.Where(!IsNull(From) && !IsNull(To), _remove).(To.TimeStamp - From.TimeStamp))

Average of all the flow durations
Average(Flatten(el.Flows:FlowOccurrences.Where(!IsNull(From) && !IsNull(To), _remove).(To.TimeStamp - From.TimeStamp)))

Context labeling

Let("a", ["foo": ["a": 1, "b": 2], "bar": 2])
GetContext(a[0])
GetValueOfContext("bar", a)

Recursion

el.Events[0].Recurse(NextInCase)
el.Events[0].RecurseWithHierarchy(NextInCase)
el.Events[0].RecursiveFind(NextInCase, Type.Name == "Invoice")
el.Flows.Where(IsNull(From)).To.RecurseWithHierarchy(OutgoingFlows.To, !IsNull(_), 2)

Functions

// Create Fibonacci number generator and a tester for it
Def("Fib", "a", If(a < 2, 1, Fib(a - 1) + Fib(a - 2)));
Def("FibTest", "a", For("i", 0, i < a, i + 1, Fib(i)));
FibTest(10);
Call function objects using given parameters
[Def("", "a", "b", a+b), Def("", "a", "b", a*b)].(_(1,2))

The sum of all the durations between the first occurrence of "Invoice" and the last occurrence of "Sales Order" event types.
Def("FirstOccurrence", "&condition", _.Events[0].RecursiveFind(NextInCase, condition())[0]);
Def("LastOccurrence", "&condition", _.Events[Count(_) - 1].RecursiveFind(PreviousInCase, condition())[0]);
TimeSpan(0, 0, 0, Average(el.Cases.(Catch(FirstOccurrence(Type.Name == "Invoice").TimeStamp - LastOccurrence(Type.Name == "Sales Order").TimeStamp, _remove).TotalSeconds)))

Miscellaneous examples

Return all starter flows
RemoveLeaves(el.Flows:From.Where(IsNull(_)))
el.Flows.Where(IsNull(From))

Create a range of dates
Def("DateRange", "minYear", "minMonth", "maxYear", "maxMonth", (Let("min", DateTime(minYear, minMonth)),Let("max", DateTime(maxYear, maxMonth)),For("dt", DateTime(min.Year, min.month), dt <= DateTime(max.Year, max.Month), dt.Month == 12 ? DateTime(dt.Year + 1, 1) : DateTime(dt.Year, dt.Month + 1), dt)))
DateRange(2011, 5, 2013, 1)

Group numbers into groups with specified minimum, maximum and range size.
Def("GroupNumbers", "a", "min", "max", "rangeSize", a.Ceiling(Max(0.0, Min(((max - min) / rangeSize) + 1, (_ - min)/rangeSize))));
GroupNumbers([-20, 0, 0.2, 3.2, 7, 9.2, 20], 0, 9, 3)

Show cases having at least one "Sales Order Changed (VA02)"
show(el.Cases.Where(Count(Events.Where(Type.Name == "Sales Order Changed (VA02)")) > 0))
show(Flatten(el.Cases.Events[0].RecursiveFind(NextInCase, Type.Name == "Sales Order Changed (VA02)").Case))

Show cases having "Sales Order Changed (VA02)" before "Outbound Delivery" at least once
show(el.Cases.Where(([Events.Type.Name].Coalesce(Min(IndexOfSubArray("Sales Order Changed (VA02)"))-Max(IndexOfSubArray("Outbound Delivery")), 0))[0] < 0))
show(Flatten(el.Cases.Events[0].RecursiveFind(NextInCase, Type.Name == "Sales Order Changed (VA02)").RecursiveFind(NextInCase, Type.Name == "Outbound Delivery").Case))

Workload analysis
Def("ActiveEventsAt", "at", "attribute", "value",
Cases:Events[0]
  .RecursiveFind(
    NextInCase,
    TimeStamp < at 
    && (Attribute(attribute) == value) 
    && (IsNull(NextInCase) || (NextInCase.TimeStamp > at))
)
);
Def("ActiveEventsAt", "at", "attribute", "value",Cases:Events[0].RecursiveFind(NextInCase,TimeStamp < at && (Attribute(attribute) == value) && (IsNull(NextInCase) || (NextInCase.TimeStamp > at))));
ReplaceLeafValues(el.ActiveEventsAt(DateTime(2011,9,6), "Organization", "Finance"), "item", [item, item.Organization], 0)
ReplaceLeafValues(el.ActiveEventsAt(DateTime(2011,9,6), "Organization", "Delivery"), "item", [item, item.Organization], 0)
ReplaceLeafValues(el.ActiveEventsAt(DateTime(2011,9,6), "Organization", "Sales"), "item", [item, item.Organization], 0)

Get names and durations of all the cases sorted by case duration grouped by variations sorted by the number of cases in each variation.
ReplaceLeafValues(OrderBy(OrderBy(el.Variations, CaseCount):Cases, Duration), "leaf", "" + leaf.Name + ": " + leaf.Duration, 0)

Conformance analysis
Let("xml", LoadTextFile("C:\\Users\\marhink\\Desktop\\SAP o2c.bpmn"));
Let("cm", DesignModelFromXml(xml));
Let("vars", OrderByDescending(EventLogById(1).Variations, CaseCount));
vars.(Let("analysis",AnalyzeConformance(cm)),""+CaseCount+" * "+(CountTop(analysis)==0)+((CountTop(analysis)>0)?" ("+StringJoin("",ReplaceLeafValues(analysis,"i",i[0]==3?i[2]+"->"+i[3]:(i[0]==2?"DNF":"")))+")":"")+": "+StringJoin("->",EventTypes.Name))
Let("cases", OrderBy(EventLogById(1).Cases, Name));
cases.(Let("analysis",AnalyzeConformance(cm)),""+(CountTop(analysis)==0)+((CountTop(analysis)>0)? " ("+StringJoin("",ReplaceLeafValues(analysis,"i",i[0]==3?i[2]+"->"+i[3]:(i[0]==2?"DNF":"")))+")":"")+": "+StringJoin("->",Events.Type.Name))

Hierarchy
el.RecurseWithHierarchy(OutgoingFlows.To, !IsNull(_), 10);
cm.StartEvents.RecurseWithHierarchy(OutgoingFlows.To, !IsNull(_), 10);