Rules in archetypes - what are the requirements?

For many years, there has been a little-used capability in ADL which enables basic expressions to be stated such as the following in the Apgar Observation archetype:

rules

*score_sum*: /data[id3]/events[id4]/data[id2]/items[id26]/value[id44]/magnitude = /data[id3]/events[id4]/data[id2]/items[id6]/value[id40]/value + /data[id3]/events[id4]/data[id2]/items[id10]/value[id39]/value + /data[id3]/events[id4]/data[id2]/items[id14]/value[id41]/value + /data[id3]/events[id4]/data[id2]/items[id18]/value[id42]/value + /data[id3]/events[id4]/data[id2]/items[id22]/value[id43]/value

where all those paths point to the various Apgar leaf data values, i.e. total, heart rate etc.

This kind of statement is intended to assert that the total value = the sum of the 5 elements, as per the Apgar formula. However, it was never that clear that it is an assertion, not a value-setting formula, which might also be something we want.

It’s also not very readable, even if the paths were rendered with a tool, they are long and painful to read.

Another kind of assertion was to for conditional mandation of some part of the data depending on some other data element (or more generally, an expression), e.g.

rules

/data[id2]/items[id21]/items[id15]/value[id50]/defining_code **matches** {[at19]} **implies exists** /data[id2]/items[id21]/items[id20]

Here the logical intention is to mandate that the data at the second path, which is about details of transfer (i.e. discharge to other care) if the value of the datum at the first path, which is ‘type of separation’ = at19|transfer|. Other examples are mandating data containing details of tobacco use if the value of the data item ‘tobacco use’ /= at44|non-user|.

This also is not that easy to read, or clear in its intentions.

More recently, as part of the development of a simple expression language that can be used across openEHR (archetypes, Task Planning, GDL etc), I proposed some key improvements to expressions in archetypes, namely:

  • symbolic names for paths, done by bindings

  • an explicit ‘check’ instruction to make the intention of assertion clearer

  • a defined() predicate to replace the use of ‘exists’

Examples of how these changes look are shown here in the working copy of the ADL2 spec. In this form, the above Apgar example becomes:

rules
**check** $apgar_total_value = $apgar_heartrate_value + $apgar_breathing_value + $apgar_reflex_value + $apgar_muscle_value + $apgar_colour_value

**data_bindings**

content_bindings = <
["apgar_breathing_value"] = <"/data[id3]/events[id4]/data[id2]/items[id10]/value[id39]/value">
["apgar_heartrate_value"] = <"/data[id3]/events[id4]/data[id2]/items[id6]/value[id40]/value">
["apgar_muscle_value"] = <"/data[id3]/events[id4]/data[id2]/items[id14]/value[id41]/value">
["apgar_reflex_value"] = <"/data[id3]/events[id4]/data[id2]/items[id18]/value[id42]/value">
["apgar_colour_value"] = <"/data[id3]/events[id4]/data[id2]/items[id22]/value[id43]/value">
["apgar_total_value"] = <"/data[id3]/events[id4]/data[id2]/items[id26]/value[id44]/magnitude">
>

And the smoking example is:

**check** $is_smoker = **True** **implies** **defined** ($smoking_details)

Note that this does not address the possible requirement of being able to state a formula that sets a field, or defines a purely computed value at a path.

We are still working on details of the expression language, variable binding idea and so on. I am interested in feedback on the approach shown in the spec, preferably provided here in the first instance.

  • thomas

I’m fine with this improvements, the only thing I feel that can be troublesome for users is having data_bindings and computed values in a completely different format/style

For us the main requirement of the rules is to calculate the value of other fields based on other fields. Only the checking of assertions has relatively little added value for the use cases our customers encounter. I would find it very hard to explain to any users or modelers that they can write checks that do the actual score calculation, but that they cannot actually use the calculated value anywhere. So we ignore this limitation altogether.

Also the value binding seems to have an case that has not been covered:

