# Automation Professionals' Integration Toolkit Module

**URL:** <https://forum.inductiveautomation.com/t/automation-professionals-integration-toolkit-module/74934>\
**Category:** 3rd Party Modules\
**Tags:** perspective, ignition81, ignition83, vision, scripting\
**Created:** [June 13, 2023, 12:42am UTC](https://forum.inductiveautomation.com/t/automation-professionals-integration-toolkit-module/74934 "2023-06-13T00:42:35Z")\
**Posts on this page:** 20\
**Page:** 13

<div class="post-metadata">

**Author:** ![nminchin](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/nminchin/32/6911_2.png) [@nminchin](https://forum.inductiveautomation.com/u/nminchin)\
**Post date:** [April 11, 2025, 5:50am UTC](https://forum.inductiveautomation.com/t/automation-professionals-integration-toolkit-module/74934/242 "2025-04-11T05:50:25Z")

</div>

Hey @pturmel,

I'm trying to figure out how I can calculate the time-weighted rate of change from a dataset with "t\_stamp" and "value" columns with your toolkit expression functions, but i'm a bit lost with the starting at i=1 and ending at i(n-1) but still using i(n)

 ![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/1/a/1a8fb666c6a55ba9db014824db3b67014368173c.png)

sample dataset:

```python
"#NAMES"
"t_stamp","value"
"#TYPES"
"date","F"
"#ROWS","6"
"2025-04-11 14:00:00.000","10.0"
"2025-04-11 14:01:00.000","20.0"
"2025-04-11 14:02:00.000","30.0"
"2025-04-11 14:03:00.000","50.0"
"2025-04-11 14:10:00.000","100.0"
"2025-04-11 14:15:00.000","100.0"

```

Without adding "now" it should return "90" edit: if it wasn't working properly. If it was working properly it would return "6"

---

<div class="post-metadata">

**Author:** ![pturmel](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pturmel/32/3295_2.png) [@pturmel](https://forum.inductiveautomation.com/u/pturmel)\
**Post date:** [April 11, 2025, 11:57am UTC](https://forum.inductiveautomation.com/t/automation-professionals-integration-toolkit-module/74934/243 "2025-04-11T11:57:59Z")

</div>

What a terrible example. That equation simplifies to (vn - v1) / (tn - t1). No need for the intermediate rows. (It does **not** yield 90, fwiw.)

The `lag()` function yields xr-1, with a null for the first row. Wrap `lag()` in a `coalesce()` to substitute in that first row. Or include an extra row as first, then cut it off with a `where()`.

---

<div class="post-metadata">

**Author:** ![nminchin](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/nminchin/32/6911_2.png) [@nminchin](https://forum.inductiveautomation.com/u/nminchin)\
**Post date:** [April 11, 2025, 12:48pm UTC](https://forum.inductiveautomation.com/t/automation-professionals-integration-toolkit-module/74934/244 "2025-04-11T12:48:25Z")

</div>

Whoops, I had a rough day 😅 I think my wife might have cooked her car engine, my son got nits from school, and I obviously forgot how numbers work 😵

> [@pturmel](#):
>
> It does **not** yield 90, fwiw

Oops, I typed the wrong value, should have been 6!

> [@pturmel](#):
>
> That equation simplifies to (vn - v1) / (tn - t1)

Correct, yes!

> [@pturmel](#):
>
> The `lag()` function yields xr-1, with a null for the first row. Wrap `lag()` in a `coalesce()` to substitute in that first row.

OK Ill give this a go, cheers!

---

<div class="post-metadata">

**Author:** ![pturmel](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pturmel/32/3295_2.png) [@pturmel](https://forum.inductiveautomation.com/u/pturmel)\
**Post date:** [April 11, 2025, 1:03pm UTC](https://forum.inductiveautomation.com/t/automation-professionals-integration-toolkit-module/74934/245 "2025-04-11T13:03:41Z")

</div>

Try this:

```python
transform(
	{path.to.source.dataset},
	sum(
		forEach(
			value(),
			if(
				isNull(lag()),
				0.0,
				(it()[1] - lag()[1]) * (toMillis(it()[0]) - toMillis(lag()[0]))
			)
		)
	) / (toMillis(value()[len(value()) -1 , 0]) - toMillis(value()[0,0]))
)

```

{Untested.}

---

<div class="post-metadata">

**Author:** ![nminchin](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/nminchin/32/6911_2.png) [@nminchin](https://forum.inductiveautomation.com/u/nminchin)\
**Post date:** [April 11, 2025, 1:11pm UTC](https://forum.inductiveautomation.com/t/automation-professionals-integration-toolkit-module/74934/246 "2025-04-11T13:11:13Z")

</div>

I think we're both overcooking it:

```python
transform(
	{this.custom.key},
	(value()[len(value())-1, 1] - value()[0, 1]) / 
	(millisBetween(value()[0, 0], value()[len(value())-1, 0])/1000/60)
)

```

---

<div class="post-metadata">

**Author:** ![pturmel](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pturmel/32/3295_2.png) [@pturmel](https://forum.inductiveautomation.com/u/pturmel)\
**Post date:** [April 11, 2025, 1:43pm UTC](https://forum.inductiveautomation.com/t/automation-professionals-integration-toolkit-module/74934/247 "2025-04-11T13:43:45Z")

</div>

> [@nminchin](#):
>
> overcooking

Hah! Indeed.

Tuck away what I show, though: if you replace the denominator with a time unit constant expressed in millis, the expression yields the _integral_ of `v dt` in that timebase.

---

<div class="post-metadata">

**Author:** ![paul-griffith](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/paul-griffith/32/37249_2.png) [@paul-griffith](https://forum.inductiveautomation.com/u/paul-griffith)\
**Post date:** [April 11, 2025, 2:44pm UTC](https://forum.inductiveautomation.com/t/automation-professionals-integration-toolkit-module/74934/248 "2025-04-11T14:44:55Z")

</div>

> [@nminchin](#):
>
> `len(value())-1`

☹

IGN-8204, someday, will let you do negative subscripts on ordered sequences.

Maybe I can convince someone to let me do a shotgun pass to the expression system...

---

<div class="post-metadata">

**Author:** ![pturmel](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pturmel/32/3295_2.png) [@pturmel](https://forum.inductiveautomation.com/u/pturmel)\
**Post date:** [April 11, 2025, 2:57pm UTC](https://forum.inductiveautomation.com/t/automation-professionals-integration-toolkit-module/74934/249 "2025-04-11T14:57:38Z")

</div>

> [@paul-griffith](#):
>
> someday, will let you do negative subscripts on ordered sequences.

Ooooo! I should add that to the `qvAt()` function.

---

<div class="post-metadata">

**Author:** ![pturmel](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pturmel/32/3295_2.png) [@pturmel](https://forum.inductiveautomation.com/u/pturmel)\
**Post date:** [April 11, 2025, 3:41pm UTC](https://forum.inductiveautomation.com/t/automation-professionals-integration-toolkit-module/74934/250 "2025-04-11T15:41:27Z")

</div>

That was easy. 😁

Automation Professionals is pleased to announce a new **Production** release of this Integration Toolkit. New features and changes since v2.1.0:

- Tag Actors
- `tags()` refactoring to return good quality even when some tag values are not good.
- New `qvAt()` function to make quality of list and dataset nested values accessible, with python-like negative index support.

For Ignition v8.1: [v2.1.1.251011523](https://www.automation-pros.com/ignition/Integration%20Toolkit-2.1.1.251011523-v81.modl)

---

<div class="post-metadata">

**Author:** ![Leor\_Fishman](https://avatars.discourse-cdn.com/v4/letter/l/e19adc/32.png) [@Leor\_Fishman](https://forum.inductiveautomation.com/u/Leor_Fishman)\
**Post date:** [April 11, 2025, 3:51pm UTC](https://forum.inductiveautomation.com/t/automation-professionals-integration-toolkit-module/74934/251 "2025-04-11T15:51:10Z")

</div>

...huh, is the expression system not running through jython -- is there a custom lexer/parser under there?

---

<div class="post-metadata">

**Author:** ![pturmel](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pturmel/32/3295_2.png) [@pturmel](https://forum.inductiveautomation.com/u/pturmel)\
**Post date:** [April 11, 2025, 3:53pm UTC](https://forum.inductiveautomation.com/t/automation-professionals-integration-toolkit-module/74934/252 "2025-04-11T15:53:55Z")

</div>

Expressions are pure java, except where a function author deliberately calls out to jython. In IA's stuff, only the `runScript()` expression hands off to jython.

In my toolkit, `objectScript()` is souped-up version of `runScript()`, and `view()` uses jython, too.

---

<div class="post-metadata">

**Author:** ![pturmel](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pturmel/32/3295_2.png) [@pturmel](https://forum.inductiveautomation.com/u/pturmel)\
**Post date:** [April 11, 2025, 3:55pm UTC](https://forum.inductiveautomation.com/t/automation-professionals-integration-toolkit-module/74934/253 "2025-04-11T15:55:16Z")

</div>

The reason users are advised to use expressions instead of scripts where possible is because the jython interpreter has significant overhead. And it is an interpreter, with all that implies.

---

<div class="post-metadata">

**Author:** ![paul-griffith](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/paul-griffith/32/37249_2.png) [@paul-griffith](https://forum.inductiveautomation.com/u/paul-griffith)\
**Post date:** [April 11, 2025, 4:25pm UTC](https://forum.inductiveautomation.com/t/automation-professionals-integration-toolkit-module/74934/254 "2025-04-11T16:25:31Z")

</div>

Yep. It's generated code, but pure Java.

> **[ELParser](https://files.inductiveautomation.com/sdk/javadoc/ignition81/8.1.47/com/inductiveautomation/ignition/common/expressions/parsing/ELParser.html)**
>
> declaration: package: com.inductiveautomation.ignition.common.expressions.parsing, class: ELParser

---

<div class="post-metadata">

**Author:** ![Leor\_Fishman](https://avatars.discourse-cdn.com/v4/letter/l/e19adc/32.png) [@Leor\_Fishman](https://forum.inductiveautomation.com/u/Leor_Fishman)\
**Post date:** [April 11, 2025, 4:35pm UTC](https://forum.inductiveautomation.com/t/automation-professionals-integration-toolkit-module/74934/255 "2025-04-11T16:35:22Z")

</div>

LR(1) lookahead, i'm gathering from the docs 🙂  
(i don't suppose y'all have a BNF for the expression language)

---

<div class="post-metadata">

**Author:** ![hunterdg](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/hunterdg/32/67774_2.png) [@hunterdg](https://forum.inductiveautomation.com/u/hunterdg)\
**Post date:** [April 11, 2025, 7:02pm UTC](https://forum.inductiveautomation.com/t/automation-professionals-integration-toolkit-module/74934/256 "2025-04-11T19:02:11Z")

</div>

is there a `set()` (specifically, the list deduplication “feature") equivalent? (before I try to derive a creative one)

---

<div class="post-metadata">

**Author:** ![ryan.white](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/ryan.white/32/73193_2.png) [@ryan.white](https://forum.inductiveautomation.com/u/ryan.white)\
**Post date:** [April 11, 2025, 7:08pm UTC](https://forum.inductiveautomation.com/t/automation-professionals-integration-toolkit-module/74934/257 "2025-04-11T19:08:12Z")

</div>

You could do something funky like `where({array}, IndexOf({array}, it()) = idx())`.  
Seems to work in perspective at the least.

Pre-Expression:

```json
[
  {
    "key": "value",
    "key_1": 2
  },
  "value2",
  {
    "key": "value",
    "key_1": 3
  },
  "value",
  {
    "key": "value",
    "key_1": 3
  },
  "value"
]

```

Post expression:

```json
[
  {
    "key_1": 2,
    "key": "value"
  },
  "value2",
  {
    "key_1": 3,
    "key": "value"
  },
  "value"
]

```

---

<div class="post-metadata">

**Author:** ![paul-griffith](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/paul-griffith/32/37249_2.png) [@paul-griffith](https://forum.inductiveautomation.com/u/paul-griffith)\
**Post date:** [April 11, 2025, 7:39pm UTC](https://forum.inductiveautomation.com/t/automation-professionals-integration-toolkit-module/74934/258 "2025-04-11T19:39:46Z")

</div>

> [@hunterdg](#):
>
> is there a `set()` (specifically, the list deduplication “feature") equivalent? (before I try to derive a creative one)

Just curious, what's the use case you have for this?

---

<div class="post-metadata">

**Author:** ![hunterdg](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/hunterdg/32/67774_2.png) [@hunterdg](https://forum.inductiveautomation.com/u/hunterdg)\
**Post date:** [April 11, 2025, 7:47pm UTC](https://forum.inductiveautomation.com/t/automation-professionals-integration-toolkit-module/74934/259 "2025-04-11T19:47:07Z")

</div>

deriving a single working tagpath from “fuzzy matching” on unordered/unspecified path _parts_ passed in to a view. could be from flex repeater instance parameters, implicit `rowData` parameters, cell `value` parameter, etc. Each method has limitations and I don’t want to hardcode fullpaths, but could end up with duplicates. I could just iterate over the duplicates, but thought might as well deduplicate them

speaking of - I’m sure I’ve figured out _why_ folder tag bindings return ‘2’, but can’t remember at the moment.

for instance each highlight is the **exact same** embedded view, but

- subview components are using full tag paths (results of system.tag.query from each row’s hidden `fullpath` column
- most table columns are using derived paths based on cell value, rowdata and column viewparams with an “altPaths” list of possible subpaths/tagnames
- % Valid column is using a completely different path that can’t be derived entirely from rowdata so using string replacement(indirection) on a “parameterized” “full path” coming from the same column viewparams “altPaths” list

 ![CleanShot 2025-04-11 at 15.42.34](https://us1.discourse-cdn.com/inductiveautomation/original/3X/1/8/18b189378845eb1e83fe364ed376a7f56735a200.png)

---

<div class="post-metadata">

**Author:** ![pturmel](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pturmel/32/3295_2.png) [@pturmel](https://forum.inductiveautomation.com/u/pturmel)\
**Post date:** [April 11, 2025, 8:12pm UTC](https://forum.inductiveautomation.com/t/automation-professionals-integration-toolkit-module/74934/260 "2025-04-11T20:12:40Z")

</div>

Yes, you can emulate `set()` with something like this:

```python
forEach(
	asMap( // Keys of a map are forced to be unique.
		forEach( // Convert simple list to list of pairs where the 2nd value is a dummy
			{path.to.list.of.strings},
			it(),
			""
		)
	),
	it()[0] // Extract the unique keys back out of the map.
)

```

---

<div class="post-metadata">

**Author:** ![lrose](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/lrose/32/62724_2.png) [@lrose](https://forum.inductiveautomation.com/u/lrose)\
**Post date:** [April 14, 2025, 1:27pm UTC](https://forum.inductiveautomation.com/t/automation-professionals-integration-toolkit-module/74934/261 "2025-04-14T13:27:42Z")

</div>

Phil,

Just wanted to say that this module has once again saved me a bunch of work.

Needed to filter a dataset, because it couldn't be done in a Named Query, and I really didn't want to have to script the query, just be cause NamedQueries can not utilize a list to facilitate an `IN` clause.

`where()` to the rescue.

Sometimes, I wonder how much time I would waste without this module.

[Previous page](https://forum.inductiveautomation.com/t/automation-professionals-integration-toolkit-module/74934.md?page=12)

[Next page](https://forum.inductiveautomation.com/t/automation-professionals-integration-toolkit-module/74934.md?page=14)
