Agreeing on optional user interface hints in templates

I don’t know about ADL2.

matches is for sure there, then in AOM we have existence in the constraint model, but not sure if implies exists is something already defined.

A related question would be if this is the only rule form we will have or if there are going to be more complex rules, just to check how syntax and underlying API would look like.

With API I’m referring to the classes and functions that implement the logic, once parsed from the syntax in the AST. The ASW is acutally mapped to something that can be instantiated, invoked, and that client code can use the results for such invoked methods/functions. So I guess it is what is binded by the lexer from the parsed DSL if I didn’t mess the terms :slight_smile:

I think this is a reasonable short-term solution for non-rule-supporting systems. I need to think a bit on the details. It would still be better to get to a syntax that didn’t have archetype paths inline (these greatly complicate the semantics) and used bindings for them instead, as per this version of the older Expression Language.

Pretty much everything turns into calls to functions that are either defined on objects of the RM in question, or underlying primitive types available in the language of the execution engine implementation. For example, traversing a piece of AST representing the expression systolic + 0.33 * (systolic - diastolic) will resolve to calls to Real.add() etc. Normally you will replace that with calls to the built-in types.

You can guess how it could be executed from the default generate AST:

The BMM AST is a bit smarter and allows more interesting things to be achieved, mainly to do with validation.

Calls to external functions should also be supported, e.g. to enable function that computes e.g. BMI or pregnancy risk based on certain variables.

In my proposed future EL, decision tables (aka if/then chains and case statements) will also be supported e.g.@

Result := case ipi_raw_score in
        =======================================
        |0..1|  : #ipi_low_risk,
        |2|     : #ipi_intermediate_low_risk,
        |3|     : #ipi_intermediate_high_risk,
        |4..5|  : #ipi_high_risk
        =======================================
    ;

and its default AST:

Also other nice structures:

score := Result.add (
        ---------------------------------------------
        basic.gender = #male                 ? 1 : 0,
        age_score                                   ,
        has_congestive_heart_failure         ? 1 : 0,
        has_hypertension                     ? 1 : 0,
        has_stroke_TIA_thromboembolism       ? 2 : 0,
        has_vascular_disease                 ? 1 : 0,
        has_diabetes                         ? 1 : 0
        ---------------------------------------------
    )
    ;

I’m getting ahead of the original question here of course :wink:

Good suggestion, it also matches tha approach in Cambio’s form editor (see screenshot below) where each node used in a form is assigned a short default id (based on the template’s node name) that can be changed manually.

Just as in ADL Cambio’s rules (called actions) are collected in a single place (not attached to each node).

Edited at 18:00, adding info aboout Better:
In Better’s form editor the rules are anchored to a specific node but can read and modify any node (see “Method code equals Auscultation” in screenshot below), not just the node it is anchored to, so I guess ther rules are actually ass free-standing as in Cambio’s solution

For template tools that do not handle rules (yet) we could then perhaps add an “id” annotation that will through post-processing create a symbol binding based on the path of the annotated node.

Edited: added the (namespace)prefix ‘a.’ to the examples below to reduce risk of name clashes and to make it work better in Ocean’s template designer tool. The letter ‘a’ being the first in the word ‘automation’

Example based on the structrue from post 13:

items
    +--- tobacco user: Y/N <-- ANNOTATION POSITION A
    +--- tobacco use details <-- ANNOTATION POSITION B
               +--- amount per week
               +--- cessation attempts
               |          +--- xxxx
               +--- yyy
Annotation position Annotation Key Annotation value
A a.id tobacco_user
B a.rule tobacco_user = ‘Y’ implies exists THIS