it is possible that a single path lookup results in a list of values. This means a single path-bound variable will contain multiple values (so a list of values). In the old case, you could handle this with a for_all statement to express that the assertion should be valid within the scope of a single event, for all events. How could value binding solve this? The same question applies to output variable binding as well as input variable binding.

Related to this, both the current and proposed specification is missing execution rules, especially when paths lookup to a list of values instead of a single variable and how to handle that. For example what does the following mean when /data/events/data/items/element[id3]/value/magnitude resolves to more than one value?

/data/events/data/items/element[id15]/value/magnitude = /data/events/data/items/element[id3]/value/magnitude + 3

And what if 3 is replaced by another path instead? What if the left hand side matches one value? And what if it matches more than one? In Archie I implemented ways to handle these cases by defining the operations on single values, on equally sized lists and in cases where one value is a list and the other one is a single value. But unless this is specified, this will be different for different implementations.

Regards,

Pieter Bos

Thanks Pieter,

this is very useful.

About the calculation:

Ah, I see, the assignment seems like a good solution. But why would I need a function to calculate a score that is just a sum of a number of values, instead of a few ±operators?

Multiplicities/data binding:

The there exists case is clear. However, what if I have four events, all having four elements, each with dv_quantity as value. Say I want the magnitude of the last of these quantities to be larger than the sum of the first three. Before I could write something like:

for_all $event in /data/events[id3]
$event/data/items/element[id6]/value/magnitude >
$event/data/items/element[id4]/value/magnitude +
$event/data/items/element[id5]/value/magnitude +
$event/data/items/element[id6]/value/magnitude

(I omitted a few node ids here that are not important for the example)

Not the most readable - but it does the job. With data binding, how do I express this? There no longer seems to be a path lookup outside of data binding, so I can’t write:

for_all $event in $events
$event/data/items/element[id6]/value/magnitude >
$event/data/items/element[id4]/value/magnitude +
$event/data/items/element[id5]/value/magnitude +
$event/data/items/element[id6]/value/magnitude

And binding all the separate paths to variables doesn’t work either – they will be bound as lists, and there is no way to iterate over four lists of values at once.

Note that a path that points to a single typed dvquantity in an archetype can still point to many items in the RM if somewhere up the tree there is a list or a set, for example more than one observation. So if you really want them to be typed on validation time, you need to check every attribute in the path to see if it can point to more than one value, then either make it a List<List> or define in which order to add it as a single list.

I implemented it by determining type at runtime, but it’s possible otherwise. This means that very often you need a for all statement, in which case data binding doesn’t really help. I defined some tricks with the basic operators also working on equally sized lists to make things a bit easier to understand for modelers. That’s why I asked about the execution rules. The tricks I did can be easily rewritten into for_all statements if we need to have them removed.

This leads to more interesting cases when you flatten rules to an OPT 2 template, to obtain a single artifact that can be used for many things, including rule evaluation. That is very doable right now by prepending some paths and adding some for_all statements. I’m not sure how to do that with data binding.

Regards,

Pieter Bos

Hi Pieter,

“But why would I need a function to calculate a score that is just a sum of a number of values, instead of a few ±operators?”

It is an open question but one advantage of using the function approach, with simple values is that it can encapsulate the algorithm without too much dependency on understanding openEHR paths or path -bindings. That should allow broader engagement with non-openEHR specialists.

My preference is to support use of openEHR datatypes within the expression (albeit perhaps in reduced format), as otherwise passing units etc as scalars starts to get cumbersome.

e.g

$apgar_heartrate, $apgar_breathing, $apgar_reflex, $apgar_muscle, $apgar_colour)

where each of these is actually an ordinal, rather than a scalar value.

Not such a good example but think of a BMI calc, where the units used for height and weight are critical

We can learn a lot from GDL experience in this regard.

Ian

About the calculation:

Ah, I see, the assignment seems like a good solution. But why would I need a function to calculate a score that is just a sum of a number of values, instead of a few +-operators?

well you might want to re-use that function. More generally, some functions are more interesting, e.g. MAP calculation, and it is potentially better to a) name them, b) declare them in a more obvious place and c) make them re-usable.

Multiplicities/data binding:

