Tag that summarises/aggregates child tags

I want to have a tag structure for the factory that summarises (a dynamic number of) child tags, as you look up the tree. For example I have a "line" UDT that has multiple "cell" UDTs below it. I want the "OEE" and "Status" tags in the "line" to be expression tags that summarise the corresponding tags on the child "cell"s. An example simplified structure:
-Children (Dataset of paths to the cell tags)


What I want is for the "line/OEE" tag to be an average of the child tags (The final implementation will pass the more data, not the top level OEE, but this is a simplified example):
Pseudo code

OEEsummary = 0
foreach child in line.Children
    OEEsummary += tag(child + "/OEE")
line.OEE = OEESummary/line.Children.Count

For the Status tag I have a feeling the business logic will get very complicated (Based off what managers want shown at their level), so for the moment I will just say that the tag should be the max (Worst case) value:
Pseudo code

MaxStatus= 0
foreach child in line.Children
    childStatus = tag(child + "/Status")
    if(childStatus > MaxStatus) MaxStatus = childStatus
line.Status = MaxStatus

I have not found a way of achieving this yet. I would prefer this is done as expressions so the code lives within the tags, but maybe this is only achievable with Python?

You can write a script for this, then use the runScript expression (documentation) on the tag to run the script.

Expression on the line/Status tag:

runScript("line.getStatus", [pollRate], {[.]Children})

Expression on the line/OEE tag:

runScript("line.getOEE", [pollRate], {[.]Children})

(you'll have to choose a reasonable poll rate in ms for your application)

Where the Project Script "line" has the following function:

def getOEE(childrenTags):
    tags = ["%s/OEE" % childTag for childTag in childrenTags]
    tagRead = system.tag.readBlocking(tags))
    return sum(tagRead)/len(tagRead)

def getStatus(childrenTags):
    tags = ["%s/Status" % childTag for childTag in childrenTags]
    return max(system.tag.readBlocking(tags))

Might not be exactly correct, but should get you started.

Thanks for the help I almost have this working, but the data from the tag seems to be imported in a java format, not a python readable format.
The function is:

def sumTags(tagArray, subTag):
    tags = [str(childTag) + subTag for childTag in tagArray]
    return sum([x.value for x in system.tag.readAll(tags)])

When called with a manually created object ["tag1","tag2"] it works perfectly. When a value is passed from a tag the input data is not formatted correctly (I have tried formatting the tag as both a dataset and an array).
Printing out "tagArray" I see:
[[[Ljava.lang.String;@475a7e29, Good, Tue Jan 10 21:43:33 PST 2023 (1673415813054)]]
Printing "tags" from the for loop creates an output like this:
["array(java.lang.String, [u'tag1', u'tag2', u'tag3'])/OEE"]
The whole arrays seems to be seen as a single string based on the subtag text being appended to the end of the array string, not to the individual items

Can we see the full call of sumTags and the parameters you are passing?

Use system.tag.readBlocking (unless you're on a very old version).
Also, use a generator expression instead of list comprehension:

return sum(x.value for x in system.tag.readAll(tags))

This won't fix your issue though, but might as well improve things where that can easily be done.

The two calls I am currently using (from the script console to get debugging) are:
print Business_Structure.sumTags(["tag 1","tag 2"], "/OEE")
Returns the correct integer
print Business_Structure.sumTags(system.tag.readBlocking("Children"), "/OEE")
Throws an error because the tags are one malformed string

The issue as I see it is a basic array is structured like this:


The tag is theoretically structured like this:


But I can't seem to iterate over the inner array. If I try to nest for-loops I get:
TypeError: 'com.inductiveautomation.ignition.common.model.values.BasicQualifiedValue' object is not iterable
It seems like I need to cast that type to Python array

Note: I have simplified the tag paths for ease of reading

system.tag.readBlocking returns a list of qualified values.

It looks like from your earlier post you're passing in a string array tag qualified value as the 'list of tags to read':

[Ljava.lang.String;@475a7e29 is the default Java toString() of a string array.
The second layer of square brackets ([[Ljava.lang.String;@475a7e29, Good, Tue Jan 10 21:43:33 PST 2023 (1673415813054)]) is the toString() of the qualified value.
The last set of square brackets on the outside is indicating you've got a list of results.

You can either unpack things before you pass them in to your sumTags function (generally preferred) or you can unpack them inside the function, but you must do one or the other.

print Business_Structure.sumTags(system.tag.readBlocking("Children"), "/OEE")
Should instead be:
print Business_Structure.sumTags(system.tag.readBlocking("Children")[0].value, "/OEE")

You must get the first element of the list, then get the .value member, in order to reference just the string array you care about.

Thanks PGriffith

I type this out before you posted, so I may aswell post it:

Ok I have worked it out the value supplied by readBlocking() is an array of tags not a single tag, I need to reference the 0th item and iterate across the value property:

def sumTags(tagArray, subTag):
    tags = [str(childTag) + subTag for childTag in tagArray[0].value]
    return sum(x.value for x in system.tag.readBlocking(tags))

However after moving it back to the tag, it wasn;t working the value supplied by the expression {[.]Children} on the tag is just the value, not the tag, so it actually would have worked on the tag, even though it wasn't working in the scripting console, because I should have been using [0].value, when calling the method in the scripting console.

This is my first time working with Python and the weak typing here made this really hard to understand what was happening...
Not seeing the actual Python errors on the tag also made it harder.