Or if choosing to manually giving an ID to both nodes, more like below. Below an alternative rule version using the ADL2 syntax desctibed in [Archetype Definition Language 2 (ADL2)](https://section 7.11.2.2. (Value-dependent Existence)) and a cambio inspired one is also provided

Annotation position Annotation Key Annotation value
A a.id tobacco_user
B a.id tobacco_details
root of template? a.rule.adl tobacco_user = ‘Y’ implies exists tobacco_details
root of template? a.rule.adl2 check tobacco_user = ‘Y’ implies defined (tobacco_details)
root of template? a.rule.cambio-style tobacco_user == ‘Y’ ASSIGN tobacco_details.hidden = false OTHERWISE tobacco_details.hidden = true
root of template? a.rule.better-style tobacco_user = ‘Y’ THEN tobacco_details show OTHERWISE tobacco_details hide

In the Cambio/Better-style rows above, the CAPITALIZED words correspond to the rule section headers in the rule editors, see screenshots above. I skipped the first (constant) “WHEN” in order to sugest shorter syntax.

1 Like

Just to go a bit further on this: the reason it complicates things is that the mapping between archetype paths and data paths is in general 1:N, due to multiplicities in runtime data. The other thing to remember is that expression languages (at least typical ones) work with data, i.e. real values, not ‘models’.

So a symbolic variable like heart_rate mentioned in an expression has to be populated with an actual heart rate value at runtime. What will this be? Well the binding shows at what archetype path it can be found in openEHR data (nice!) but we still have the problem of dealing with there being e.g. 50 heart rate values over time, all at that same archetype path. Which one(s) does the expression execution engine use? In some cases, there is an implied ‘for all’ operator (as @pieterbos pointed out a long time ago), but that is not universal. That means the EL engine potentially has to execute an expression 50 times or do something else that is not at all obvious from the expression itself. The expression itself is clear however; the challenge lies in how it will be applied to data. I believe we want to separate that complication from the syntax and semantics of expressions, so that expressions are just like they are in other languages.

I won’t bore everyone with further details, hopefully this gives a clue as to why bindings are important. For the masochistic, a long discussion here.

1 Like

Something like the second suggestion in that post pretty closely simulates the bindings + symbolic approach I have advocated. It means that the rules are readable, which I think helps a lot.

A for_all and perhaps an exists block, rather than a for_all just in a statement, in which one can define variables as pointing to a path, would result in a small modification to the current grammar resulting in much more easy to read rules. Then rules could be written so that paths appear only in binding to variables, and assertions can be written only with variables, and nearly all variables could point to single values instead of lists or other collections. Without any complex ‘operators have to be defined on lists as well as single values’ logic, as we currently have.

However, if data binding is made separate from the language, and you cannot use a path anywhere else in the language, this will complicate these issues very much - then you cannot easily express ‘I want these rules to apply to this event in this observation only, even if the event occurs multiple times, but I still want to reference other data in the observation outside of the single event’.

if data binding is separate, but sub-paths are still possible, and a for_all block is added, then the problem is also easily solved.

Wouldn’t you just create a more specific binding for that particular event, to a more specific variable, e.g. ‘Apgar_heartrate_5_mins’ or similar, when you might already have ‘Apgar_heartrate’? Or you are talking about apply these rules in the sub-tree at this path? For the latter I think you would define a binding to the container object (HISTORY or whatever) under which you want a for_all or there_exists to execute.

It’s an interesting question generally as to whether the binding needs to be separate from the language. I have made the assumption that it does because the bindings in GDL, Task Planning and Archetypes are all completely different.

I am more inclined to this approach - because with only symbolic vars in the EL, EL texts are least is readable and maintainable. This goes back to our discussion last year.

I had another thought on this - I just realised you probably thought that I propose no path access method at all within a revised EL. The approach I propose is more like Xpath. Firstly it is access to nodes below some root object within data, i.e. instance level paths. In Xpath, as we all know, this is done with predicates in . And predicates can be (nearly) any expression to get to the intended sub-node, including path-patterns, but also other expressions, e.g. the simple ones like [1] (the first item). If we treat an inline archetype path as a path pattern predicate we then achieve what you want (which I agree with of course :wink: - but the semantics are now clearer - they are as in Xpath - if the path pattern matches 10 real paths in the current data instance, then that’s what you get.

The new proposed EL has this kind of functionality, see here (but no path example there yet).

The paths by the way would have ‘.’ separators rather than ‘/’, indicating formal RM structure access.

A side note: In Ocean Template designer 3.1 (from 2015?) you can add configuration of annotation types (in subcatefories called sets), and say what elements they apply to, and optionally what the allowed values are. As default on installation the Categories “Notes” and “GUI directives” are included, see screenshot.

Does anybody remember what the “Group” annotation was intended for?

Also, when trying to load templates with “plain” annotation names (like “fhir_mapping”) the tool gives an error. So the annotation names have to be changed to be in a prefix.suffix format. (I assume that is to cater for the annotation sets.

Prefixing the experimental annotations discussed in this thread may be a good thing anyway.

image

I don’t know, but assume that Group may be a hint that several elements belong together in some way.
Not sure this part was ever used much though.
There is also a newer more advanced Annotation Designer in Template Designer nowadays, but independent of that I believe that the annotations were extended so that simple keys like fhir_mapping can be used (or at least read) as well without a prefix in the latest version.

Maybe it is the ‘group’ constraint described here.

ADL 2/OPT 2 annotations have the same concept, called groups, although in the specification currently only ‘documentation’ is defined. See Archetype Definition Language 2 (ADL2) .
@thomas.beale the RESOURCE_ANNOTATIONS class in the resource specification seems to have disappeared in the latest specification.

I think with the current ADL 2 rules, plus a well defined set of annotations, it is possible to create interactive templates with quite a lot of user interface hints. Rules and annotations have a different purpose here: rules can be used for extended validations and interactive forms. Annotations can be used to define user interface hints, such as which UI element should be rendered. That means one is not an easy replacement for the other.

ADL2 relies on the BASE Resource specification now.

Agree - although annotations get used for other things unrelated to UI as well including pure documentation.

Yes, thanks. But the definition of the RESOURCE_ANNOTATIONS class is missing from that specification.

Ah - I see a class include is missing from the spec - it’s in the UML diagram. I’ll fix that.

EDIT: Fixed now in latest version.

1 Like

@thomas.beale regarding an elsewhere mentioned idea of a possible “ADL3” serialisation/variant of ADL2 semantics, perhaps something like the above mentioned…

image

…could be used to have consistent mapping between at/id codes of nodes and “readable” IDs?

I think that is a good idea. Such an annotation - or feature, if ADL 3 - could also be very useful for stable developer friendly ‘simple’ API generation, that remains the same even if the archetype gets textual changes. Which as far as I know is not really the case for the current json web templates plus APIs that often appear to be used. Also it could improve the field names in generated APIs.

It makes it more complicated to design archetypes though, unless these values can be auto-generated from the textual descriptions. Now clinicians without technical experience can design archetypes, you may need a more technical person to design and maintain sensible textual ids. The improved developer-friendlyness could very well be worth it…

1 Like