The there exists case is clear. However, what if I have four events, all having four elements, each with dv_quantity as value. Say I want the magnitude of the last of these quantities to be larger than the sum of the first three. Before I could write something like:

for_all $event in /data/events[id3]
$event/data/items/element[id6]/value/magnitude >
$event/data/items/element[id4]/value/magnitude +
$event/data/items/element[id5]/value/magnitude +
$event/data/items/element[id6]/value/magnitude

Assuming you meant to put 'id7' as the first one, I don't understand what this achieves beyond just:

/events[id2]/data/items/element[id7]/value/magnitude >
/events[id2]/data/items/element[id4]/value/magnitude +
/events[id2]/data/items/element[id5]/value/magnitude +
/events[id2]/data/items/element[id6]/value/magnitude

which says the same thing, for all events in the runtime data that conform to the /events[id2] branch of the structure.

If we were to allow the expression for_all $event in /data/events[id3] then we need to be clear on what it means: it actually refers to an object of type List<EVENT>, but do the members consist of EVENT objects found in data at the moment the expression is evaluated? Or just the statically defined members in the archetype - which can also be multiple, e.g. see the Apgar archetype, it has 1 min, 2 min, 5 min events?

Normally we want the processing of 'rules' expressions in archetypes to apply to the data when the archetype is being used in its normal role of creation / semantic validation at data commit time. So it seems to me that if we want to support expressions like the above, we need to be able to do something like (don't worry about the concrete syntax too much, could easily be TS or java-flavoured):

*use_model*
org.openehr.rm

*data_context*

$xxx\_events: List&lt;EVENT&gt;
$item\_aaa, $item\_bbb, $item\_ccc, $item\_ddd: Real

*definition*

check for\_all event in $xxx\_events:
    event/$item\_aaa &gt; event/$item\_bbb \+ event/$item\_ccc \+ event/$item\_ddd

*data_bindings* -- pseudo-code for now

$xxx_events -> /events[id2]
$item_aaa -> /data/items/element[id7]/value/magnitude
$item_bbb -> /data/items/element[id4]/value/magnitude
$item_ccc -> /data/items/element[id5]/value/magnitude
$item_ddd -> /data/items/element[id6]/value/magnitude

I don't know what this archetype is, so assume that $xxx_events, $item_aaa etc are more meaningful names.

The next problem you mentioned is:

PB: Note that a path that points to a single typed dvquantity in an archetype can still point to many items in the RM if somewhere up the tree there is a list or a set, for example more than one observation

So I think this implies an incorrect interpretation of this kind of code within an archetype. It can't be understood as simultaneously applying to multiple Observations if it is within an Observation archetype, only to one OBSERVATION instance at a time - usually one about to be committed.

You can still have Lists of things internal to the archetype, as shown above with the Events list, but to process the multiplicity, you would need to do as we have done and use for_all, or some other container-aware operator or function.

Anyway, does this get closer to the sense of what you would like to do? It's more than I had conceived of, so this is a useful challenge...

- thomas

agree - that is the idea of this construct in the EL

*use_model*
org.openehr.rm

then you can declare vars to be of RM types like DvQuantity, DvOrdinal etc.

- t

But then you need a second language to define a function to calculate a simple score. Without it’s possible to create tooling to help modelers write at least simple expressions. basic math and some simple ‘if x is between 3 and 10, fill this field with this value’ is entirely doable without manually writing expressions, even in the proposed syntax. And the more technically inclined modellers can do a lot more if the tooling helps them with choosing the right paths. Also functions will need to be reimplemented for every environment - doesn’t sound like interoperability to me.

On the subject of the lists of values problem: Instead of binding outside the expression language, you could do something like assigning the value of a path to variables with a readable name as statements in the actual expression language. in combination with a for loop that can contain multiple statements, or at least variable assignments and one statement, it would solve these issues completely and still be readable to humans. and you can still write tooling to visualize the rules to non-technical people like we can now.

Also I consider the requirement to explicitly state the type of variables beforehand to be tricky. Tooling could solve it, but it’s not something I can easily explain to less technical people.

An alternative could be to switch to something Turing complete, like JavaScript or typescript, to define a few included functions to lookup data and to interact with the data, and a defined data model to ensure interoperability. Much easier to specify and implement, although it will be harder/no longer possible to write tooling to visualize the code and harder to write tools that allow less-technical people to write code.

Regards,

Pieter Bos

Hi Pieter,

“But why would I need a function to calculate a score that is just a sum of a number of values, instead of a few ±operators?”

It is an open question but one advantage of using the function approach, with simple values is that it can encapsulate the algorithm without too much dependency on understanding openEHR paths or path -bindings. That should allow broader engagement with non-openEHR specialists.

My preference is to support use of openEHR datatypes within the expression (albeit perhaps in reduced format), as otherwise passing units etc as scalars starts to get cumbersome.

e.g

$apgar_heartrate, $apgar_breathing, $apgar_reflex, $apgar_muscle, $apgar_colour)

