Page tree

4464 View 11 Comment In discussion Comments enabled In the category: Undefined

Discussion about extending the ECL to support sub-expressions

Contributors (2)


  1. The problem

    In the context of the Australian Medicines Terminology, there is a need to be able to query the terminology content to support the "dispense" use case.

    This use case can be characterised as starting with a unbranded (MPP) medication concept (ie the prescribed medication), find all branded, container-ed concepts (CTPPs) that are compatible (ie candidate dispensable medications).

    In AMT, CTPP isa TPP isa MPP and all the medication concepts have "containining" / "some" semantics.  So multi-ingredient concepts are children of single ingredient concepts.  Thus to write an ECL to find suitable CTPPs you need to be able to exclude multi-ingredient children.

    In the older proposed query language you could do this with a query like:

    Exclude( Descendants( x ), DescendantsAndSelf ( TPP ) )

    where 'x' is the prescribed (MPP) concept, which identifies the multi-ingredient MPPs whose CTPP descendants we want to exclude from the final result. We can express this in ECL

    < x - << TPP

    But then we need to write the complete query:

    Exclude( Intersection(Descendants(x),Descendants(CTPP)), Descendants(Exclude(Descendants(x),DescendantsAndSelf(TPP))) )

    Transliterating this into ECL gives:

    (< x AND < CTPP) - < (< x - << TPP)

    But this is not a valid ECL expression because we cannot apply operators like <, <<, <!, etc to expressions.

    This lack of composability imposes a significant limitation on the expressive power of the language.

    The proposal

    Change the grammar rule:

    simpleExpressionConstraint = [constraintOperator ws] focusConcept


    simpleExpressionConstraint = [constraintOperator ws] ( focusConcept / "(" expressionConstraint ")" )

    where the semantics is just to take the union of the results of applying the constraintOperator to each concept matching the expressionConstraint.


  2. Thanks very much for the feedback Michael! And good timing - as I was planning to finalize ECL v1.1 this week.

    I will have a more detailed look at your suggestion over the next couple of days, and let you know my thoughts.

    Kind regards,

  3. Hi Michael,

    Could you please help me understand your requirements better. I think I got a bit lost when you explained why subtracting TPP (Trade Product Packs) removes the products with additional active ingredients (that weren't prescribed). Note 1: I understand that you are using existential restriction, and that subtypes may contain a larger set of active ingredients ... just not sure how subtracting "<< TPP" removes the products you don't want prescribed. Note 2: Imagine how much easier this would be if the prescribed concept used universal restriction. (smile)

    I understood from your post that 'x' represents the specific MPP concept that was prescribed (e.g. |Amoxicillin 500 mg capsule [25 capsules]|), and I think TPP represents the class of product pack concepts that have a trade name? (e.g. |AMOXIL (Amoxicilin) 500 mg capsule (25 capsules)|) .... so I assume that << TPP is the set of TPP product concepts (i.e. same as "^ TPP" if TPP was a refset of Trade Product Pack concepts)? With this in mind, I'm a bit confused as to why '< x AND < CTPP' removes the products with more active ingredients than you need. Obviously I'm missing something here.

    If subtracting '<< TPP' is actually what you're after, then could you please help me understand why "(< x AND < CTPP) MINUS << TPP" doesn't give you the same result as the ECL you are suggesting?

    However, based on your explanation that (1) CTPP concepts are subtypes of TPP concepts, (2) TPP concepts are subtypes of MPP and that (3) x is an MPP, I would have expected the following ECL to find the appropriate CTPP concepts with the correct set of active ingredients:

        (< x AND < CTPP) MINUS (< x : 127489000 |Has active ingredient| != (<< 105590001 |substance|:R 127489000 |has active ingredient| =x ))

    or using the new 'syntactic sugar':

        (< x AND < CTPP) MINUS (< x : 127489000 |Has active ingredient| != << (x .127489000 |has active ingredient|))

    That is, the set of subtypes of x, which are CTPP (Containered Trade Product Pack) concepts, minus the concepts that have an active ingredient that is not in the set of x's active ingredients.

    Kind regards,



  4. Hi Linda,

    Yes, there's a subtlety in this that I had to re-construct when I revisited the query.  It turns on the fact that, due to knowledge of AMT content, the only TTP descendants of a TPP (ie those that are not also CTPPs) must have an extra ingredient.

    However, following your approach of working directly with the ingredients, combining != with reverse relationships, and correcting the codes to account for the AMT drug model (ie using the AU substance code rather than the INT substance code and dealing with the indirection to ingredients via MPUUs/TPUUs, I now have:

    (< [[@x]] AND < 30537011000036101 |CTPP|) MINUS
    (< [[@x]] : << 30348011000036104 |hasMPUU| = (
    < 30450011000036109 |MPUU| : 700000081000036101 |Has intended active ingredient| !=
    (<< 30388011000036105 |substance|:R 700000081000036101 |has intended active ingredient| = (
    < 30450011000036109 |MPUU| :R 30348011000036104 |hasMPUU| = [[@x]] )

    Which, I think, works very nicely and doesn't also rely on the assumption that an MPP immediate child that is not a also TPP must have an extra active ingredient.


    1. Glad to hear it works (with the necessary modifications).

      Kind regards,


  5. A common need for us is to identify the most general or most specific concepts from a set of concepts. Sometimes these come from a reference set (^ x), sometimes from a more general ECL expression.

    If x is that more general expression, then what we want to compute is x - > ( x ) and x - < ( x )

    As a specific example, "what are the most general body sites referred to by concepts in the reference set r" - what I'd like to be able to write is ( (^r) . finding_site ) - < ( (^r) . finding_site )

    Is that the sort of example you're looking for?

  6. Hi Michael,

    Yes - This is a good example thanks. I can't think of any other way of expressing this, and it does require the 'descendant' operator to be applied to a non-simple expression constraint.

    Thanks! I will take a look at the ABNF.

    Kind regards,


  7. Michael,

    I have used the ABNF you suggested to support constraint operators on nested subexpressions .... except for the minor amendment of allowing ws (optional white space) after an open bracket and before a closed bracket :

    simpleExpressionConstraint = [constraintOperator ws] (eclFocusConcept / "(" ws expressionConstraint ws ")")

    It's now tested and complete. Please check and confirm.


    Kind regards,


  8. I now have a use case for sub-expressions in the attributeName part of an expression.

    The use case is as follows.  I have a focus concept and a set of attributes.  I want to find all descendantsOrSelf of the focus concept that have exactly these attributes and no others.

    So, I want something like:

    << 125605004 |Fracture of bone| :
    [1..1] 363698007 |Finding site| = 23416004 |Structure of ulna|,
    [1..1] 116676008 |Associated morphology| = 72704001 |Fracture|,
    [0..0] 363698007 |Finding site| != 23416004 |Structure of ulna|,
    [0..0] 116676008 |Associated morphology| != 72704001 |Fracture|,
    [2..2] << 410662002 |Concept model attribute| = *

    But this relies on the magic number '2' in this case, and won't easily generalise to cases where, for example, a single finding site might appear in multiple groups.  What I think I need to be able to write is something like the following (which is not currently valid ECL):

    << 125605004 |Fracture of bone| :
    [1..*] 363698007 |Finding site| = 23416004 |Structure of ulna|,
    [1..*] 116676008 |Associated morphology| = 72704001 |Fracture|,
    [0..0] 363698007 |Finding site| != 23416004 |Structure of ulna|,
    [0..0] 116676008 |Associated morphology| != 72704001 |Fracture|,
    [0..0] (<<410662002 |Concept model attribute| MINUS 363698007 |Finding site| MINUS 16676008 |Associated morphology|) = *

    Update: cardinalities in the example above have been corrected to show [1..*] rather than the original cut-and-paste error of [1..1].

  9. Hi Michael,

    Thanks for the interesting problem!

    First, I'd like to try to understand your examples. The ECL that you've proposed seems to have redundant lines (unless I'm missing something).

    Your first example reads:

          Descendant or self of Fracture of bone  (ECL line 1)

          ... that has exactly one finding site relationship with a value of |Structure of ulna| (ECL line 2)

          ... and NO finding site relationships with a value that is NOT |Structure of ulna| (ECL line 4)

          .... and exactly one associated morphology relationship with a value of |Fracture| (ECL line 3)

          .... and NO associated morphology relationships with a value that is NOT |Fracture| (ECL line 5)

          .... and exactly 2 relationships (ECL line 6)

    So, it seems unnecessary to include lines 4 and 5 (with the [0..0] cardinality), as the cardinality of [2..2] (in line 6) excludes any other relationships.

    In the second example, you mention in the text above that example 1 "won't easily generalise to cases where, for example, a single finding site might appear in multiple groups." However, the example does not include any braces, and it implies that there is exactly 1 finding site (with value |Structure of ulna| and no other values are allowed), and exactly 1 associated morphology (with the value |Fracture| and no other). I suspect, however, that you may have intended to delete lines 4 and 5 in the second example and add relationship group braces (to ensure that the fracture applies to the ulna)? ... and that you were aiming for something more like the following (which allows other finding sites or associated morphologies to be present .... but requires the fracture to apply to the ulna):


    << 125605004 |Fracture of bone| :
    {[1..1] 363698007 |Finding site| = 23416004 |Structure of ulna|,
    [1..1] 116676008 |Associated morphology| = 72704001 |Fracture|},
    [0..0] (<<410662002 |Concept model attribute| MINUS 363698007 |Finding site| MINUS 16676008 |Associated morphology|) = *

    As you know, this is not valid ECL ... but is that what you're looking for?

    Extending the ECL is certainly one option, but have you considered using an expression template instead (which only allows expressions that match the template structure, thereby excluding additional attributes)?


    Kind regards,

  10. Thanks Linda, the error I made in the second example was to have 1..1 rather than 1..* for the first two parts

    Consider 57571002 |Crossed straight leg test| where 

    Where the method occurs twice but with different sites.

    regarding role grouping, in my use case I have no knowledge of this.  Also, wrt using the template language, that would only get me a post coordinated expression, whereas I'm looking to match a pre coordinated concept.