tcl scripting help

Homepage Clovertech Forums Read Only Archives Cloverleaf Cloverleaf tcl scripting help

  • Creator
    Topic
  • #51303
    Tom Arrowsmith
    Participant

    I need to write a script that goes something like this:

    If OBR-4.2 does not contain ECG or EKG then kill the message.

    The situation may evolve to include a 3rd or 4th “or”.

    I’m used to using scripts that revolve around the “equals” concept (cequal) and have not yet written one to catch a sequence of characters no matter where they occur in the data element. I say “contains” but the keyword may well be different….

    Any help would be appreciated.

    Thanks!

    Tom Arrowsmith

Viewing 16 reply threads
  • Author
    Replies
    • #69599
      Robert Kersemakers
      Participant

      Hi Tom,

      I think you are looking for the ‘regexp’ command.

      if {![regexp $filter $field]} {

       

      }

      Your $filter can look like “ECG|EKG”. This would mean that if the $field contains ECG OR EKG anywhere in the field, then the regexp is valid.

      Hope this helps.

      Zuyderland Medisch Centrum; Heerlen/Sittard; The Netherlands

    • #69600
      Keith McLeod
      Participant

      Another option might be lcontain meaning list contains.

      Syntax: lcontain list element

      set your_list {ECG EKG}

      If {![lcontain $your_list $your_field]} {

         kill message

      }

    • #69601
      Tom Rioux
      Participant

      If you are using an xlate, this can also be done inside the xlate IF statement using “ct” for contains.   Then if it meets your criteria, just suppress the message.  I believe I have used this somewhere and can find an example if you like.

      Just a note, when I suppress a message within an xlate, I also include an echo statement that will write to the log file telling me why the message in question was suppressed.

      Thanks….

      Tom Rioux

    • #69602
      Tom Arrowsmith
      Participant

      Thanks Tom,

      Actually, my predecessor who mentored me a year ago when I took this job left me with the impression that the only alternative for killing messages was a tcl script.

      This is the second time I’ve heard that messages can be suppressed form within an xlate.

      Can you tell me what the pro’s and con’s are of each way  – I’d like to learn if using an xlate and suppressing is a methodology I should be taking advantage of.

      I would be very interested to see your example – and also interested in how you write it out to a log file.

      Please feel free to call me directly if that is easier …

      Anyone else who has thoughts on “xlate-suppress” vs “tcl kill”, please chip in!

      Tom – (703) 779-5474

    • #69603
      Robert Kersemakers
      Participant

      I always try to kill the messages before the Xlate. Xlate takes a lot of performance (or so I hear), so it’s better to get rid of messages before they are translated.

      But sometimes the conditions under which you want to kill the message are too complicated for a (generic) tclproc, so I have some Xlates where I kill certain messages inside the Xlate.

      Zuyderland Medisch Centrum; Heerlen/Sittard; The Netherlands

    • #69604
      Tom Arrowsmith
      Participant

      I would love to see an example.

      Thanks!

    • #69605
      Vince Angulo
      Participant

      I often use a basic xlate construct like this:

      COPY =true => @SendMsg

      COMMENT “++++ Start Exclusion Criteria +++++”

      COMMENT “Exclude transactions where visit type is P or event is A05”

      IF PV1.00132.[0] eq =P || EVN.00099.[0] eq =A05

      >>>> COPY =false => @SendMsg

      ….other criteria follow….

      COMMENT “++++ End Exclusion Criteria +++++”

      IF @SendMsg eq =true

      >>>> ….construct msg with copies and iterates, etc….

      ELSE

      >>>> SUPPRESS

      Hope this helps.

    • #69606
      Mason Miller
      Participant

      here is a screenshot from xlate[/code]

    • #69607
      Mason Miller
      Participant

      attachment did not work here it is

    • #69608
      Troy Morton
      Participant

      I agree that Xlate is not the best place to KILL messages.  I believe it should be avoided if at all possible (I can’t think of any reason it wouldn’t be possible).

      Here is an example script where I’m KILLing the message based on mutliple criteria.

      Code:

      ################################################################################
      # Name: tps_TRvarianFilter_B_a
      # Purpose:
      #
      #    Filters ADT messages
      #
      # UPoC type: tps
      # Args: tps keyedlist containing the following keys:
      #       MODE    run mode (”start”, “run” or “time”)
      #       MSGID   message handle
      #       ARGS    TOFILE
      #    
      #
      #

      proc tps_TRvarianFilter_B_a { args } {

      global HciConnName HciSite

      keylget args MODE mode               ;# Fetch mode

      set dispList {} ;# Nothing to return

      switch -exact — $mode {

       start {
       # Perform special init functions
       # N.B.: there may or may not be a MSGID key in args
       }

       run {
       
       # init variables
       set module “tps_TRvarianFilter_B_a”
       set hosp “SHEC”
       set killreason “”
       set msgkill 0
       set blnTrcWrite 0
       set mh “”
       set msgtext “”
       set mshseg “”
       set pv1seg “”
       set eventtype “”
       set patclass “”
       set patloc “”
       set patloc1 “”
       set patloc2 “”
       set now [clock format [clock seconds] -f “%m/%d/%Y %H:%M:%S”]

       
       
       # Get arguments
       keylget args MSGID mh
       
       # Read message from handle
       set msgtext [msgget -cvtnull “” $mh]
       
       # Pull out the fields needed for our filters
       set mshseg [getHL7Segment $msgtext MSH]
       set pv1seg [getHL7Segment $msgtext PV1]
       
       set cid       [getHL7Field $mshseg 9]
       set eventtype [getHL7Field $mshseg 8]
       set patclass  [getHL7Field $pv1seg 2]
       set patloc    [getHL7Field $pv1seg 3]
       set patloc1   [getHL7Comp $pv1seg 3 1]
       set patloc2   [getHL7Comp $pv1seg 3 2]
       
      #  echo ”      cid: $cid”
      #  echo “eventtype: $eventtype”
      #  echo ” patclass: $patclass”
      #  echo ”   patloc: $patloc”
      #  echo ”  patloc1: $patloc1″
      #  echo ”  patloc2: $patloc2n”
       
       # Filter 1 : If EventType is A29, only keep if PatClass is “M” and PatLoc is “RCC   ”
       if { $eventtype == “ADT^A29” && ( $patclass != “M” || ( $patclass == “M” && $patloc1 != “RCC   ” ) ) } {
        set killreason “If EventType is A29, only keep if PatClass is “M” and Patient Location is “RCC   “.”
        set msgkill 1
       }
       
       # Filter 2 : If EventType is A05 or A08 and PatClass is “B”, only keep if PatLoc2 is “ONCL”
       if { ( $eventtype == “ADT^A05” || $eventtype == “ADT^A08” ) && $patclass == “B” && $patloc2 != “ONCL” } {
        set killreason “If EventType is A05 or A08 and PatClass is “B”, only keep if Patient Clinic Location is “ONCL”.”
        set msgkill 1
       }
       
       # Filter 3 : If EventType is not A29 and PatClass is M, only keep if PatLoc is “CLINIC^ONCL”
       if { ( $eventtype != “ADT^A29” && $patclass == “M” ) && $patloc != “CLINIC^ONCL” } {
        set killreason “If EventType is not A29 and PatClass is “M”, only keep if Patient Location is “CLINIC^ONCL”.”
        set msgkill 1
       }
       
       # Filter 4 : If EventType is A08, do not send if PatClas is F, G, 3 or 4.
       if { $eventtype == “ADT^A08” && [lsearch “F G 3 4” $patclass] != -1 } {
        set killreason “If EventType is A08, do not send if PatClas is F, G, 3 or 4.”
        set msgkill 1
       }
       
       if { $msgkill } {
       
        # Set disposition to KILL
        lappend dispList “KILL $mh”
                     
       } else {
        lappend dispList “CONTINUE $mh”
       }
       
       }

       time {
       # Timer-based processing
       # N.B.: there may or may not be a MSGID key in args
       }
       
       shutdown {
       # Doing some clean-up work
       }
       
      }

      return $dispList

      }

    • #69609
      Russ Ross
      Participant

      Using a trxID proc for killing messages can also be a handy way when faced with multiple complex conditions.

      I posted an example I ended up doing for a pharmacy integration at the following URL:

      http://clovertech.infor.com/viewtopic.php?t=1562” class=”bbcode_url”>http://clovertech.infor.com/viewtopic.php?t=1562

      Russ Ross
      RussRoss318@gmail.com

    • #69610
      Jim Kosloskey
      Participant

      While I prefer filtering messages outside the Xlate (specifically in the Pre-Xlate Routing), I also understand not every shop has the Tcl skills to do this.

      However, the demands do not wait for skill level upgrading.

      So in my mind a case can be made for doing filtering in the Xlate.

      It probably would be a good idea to make sure the fact that a particular Xlate filters mesages and the filtering conditions should be known outside of the Xlate (like in some integration documentation or perhaps the xlate name itself).

      That way one can know without having to investigate that a given Xlate cannot be used in any global fashion.

      Also, lot’s of commenting inside the Xlate should be de rigueur as well as trying to place all of the filtering logic together so it is easy to find.

      Having said the above, there are some generic filtering procs in the community that can be used to do a lot (if not all) of the message filtering required.

      But even then, that may challenge someone who has not yet attained the requisite skill level.

      email: jim.kosloskey@jim-kosloskey.com

    • #69611
      Mason Miller
      Participant

      try this it should work for what you are tyring to do.

      Code:


      ######################################################################
      # Name: supress_adt_sc
      # Purpose: supress all adt transactions from soarian clinicals
      # UPoC type: tps
      # Args: tps keyedlist containing the following keys:
      #       MODE    run mode (”start”, “run” or “time”)
      #       MSGID   message handle
      #       ARGS    user-supplied arguments:
      #              
      #
      # Returns: tps disposition list:
      #          
      #

      proc tps_kill_ORR4_2 { args } {
         keylget args MODE mode               ;# Fetch mode

         set dispList {} ;# Nothing to return

         switch -exact — $mode {
             start {
                 # Perform special init functions
         # N.B.: there may or may not be a MSGID key in args
             }

              run {
                 # ‘run’ mode always has a MSGID; fetch and process it
                 
                 keylget args MSGID mh
      set msg [msgget $mh]
      set segmentList [split $msg r]
      set segment [lindex [lsearch -all -inline $segmentList OBR*]0]
      set obr4_2 “”
      set fieldSep [string index $msg 3]
      set subfieldsep [string index $msg 4]
      set fieldList [split $segment $fieldSep]
      set obr4 [lindex $fieldList 4]
      set obr4_2 [split $obr4 $subfieldsep]
      set obr4_2 [lindex $obr4_2 2]

      #echo $obr4
      #echo $obr4_2

      #if OBR4.2 is equal to EKG or ECG kill message!
      if { $obr4_2 == “EKG” || $obr4_2 == “ECG” } {
      set disp KILL
      #echo kill message!!

              } else {
          set disp CONTINUE
      #echo continue
             }
        lappend dispList “$disp $mh”
             }

             time {
                 # Timer-based processing
         # N.B.: there may or may not be a MSGID key in args
             }
             
             shutdown {
         # Doing some clean-up work
      }
         }

         return $dispList
      }

    • #69612
      Tom Arrowsmith
      Participant

      I have acheived the desired result by concatenating the OBR-4.3 and OBR-4.1 together to get a unique identifier for the orders, placing the result in the MSH-14, then using a table that identifies which of these resulting values should be recognized as ECG orders and if so, place the value EKG in the MSH-13.

      Then I have a more simple tcl kill script that kills any messages that do not have the value EKG in the MSH 13.

      So the pressures off….

      However, looking at the various solutions that people offered – I want to clarify that the alternative route I was looking to go down was how to kill based on the ECG or EKG values being somewhere embedded in a longer string. It seems that the folks that give the procedures names here have no idea of a naming convention – and the only thing I could of hung my hat on is the fact that somewhere in the procedure name the characters ECG or EKG where there.

      So I need the  language to do that in a tcl script – but as this was a production issue I decided to pursue the solution I knew would work (Now I have a table to maintain, though!)

      So now that the dust has settled, I would love to pursue the other method of identifying characters within a string – so that next time I can have a more elegant solution.

      Still open to comments… I am very thankful for the suggestions and help offered here! This forum has often provided me with workable solutions and has help me to progress with my skills.

      Thanks!

      Tom

    • #69613
      Mason Miller
      Participant

      if you are saying that if OBR4.2 contains EKG or ECG you may use someting like this

      Code:

      if { [string first “EKG” $obr4_2] !=-1 || [string first “ECG” $obr4_2] !=-1 } {
      set disp KILL
      echo kill message!!

              } else {
          set disp CONTINUE
      #echo continue

    • #69614
      Troy Morton
      Participant

      To find a string in another string, I use the “string first” command:

      string first string1 string2 ?startIndex?

      Search string2 for a sequence of characters that exactly match the characters in string1. If found, return the index of the first character in the first such match within string2. If not found, return -1. If startIndex is specified (in any of the forms accepted by the index method), then the search is constrained to start with the character in string2 specified by the index.

      string first a 0a23456789abcdef 5

      will return 10, but

      string first a 0123456789abcdef 11

      will return -1.

      Code:

      tcl>set mystring “0a23456789abcdef”
      tcl>string first a $mystring
      1
      tcl>string first a $mystring 5
      10
      tcl>string first a $mystring 11
      -1
      tcl>string first “bcd” $mystring
      11

      Hope this gives you some ideas.   😀

    • #69615
      Todd Lundstedt
      Participant

      We have a script in use widely here called “kill_hl7_val.tcl” in which you can input any segment, field position, and a list of values.  If any of those input values exist in that HL7 field, the message is killed.  We have a similar script called “accept_hl7_val.tcl” which will continue on the same criteria.  We haven’t had an overwhelming need to kill at a subcomponent level, yet.  But, because of your post, and because I had an hour or so left on this Friday afternoon, I decided to go that extra step.

      Note… the following script does NOT look at repeating fields… that will be a future need, I am sure.

      Code:

      ######################################################################
      # Name: kill_hl7_subcom_val
      # Purpose: Interrogate any segment/element for value and kill it.
      # UPoC type: tps
      # Args: tps keyedlist containing the following keys:
      #       MODE    run mode (”start”, “run” or “time”)
      #       MSGID   message handle
      #       ARGS    user-supplied arguments:
      #               Example:
      #                       {SEG OBR} {SEGELE 4} {SEGSUB 1} {SEGVAL {EKG ECG}}
      #                       code will kill records with OBR-4.2 = EKG or ECG
      #
      # Returns: tps disposition list:
      #          KILL or CONTINUE message.
      #

      proc kill_hl7_subcom_val { args } {
         keylget args MODE mode               ;# Fetch mode

         set dispList {} ;# Nothing to return

         switch -exact — $mode {
           start {
               # Perform special init functions
       # N.B.: there may or may not be a MSGID key in args
           }

           run {
             # ‘run’ mode always has a MSGID; fetch and process it
             keylget args MSGID mh
             keylget args ARGS.SEG SEG
             keylget args ARGS.SEGELE SEGele
             keylget args ARGS.SEGSUB SEGsub
             keylget args ARGS.SEGVAL SEGval
             set msg [msgget -cvtnull _ $mh]
             set sep [csubstr $msg 3 1]
             set subsep [csubstr $msg 4 1]
             set repsep [csubstr $msg 5 1]
             set segments [split $msg r]
             set SEGlistLength [llength $SEGval]
             #echo “kill_hl7_subcom_val SEG input =$SEG”
             #echo “kill_hl7_subcom_val SEGele input =$SEGele”
             #echo “kill_hl7_subcom_val SEGsub input =$SEGsub”
             #echo “kill_hl7_subcom_val SEGval input =$SEGval”
             #echo “segments is $segments”
             lappend dispList “CONTINUE $mh”
             foreach seg $segments {
               # Check to see if SEGele equals one of the input values
               if [cequal $seg “”] {continue}              ;# check blank segment
                   
               if [cequal [csubstr $seg 0 3] $SEG] {       ;# Get SEG input
                 set segFields [split $seg $sep ]          ;# SEG fields
                 set field [lindex $segFields $SEGele]     ;# Get SEG element #
                 set subcom [lindex [split $field $subsep] $SEGsub] ;# get SUBcomponent

                 for {set value 0} {$value < $SEGlistLength} {incr value 1} {
                   if [cequal $subcom [lindex $SEGval $value]] {
                     set dispList {}
                     lappend dispList "KILL $mh"
                     #echo $dispList
                     return $dispList
                   }
                 }
               }
             }
             #echo $dispList
             return $dispList
           }

           time {
             # Timer-based processing
             # N.B.: there may or may not be a MSGID key in args
           }
         }
      }

      As Jim K. indicated, need and desire to improve a personal skill set were the driving factors to the predecessors to the above script.  We started out creating very similar kill scripts, one for each particular instance or need (kill_evn1, kill_obr25), and eventually grew into scripts that would accept different values to look for, and finally to the kill_hl7_val script on which the above script is based.

      I have done minor testing, and this script is not in use anywhere yet, but I think it will work for you.  One thing it does NOT do is tolerate mistakes in the input values… so use them with accuracy.

      Example input parms:

      kill OBX 3.2 values of Apgar 1 or Apgar 5

      {SEG OBX} {SEGELE 3} {SEGSUB 1} {SEGVAL {{Apgar 1} {Apgar 5}}}

      kill records with patient home phone area codes of 456, 987 or 147

      {SEG PID} {SEGELE 13} {SEGSUB 5} {SEGVAL {456 987 147}}

      Remember, if you are looking for text with an imbedded space, you must enclose that value in curly braces.

      Hope this helps!

      Todd

Viewing 16 reply threads
  • The forum ‘Cloverleaf’ is closed to new topics and replies.

Forum Statistics

Registered Users
4,968
Forums
28
Topics
9,109
Replies
33,637
Topic Tags
248