where each of these is actually an ordinal, rather than a scalar value.

Not such a good example but think of a BMI calc, where the units used for height and weight are critical

We can learn a lot from GDL experience in this regard.

Ian

Assuming you meant to put 'id7' as the first one, I don't understand what this achieves beyond just:

/events[id2]/data/items/element[id7]/value/magnitude >
        /events[id2]/data/items/element[id4]/value/magnitude +
        /events[id2]/data/items/element[id5]/value/magnitude +
        /events[id2]/data/items/element[id6]/value/magnitude

which says the same thing, for all events in the runtime data that conform to the /events[id2] branch of the structure.

Since the occurrences of events[id2] can be more than one, /events[id2]/data/items/element[id7]/value/magnitude in the execution environment (actual data) maps to a List<Real>. That means you need to define operators such as +, > and = on a list of reals. Or to define somehow that a statement containing only path bindings within a single multiply-occurring structure will mean that it gets evaluated for each occurrence of such a structure. The second case is complicated if you need to include data outside /events[id2] in your expression. A real world use case would be data in /protocol, which can influence the interpretation of event data, but is outside of the event.
So we did the first in Archie, with a bit of tricks to make this work for assignments. For example, how the plus operator is interpreted in Archie:

+(Real r1, Real r2)
  return the sum both numbers
+(Real r1, List<Real> r2)
   return a list the same size as r2, with every element result[i] = r1 + r2[i]
+(List<Real> r1, r2)
   return a list the same size as r1, with every element result[i] = r1[i] + r2
+(List<Real>l r1, List<Real> r2)
  if r1 and r2 have different length, throw an exception. Otherwise, return a list the same size as r1, with every element result[i] being r1[i] + r2[i]

And the > operator:

(Real r1, Real r2)

  return true if r1 > r2, false otherwise

(Real r1, List<Real> r2)

  return a list of Booleans, one for every element in r2, true if r1 > that element

(List<Real> r1, r2)

  return a list of Booleans, one for every element in r1, true if that element > r2

(List<Real>l r1, List<Real> r2)

  if r1 and r2 have different length, throw an exception. Otherwise, return a list of Booleans, with every element result[i] being r1[i] > r2[i]

This is a simplification, in reality there is null-handling and integer-> real conversion involved, which is also missing in the specification I think.
We defined assertions/checks to only succeed if every Boolean in such a list is true or null/undefined (to not fail on data which is optional). Additionally in Archie every value returned contains every unique path in the data that was used to evaluate it, so you can see exactly for which data an assertion/check failed in the result, to notify the user where the problem occurs. Or to apply an assignment to the correct output if the output path does not match a single field. This last bit is implementation specific of course.

Much of this complexity can be avoided by not defining the operator on lists/sets, and requiring the for_all loop on lists or sets of data in the specification. This comes at a price, because the author of the expressions needs to understand more of the language and data structures. So we chose the second, since the previous draft specification did not specify at all how to handle these cases.

Undefined value handling is another subject, I have not checked yet if the new proposal solves it. We defined some functions to handle this explicitly if the automatic handling doesn’t do it ((input , alternative) -> return input unless input is undefined, then alternative), as well as some rounding functions.

If we were to allow the expression for_all $event in /data/events[id3] then we need to be clear on what it means: it actually refers to an object of type List<EVENT>, but do the members consist of EVENT objects found in data at the moment the expression is evaluated? Or just the statically defined members in the archetype - which can also be multiple, e.g. see the Apgar archetype, it has 1 min, 2 min, 5 min events?

You would need to evaluate on actual data. If you define it on the archetype data, you would need some kind of rules to convert it to an evaluation on actual data with different multiplicities than the rules specify, for example if events[id2] has occurrences > 1. Might be possible, I have not tried to define that. Would probably include some extra for_all loops plus some kind of validation errors for cases that cannot be converted.
So I would say always the data found at the moment which the expression is evaluated. You can still refer to separate statically defined members using their distinct node ids, and even those could have occurrences > 1 (not in the apgar example since those have occurrences {0..1} in the archetype).

Normally we want the processing of 'rules' expressions in archetypes to apply to the data when the archetype is being used in its normal role of creation / semantic validation at data commit time.

Agreed.

So it seems to me that if we want to support expressions like the above, we need to be able to do something like (don't worry about the concrete syntax too much, could easily be TS or java-flavoured):

use_model
    org.openehr.rm

data_context

    $xxx_events: List<EVENT>
    $item_aaa, $item_bbb, $item_ccc, $item_ddd: Real

definition

    check for_all event in $xxx_events:
        event/$item_aaa > event/$item_bbb + event/$item_ccc + event/$item_ddd

data_bindings -- pseudo-code for now

    $xxx_events -> /events[id2]
    $item_aaa -> /data/items/element[id7]/value/magnitude
    $item_bbb -> /data/items/element[id4]/value/magnitude
    $item_ccc -> /data/items/element[id5]/value/magnitude
    $item_ddd -> /data/items/element[id6]/value/magnitude

I don't know what this archetype is, so assume that $xxx_events, $item_aaa etc are more meaningful names.

That would leave $item_aaa globally defined as a path-reference, not matching a path in the archetype on its own. Type of variable being some kind of PATH_REFERENCE? That would solve the problem. Why not just inline the assignments, such as:

$events := /events[id2]

For_all event in $events:
    $item_aaa := /data/items/element[id7]/value/magnitude
    $item_bbb := /data/items/element[id4]/value/magnitude
    $item_ccc := /data/items/element[id5]/value/magnitude
    $item_ddd := /data/items/element[id6]/value/magnitude

    check $item_aaa > $item_bbb + $item_ccc + $item_ddd
end

Or if the end keyword is not desired, perhaps

$events := /events[id2]

For_all event in $events:
    ($item_aaa := $events/data/items/element[id7]/value/magnitude,
    $item_bbb := $events/data/items/element[id4]/value/magnitude,
    $item_ccc := $events/data/items/element[id5]/value/magnitude,
    $item_ddd := $events/data/items/element[id6]/value/magnitude)

    check $item_aaa > $item_bbb + $item_ccc + $item_ddd

although the second syntax is a bit less flexible when multiple statements need to be included within the for loop, you would need to repeat the for statement in that case.

The next problem you mentioned is:

PB: Note that a path that points to a single typed dvquantity in an archetype can still point to many items in the RM if somewhere up the tree there is a list or a set, for example more than one observation

So I think this implies an incorrect interpretation of this kind of code within an archetype. It can't be understood as simultaneously applying to multiple Observations if it is within an Observation archetype, only to one OBSERVATION instance at a time - usually one about to be committed.

You can still have Lists of things internal to the archetype, as shown above with the Events list, but to process the multiplicity, you would need to do as we have done and use for_all, or some other container-aware operator or function.

Of course, the context being a single instance of data – In case of an OBSERVATION a single OBSERVATION with possibly more than one event, or one event with a cluster that occurs twice. But in case of a COMPOSITION archetype it can be more than one OBSERVATION.

Anyway, does this get closer to the sense of what you would like to do? It's more than I had conceived of, so this is a useful challenge...

Much closer! Does not do everything (looks like not turing complete?) but functions will solve the remaining cases.

*From: *openEHR-technical <openehr-technical-bounces@lists.openehr.org> on behalf of Thomas Beale <thomas.beale@openehr.org>
*Reply-To: *For openEHR technical discussions <openehr-technical@lists.openehr.org>
*Date: *Saturday, 2 February 2019 at 15:01
*To: *"openehr-technical@lists.openehr.org" <openehr-technical@lists.openehr.org>
*Subject: *Re: Rules in archetypes - what are the requirements?

Assuming you meant to put 'id7' as the first one, I don't understand what this achieves beyond just:

/events[id2]/data/items/element[id7]/value/magnitude >
/events[id2]/data/items/element[id4]/value/magnitude +
/events[id2]/data/items/element[id5]/value/magnitude +
/events[id2]/data/items/element[id6]/value/magnitude

which says the same thing, for all events in the runtime data that conform to the /events[id2] branch of the structure.

Since the occurrences of events[id2] can be more than one, /events[id2]/data/items/element[id7]/value/magnitude in the execution environment (actual data) maps to a List<Real>.

well it could be understood that way - that would be to literally interpret it as a runtime path. The way I think of it is to mean 'this condition xyz must hold true' for each instance to which it applies. This greatly simplifies things - otherwise you end up in a complicated place like you have described below.

That means you need to define operators such as +, > and = on a list of reals. Or to define somehow that a statement containing only path bindings within a single multiply-occurring structure will mean that it gets evaluated for each occurrence of such a structure. The second case is complicated if you need to include data outside /events[id2] in your expression. A real world use case would be data in /protocol, which can influence the interpretation of event data, but is outside of the event.
So we did the first in Archie, with a bit of tricks to make this work for assignments. For example, how the plus operator is interpreted in Archie:

+(Real r1, Real r2)
return the sum both numbers
...
Much of this complexity can be avoided by not defining the operator on lists/sets, and requiring the for_all loop on lists or sets of data in the specification. This comes at a price, because the author of the expressions needs to understand more of the language and data structures. So we chose the second, since the previous draft specification did not specify at all how to handle these cases.

Undefined value handling is another subject, I have not checked yet if the new proposal solves it. We defined some functions to handle this explicitly if the automatic handling doesn’t do it ((input , alternative) -> return input unless input is undefined, then alternative), as well as some rounding functions.

the Expression Language draft <https://specifications.openehr.org/releases/LANG/latest/expression_language.html#_defined_predicate&gt; has the defined() predicate which I think should do the job.

If we were to allow the expression for_all $event in /data/events[id3] then we need to be clear on what it means: it actually refers to an object of type List<EVENT>, but do the members consist of EVENT objects found in data at the moment the expression is evaluated? Or just the statically defined members in the archetype - which can also be multiple, e.g. see the Apgar archetype, it has 1 min, 2 min, 5 min events?

You would need to evaluate on actual data. If you define it on the archetype data, you would need some kind of rules to convert it to an evaluation on actual data with different multiplicities than the rules specify, for example if events[id2] has occurrences > 1. Might be possible, I have not tried to define that. Would probably include some extra for_all loops plus some kind of validation errors for cases that cannot be converted.
So I would say always the data found at the moment which the expression is evaluated. You can still refer to separate statically defined members using their distinct node ids, and even those could have occurrences > 1 (not in the apgar example since those have occurrences {0..1} in the archetype).

Normally we want the processing of 'rules' expressions in archetypes to apply to the data when the archetype is being used in its normal role of creation / semantic validation at data commit time.

Agreed.

So it seems to me that if we want to support expressions like the above, we need to be able to do something like (don't worry about the concrete syntax too much, could easily be TS or java-flavoured):

*use_model*
org.openehr.rm

*data_context*

$xxx_events: List<EVENT>
$item_aaa, $item_bbb, $item_ccc, $item_ddd: Real

*definition*

check for_all event in $xxx_events:
event/$item_aaa > event/$item_bbb + event/$item_ccc + event/$item_ddd

*data_bindings*-- pseudo-code for now

$xxx_events -> /events[id2]
$item_aaa -> /data/items/element[id7]/value/magnitude
$item_bbb -> /data/items/element[id4]/value/magnitude
$item_ccc -> /data/items/element[id5]/value/magnitude
$item_ddd -> /data/items/element[id6]/value/magnitude

I don't know what this archetype is, so assume that $xxx_events, $item_aaa etc are more meaningful names.

That would leave $item_aaa globally defined as a path-reference, not matching a path in the archetype on its own. Type of variable being some kind of PATH_REFERENCE?

It would still be Real. The idea is that the path is relative to an EVENT, although that is not clear from the above. Perhaps something like:

$item_aaa -> (EVENT)/data/items/element[id7]/value/magnitude

That would solve the problem. Why not just inline the assignments, such as:

$events := /events[id2]

this was in a previous draft. But I am pretty convinced we don't want to mix paths in with the main 'code'. This is particularly the case if we want to use TypeScript or some other mainstream language to write the main statements in - the bindings need to be separate.

I'll have more of a think about how the bindings should work.

- thomas

Assuming you meant to put 'id7' as the first one, I don't understand what this achieves beyond just:

/events[id2]/data/items/element[id7]/value/magnitude >
        /events[id2]/data/items/element[id4]/value/magnitude +
        /events[id2]/data/items/element[id5]/value/magnitude +
        /events[id2]/data/items/element[id6]/value/magnitude

which says the same thing, for all events in the runtime data that conform to the /events[id2] branch of the structure.

Since the occurrences of events[id2] can be more than one, /events[id2]/data/items/element[id7]/value/magnitude in the execution environment (actual data) maps to a List<Real>.

well it could be understood that way - that would be to literally interpret it as a runtime path. The way I think of it is to mean 'this condition xyz must hold true' for each instance to which it applies. This greatly simplifies things - otherwise you end up in a complicated place like you have described below.

If not runtime but archetype paths, eventually you need to apply them to runtime data to evaluate. There are several ways of doing that – one way is what I did in Archie. Another one that I considered was automatically detecting common parent objects with effective_occurrences potentially > 1 and automatically adding for_all, plus some validation – less code, but more complex code. Another one is requiring for_all statements written by the rule modeler/developer (this is not possible in the published draft without your relative path proposal below). There are more ways to do this. Each one will operate differently in different edge cases, if this is not specified.

If however you do your ‘this path is relative to the other one’ context binding below, with a for_all that works with these path bindings, I think that should solve most of the problems.

the Expression Language draft<https://specifications.openehr.org/releases/LANG/latest/expression_language.html#_defined_predicate&gt; has the defined() predicate which I think should do the job.

It should, when combined with the new if-statement that is much more powerful than the implies-statement in the previous version.

However, it has some interesting implications when combined with the “'this condition xyz must hold true for each instance to which it applies”-idea specified above if not combined with an explicit for_all. Because also the corresponding defined()-check must be done for each instance. That’s the reason I integrated null-handling as ‘if an assertion/check has a null value, that means it’s missing data required to evaluate it. So the assertions is not relevant, meaning that it passes’. Then you can manually specify exists/defined conditions if you need them.

It would still be Real. The idea is that the path is relative to an EVENT, although that is not clear from the above. Perhaps something like:

$item_aaa -> (EVENT)/data/items/element[id7]/value/magnitude

That would solve it.

That would solve the problem. Why not just inline the assignments, such as:

$events := /events[id2]

this was in a previous draft. But I am pretty convinced we don't want to mix paths in with the main 'code'. This is particularly the case if we want to use TypeScript or some other mainstream language to write the main statements in - the bindings need to be separate.

I'll have more of a think about how the bindings should work.

If typescript/javascript (typescript compiles to javascript before execution), that will change quite a lot about the discussion before.

Since a path query is just a string, it’s not so hard to create an (included) path lookup function and an ‘assign value to this path’ function.

Regards,

Pieter Bos