Well, that's no ordinary rabbit!

The Basics

This query and the attributes and macros that are attached to it were created to pull a patient's medications into a customer defined screen for an NUR assessment. The screen is used to pull various pieces of information together for online discharge documentation. Some of the other queries collect diet handouts (which automatically print when the screen is filed), future doctor appointments and activity restrictions. The meds are pulled into a multiple type query using an IFE attribute that calls a macro. The macro goes to pharmacy, collects and formats the list of meds and then displays the list in a text editor where they can be edited and documentation and notes added. Here's a sample screen of the text editor window.


meds in a cds

That's a brief description. Now let's get to the details of how it works and some of the issues I ran into.

The IFE Attributes

The nursing staff determined that they wanted to be able to answer some queries long before discharge and not have to deal with the meds until right before discharge. This was done by adding a Y/N query right before the meds query which simply asked if they wanted to edit the meds.I'll refer to this query as YESNOQ and the meds query as MEDQ.

Simple enough, right? Problem - the nurse could respond Y and edit the meds and then later go back into the cds but not want to edit the meds this time. So that the nurse could happily type ENTER ENTER ENTER and not accidentally go into the meds query the Y would have to be removed the next time the screen was accessed. Of course your users never type ENTER ENTER ENTER to get thru a screen. I'm sure they all use the NEXT and PGDN keys to bypass sections they aren't concerned with. This was accomplished by adding an IFE attribute to YESNOQ and to the query after MEDQ (I'll call this NEXTQ) that removes the response to YESNOQ. Now every time the screen is accessed they have to respond Y to YESNOQ in order to get to MEDQ regardless of whether they are advancing thru the screen or using the PREV key to reverse thru the screen. Also the IFE attribute for MEDQ always evaluates to "" so the cursor never stops there. The meds can only be edited within the text editor.

YESNOQ

IFE=IF{P(R,S," ")^#,""^/[ANS%0,"YESNOQ"]|0,1;1}

MEDQ

IFE=See attribute below

NEXTQ

IFE=IF{""^/[ANS%0,"YESNOQ"]|0 1;1}

MEDS IN A CDS 2

The IFE Attributes Continued

Ok, that's not the actual IFE attribute for the MEDQ query but it covers what I've discussed so far. The next issue is how to get to the meds, after all they're in the PHA application and the CDS is in NUR. The safest way would be to use the Z.rw.fragment program and call a PHA report which would load the meds into /R.FRAG.ARG[XX] and then move the data from /R.FRAG.ARG[XX] into /[ANS%0,"MEDQ","M",XX]|0. If you want to do it the safe way I might be persuaded to reproduce the pharmacy section of the macro as a PHA.RX fragment but don't look for it any time soon. I like writing magic code so rather than create the safe fragment I just $SEG'd over to PHA and looped thru the TR data structure to get what I needed. (Meditech isn't reading this are they?)

Have I lost anyone yet? Hold on because it only gets better. I still have to get the data to display in a text editor. Actually that's not really very hard. Once the meds are in a / variable you simply call the Z.text.ed.shell program with the / variable as the first argument.So if my meds are all in /MEDS this code would open a text editor with the meds already loaded.

%Z.text.ed.shell(^/MEDS,2,22,75,0,"","","","","")X

A fuller discussion on the program is further down including what all those other arguments are.

One other point before revealing the full IFE attribute for MEDQ. Once MEDQ has been answered I no longer needed to go to PHA for the list of meds. The next time the screen was accessed it should pull the previous response into the text editor. That way any changes that had been made or notes added would not be lost.

Most of the good stuff is in the macro but here's what the IFE attribute for MEDQ does.
1. check for a Y response in YESNOQ
2. call the macro that does the good stuff
3. save the good stuff in MEDQ
4. display the first 9 lines of text in MEDQ since the multiple query is set to display 9 lines

IFE=IF{"Y"'=[ANS,"YESNOQ"]|0"";
IFE=IF{%MIS.zcus.pgm.M.nur.meds(/["aa"],ANS%0),
IFE=""^TT,DO{TT+1^TT<10
IFE= P(R+TT-1,S,/[ANS%0,"MEDQ","M",TT]|0:75TL)^#},"";""}}

One additional comment before I get into the really good stuff. The CDS syntax checker does not like my macro even though it is all valid code. To get around it I'm using the patient as the first argument in the call to the macro. The main macro code is only executed if that argument has a value. Since there is no patient when the CDS  syntax checker is invoked the macro code is bypassed and it passes the syntax check.

The Macro - MIS.zcus.pgm.M.nur.meds

Here it is - over 200 lines of macro code. Half of it is probably comments though. (Note: Updated 7/25/02)

;--------------------------------------------------------------
;--- A argument - patient
;--- B argument - ANS
;--- first line checks for a value in A
;--- and only executes the main code if a value exists
;--- this is to get around the CDS syntax checker
;--- since I am using patient as the A argument
;--- a value will only exist when the screen is
;--- being used, not when it is being built
;--------------------------------------------------------------
IF{A @MAIN.CODE},
1;

MAIN.CODE
;--------------------------------------------------------------
;--- first clean out any values in /MEDS just in case
;--- there is any data from another source in that variable
;--------------------------------------------------------------
""^TT,
DO{>/RXS[TT]^TT ""^/RXS[TT]},
""^TT,
DO{+/MEDS[TT]^TT ""^/MEDS[TT]},
                 ;----------------------------------------------------
                 ;--- check the /ANS structure to see if the query already
                 ;--- has any data in it.
                 ;--- if not get the meds from pharmacy
                 ;--- if so get the meds from the query
                 ;-----------------------------------------------------
                 IF{/[B,"MEDQ","M",1]|0_.=. @GET.FROM.PHARMACY;
                    @GET.PREVIOUS.TEXT},
;--------------------------------------------------------------
;--- now that the meds are in our /MEDS variable
;--- open the text editor window for editting
;--------------------------------------------------------------
@TEXT.EDITOR,
;--
;--- save the new text into the query
""^XX,
DO{+/MEDS[XX]^XX /MEDS[XX]^/[B,"MEDQ","M",XX]|0,
                 ""^/MEDS[XX]}

GET.FROM.PHARMACY
;--------------------------------------------------------------
;--- remove all responses from MEDQ
;--------------------------------------------------------------
""^TT,
DO{+/[B,"MEDQ","M",TT]^TT ""^/[B,"MEDQ","M",TT]|0},
A^PAT,
;--------------------------------------------------------------
;--- if PAT has a value get a list of meds from pharmacy application
;--------------------------------------------------------------
IF{'PAT;
   @OPEN.PHA,
   @GET.RXS,
   @LOOP.SORTED.RX,
   @CLOSE.PHA}

GET.PREVIOUS.TEXT
;--------------------------------------------------------------
;--- loop thru the current query responses
;--------------------------------------------------------------
""^TT,1,
DO{+/[B,"MEDQ","M",TT]^TT @STORE.TEXT}

STORE.TEXT
;--------------------------------------------------------------
;--- save the query responses in the /MEDS file that the text editor uses
;--------------------------------------------------------------
/[B,"MEDQ","M",TT]|0^/MEDS[TT],
;--------------------------------------------------------------
;--- delete the current query response
;--- it will be reloaded when the text editor is exited
;--------------------------------------------------------------
""^/[B,"MEDQ","M",TT]

GET.RXS
""^TRN^TT,
;--------------------------------------------------------------
;--- text that should always be on line 1
;--------------------------------------------------------------
" ************** MEDICATIONS **************"^/MEDS[TT+1^TT],
;--------------------------------------------------------------
;--- add a blank line
;--------------------------------------------------------------
" "^/MEDS[TT+1^TT],
;--------------------------------------------------------------
;--- :TRP is the patient RX index file
;--------------------------------------------------------------
DO{+:TRP[PAT,TRN]^TRN :TRP[PAT,TRN]^RX,
                      ;----------------------------------------
                      ;-- skip if rx is not active
                      ;----------------------------------------
                      IF{:TR[RX]|8'="AC";
                         ;----------------------------------------
                         ;--- skip if order type is PIV
                         ;----------------------------------------
                         :TR[RX]|7="PIV";
                         ;----------------------------------------
                         ;--- skip if med is FLUSH
                         ;----------------------------------------
                         :TR[RX]M|0="FLUSH";
                         ;----------------------------------------
                         ;--- store all the rest is a sorted / variable
                         ;----------------------------------------
                         @SORT.RX}}

LOOP.SORTED.RX
;--------------------------------------------------------------
;--- loop thru the sorted list of rx's
;--------------------------------------------------------------
""^SK1,
DO{+/RXS[SK1]^SK1 ""^SK2,
                  DO{+/RXS[SK1,SK2]^SK2 ""^SK3,
                                        DO{+/RXS[SK1,SK2,SK3]^SK3 ""^RX,
                                                                  @GET.RX.INFO}}}

GET.RX.INFO
;--------------------------------------------------------------
;--- Med and IV order types have different formats
;--------------------------------------------------------------
DO{+/RXS[SK1,SK2,SK3,RX]^RX IF{?TDO[:TR[RX]|7]|2="MED" @MED.INFO;
                               ?TDO[:TR[RX]|7]|2="IV" @IV.INFO;
                               1}}

MED.INFO
;--------------------------------------------------------------
;--- get the med name
;--------------------------------------------------------------
?TF[:TR[RX]M|0^TF]|1^NAME,
;--------------------------------------------------------------
;--- reformat the med name
;--------------------------------------------------------------
@TJT.NAME^NAME,
;--------------------------------------------------------------
;--- reformatted med name to mixed case
;--- add the generic name
;--------------------------------------------------------------
%MIS.zcus.pgm.M.lower.name(NAME_" ("_?TDG[?TF[TF]|2]|1_")")^NAME,
;--------------------------------------------------------------
;--- get dose and sig information and reformat
;--------------------------------------------------------------
IF{:TR[RX]M|1>0 :TR[RX]M|1_" "_?TF[TF]|5;
   :TR[RX]M|9}^FD2,
%MIS.zcus.pgm.M.lower.name(\GGW[:TR[RX]|9]|1)^SIG,
IF{SIG="PRN" "As Needed";SIG="Prn" "As Needed";SIG}^SIG,
;--------------------------------------------------------------
;--- save the name and dose/sig info in /MEDS
;--------------------------------------------------------------
NAME_" ":45TL_(FD2:10TL)_SIG^NAME:75TL^/MEDS[TT+1^TT],
;--------------------------------------------------------------
;--- Add does instructions to /MEDS
;--------------------------------------------------------------
IF{:TR[RX,"DI",1]_.=.;
   ""^DIQ,
   DO{+:TR[RX,"DI",DIQ]^DIQ " "_:TR[RX,"DI",DIQ]^/MEDS[TT+1^TT]}},
;--------------------------------------------------------------
;--- Add additional text after each med in /MEDS
;--------------------------------------------------------------
" Last Dose Given: "^/MEDS[TT+1^TT],
" Rx Given: "^/MEDS[TT+1^TT],
;--------------------------------------------------------------
;--- add a row of hyphens to /MEDS to separate the meds
;--------------------------------------------------------------
("-":75)^/MEDS[TT+1^TT]

IV.INFO
""^AQ,
;--------------------------------------------------------------
;--- get additive names and generic names, reformat and add to /MEDS
;--------------------------------------------------------------
DO{+:TR[RX,"AD",AQ]^AQ :TR[RX,"AD",AQ]|0^TF,
                       ?TDG[?TF[TF]|2]|1^NAME,
                       NAME_" "_IF{:TR[RX,"AD",AQ]|7;:TR[RX,"AD",AQ]|1}^NAME,
                       %MIS.zcus.pgm.M.lower.name(NAME)^NAME,
                       NAME_" "_?TF[TF]|5^/MEDS[TT+1^TT]},
;--------------------------------------------------------------
;--- add the carriers to /MEDS
;--------------------------------------------------------------
""^IC,
DO{+:TR[RX,"IC",IC]^IC :TR[RX,"IC",IC]|0^TF,
                       ?TDG[?TF[TF]|2]|1^NAME,
                       NAME_" "_IF{:TR[RX,"IC",IC]|7 :TR[RX,"IC",IC]|7_"%";
                                   :TR[RX,"IC",IC]|1}^NAME,
                       %MIS.zcus.pgm.M.lower.name(NAME)^NAME,
                       IF{:TR[RX,"AD",1] " in "}_NAME_" "_?TF[TF]|5^/MEDS[TT+1^TT]},
;--------------------------------------------------------------
;--- add additional text to /MEDS
;--------------------------------------------------------------
" Last Dose Given: "^/MEDS[TT+1^TT],
" Rx Given: "^/MEDS[TT+1^TT],
;--------------------------------------------------------------
;--- add row of hyphens to /MEDS
;--------------------------------------------------------------
("-":75)^/MEDS[TT+1^TT]

OPEN.PHA
$MAC^SEG,
$["DIR.NAME"](%)#0P^DIR,
;--------------------------------------------------------------
;--- PHA.DB must be replaced with the database name of your PHA
;--- in 4 lines of this section
;--------------------------------------------------------------
@MIS.APPL.database.segment["PHA.DB"]^NEWSEG,
@MIS.APPL.database.directory["PHA.DB"]^NEWDIR,
$["SEGS"](NEWSEG,NEWDIR),
CL(:S,?S),
;---------------------------------------------------------
;--- open the PHA data stucture so we can get the meds from the rx's
;---------------------------------------------------------
ZZZ%OP(:,%[".PHA.data",/.MIS,"PHA.DB"]),
;---------------------------------------------------------
;--- open the PHA dictionary structure so we can get med names, etc
;---------------------------------------------------------
ZZZ%OP(?,%[".PHA.dic",/.MIS,"PHA.DB"])

CLOSE.PHA
;---------------------------------------------------------
;--- forget this step and your database could be corrupted
;--- you will never be forgiven for this error
;---------------------------------------------------------
CL(?U,:U),
CL(%U,$U),
$["SEGS"](SEG),
$["DIR"](DIR)

TEXT.EDITOR
@W.return(""),
@Window.centered(23,75,@Z.color.labels1)^#,
IF{/.GUI D(30)_"CC"_{("Press to exit the text editor window":75TL,"")}^#;
   P(0,0,"Press to exit the text editor window":75CT)^#,
   @Window.horiz.line(1)^#},
@Set.attr(@Z.color.data1)^#,
@Erase.region(2,0,21,75)^#,
%Z.text.ed.shell(^/MEDS,2,22,75,0,"","","","","")X,
@Restore.window("")^#,
1

SORT.RX
;---------------------------------------------------------
;--- the meds need to be sorted to match the MAR
;--- primary sort
;--- 1 - sched meds
;--- 2 - sched epi and pca
;--- 3 - prn epi and pca
;--- 4 - sched SIV
;--- 5 - sched PIV (already excluded so there should not be any)
;--- 6 - prn SIV
;--- 7 - prn PIV (already excluded)
;--- 8 - prn meds
;--- secondary and tertiary sort vary depending on order type and schedule
;--- last sort is the rx urn
;---------------------------------------------------------
1^SK1^SK2^SK3^SK4,
IF{:TR[RX]|10="SCH" IF{:TR[RX]|7="M" 1^SK1,
                                     ?TDG[?TF[:TR[RX]M|0]|2]|1^SK2;
                       :TR[RX]|7="E" 2^SK1,
                                     ?TDG[?TF[:TR[RX]M|0]|2]|1^SK2;
                       :TR[RX]|7="PCA" 2^SK1,
                                       ?TDG[?TF[:TR[RX]M|0]|2]|1^SK2;
                       :TR[RX]|7="SIV" 4^SK1,
                                       ?TDG[?TF[:TR[RX]M|0]|2]|1^SK2;
                       :TR[RX]|7="PIV" 5^SK1,
                                       ?TDG[?TF[:TR[RX]M|0]|2]|1^SK2};
   :TR[RX]|10="PRN" IF{:TR[RX]|7="M" 8^SK1,
                                     ?TDG[?TF[:TR[RX]M|0]|2]|1^SK2;
                       :TR[RX]|7="SIV" 6^SK1,
                                       ?TDG[?TF[:TR[RX]M|0]|2]|1^SK2;
                       :TR[RX]|7="PIV" 7^SK1,
                                       ?TDG[?TF[:TR[RX]M|0]|2]|1^SK2;
                       :TR[RX]|7="E" 3^SK1,
                                     :TR[RX]|7^SK2,
                                     ?TDG[?TF[:TR[RX]M|0]|2]|1^SK3;
                       :TR[RX]|7="PCA" 3^SK1,
                                       :TR[RX]|7^SK2,
                                       ?TF[:TR[RX]M|0]|1^SK3}},
1^/RXS[SK1,SK2,SK3,RX]

TJT.NAME
;---------------------------------------------------------
;--- reformat the name to remove size/strength information
;--- ANCEF 1 GM VL should be listed as just ANCEF
;--- exclude % and / (10% or mg/ml) info from being removed.
;---------------------------------------------------------
""^SLASH^XXX,
IF{L(NAME,"%")^XXX<L(NAME) XXX+1;0}^XXX,
IF{L(NAME,"/")^YYY<L(NAME) YYY+1^YYY,1^SLASH},
IF{SLASH DO{NAME#YYY'=" " YYY+1^YYY},
YYY^XXX;
DO{NAME#XXX'?0N XXX+1^XXX}},
NAME$XXX

The Macro - MIS.zcus.pgm.M.lower.name

; ----- This macro converts text to mixed case.
A~$L.TO.U^A,
.^PR,
0^P,
DO{A#P^C IF{E(PR)^E<65!(E>90) C;
            C~$U.TO.L}^CH,
         C^PR,
         N_CH^N,
         @Add(1,P)},
""^P,
@JR.SR,
@MACS,
@II,
@PO.BOX,
@TH,
@MLK,
N;

JR.SR
IF{N$3="Jr " N%2_" Jr"^N;
   N$3="Sr " N%2_" Sr"^N}

MACS
L(N)^L,
IF{L(N,"Mca")^P<L "McA"^RP;
   L(N,"Mcb")^P<L "McB"^RP;
   L(N,"Mcc")^P<L "McC"^RP;
   L(N,"Mcd")^P<L "McD"^RP;
   L(N,"Mce")^P<L "McE"^RP;
   L(N,"Mcf")^P<L "McF"^RP;
   L(N,"Mcg")^P<L "McG"^RP;
   L(N,"Mch")^P<L "McH"^RP;
   L(N,"Mci")^P<L "McI"^RP;
   L(N,"Mcj")^P<L "McJ"^RP;
   L(N,"Mck")^P<L "McK"^RP;
   L(N,"Mcl")^P<L "McL"^RP;
   L(N,"Mcm")^P<L "McM"^RP;
   L(N,"Mcn")^P<L "McN"^RP;
   L(N,"Mco")^P<L "McO"^RP;
   L(N,"Mcp")^P<L "McP"^RP;
   L(N,"Mcq")^P<L "McQ"^RP;
   L(N,"Mcr")^P<L "McR"^RP;
   L(N,"Mcs")^P<L "McS"^RP;
   L(N,"Mct")^P<L "McT"^RP;
   L(N,"Mcu")^P<L "McU"^RP;
   L(N,"Mcv")^P<L "McV"^RP;
   L(N,"Mcw")^P<L "McW"^RP;
   L(N,"Mcx")^P<L "McX"^RP;
   L(N,"Mcy")^P<L "McY"^RP;
   L(N,"Mcz")^P<L "McZ"^RP},
IF{RP N$P_RP_(N%(P+2))^N},
""^RP

II
L(N)^L,
IF{L(N,"Iii")^P<L "III"^RP,2+P^L;
   L(N,"Ii")^P<L "II"^RP,1+P^L},
IF{RP (N$(P))_RP_(N%(L))^N},
""^RP

PO.BOX
L(N)^L,
IF{L(N,"Po ")^P<L "PO "^RP,2+P^ST;
   L(N,"P.o. ")^P<L "PO "^RP,4+P^ST;
   L(N,"Pob ")^P<L "PO Box "^RP,3+P^ST;
   L(N,"Pobox")^P<L "PO Box "^RP,4+P^ST},
IF{RP (N$(P))_RP_(N%(ST))^N},
""^RP

TH
L(N)^L,
IF{L(N,"Th ")^P<L "th "^RP,P+2^TH},
IF{RP (N$(P))_RP_(N%(TH))^N},
""^RP

MLK
L(N)^L,
IF{L(N," Ml ")^P<L " ML "^RP,P+3^TH;
   L(N," Mlk ")^P<L " MLK "^RP,P+4^TH},
IF{RP (N$(P))_RP_(N%(TH))^N},
""^RP

The Z.text.ed.shell Program

This program opens a text editor window. For this discussion I'll use the same arguments that are used in the MEDQ attribute and explain what each one does. Arguments are referenced by letter - the first is A, the second is B, the third is C and so on.

%Z.text.ed.shell(^/MEDS,2,22,75,0,"","","","","")X

The A argument (^/MEDS) is the / file that will be used for the text. It can already have text in it as it does when used in MEDQ and the text will be loaded into the editor when it opens. Or it can be empty in which case the editor will open with an empty window. The / file has a single subscript which is the line number. The file for MEDQ would look like this...

/MEDS[1]=    *********** MEDICATIONS ***********
/MEDS[2]=
/MEDS[3]=Dexamethasone 10Mg/l (Dexamethasone)       6 MG
/MEDS[4]=    Last Dose Given:
etc.

Note that /MEDS[2] contains no text. Although the text editor handles blank lines just fine the multiple query does not - all responses must have a value. In order to keep the format the same in the query, blank lines of text should be replaced with a single space when saved as a query response.

The B argument (2) is the top margin. This defaults to 3 if not defined.

The C argument (22) is the bottom margin. This defaults to 23 if not defined. Maximum value is 27.

The D argument (75) is the width of the window. This example uses 75 because that is the length of the query response where the text will be saved after editing. It defaults to 79 if not defined. The maximum value is 92.

The E argument (0) is the left margin. It defaults to 0.

The F argument has a value of "D" to indicate display only. Otherwise both edit and display are assumed

The G and H arguments point to the dpm and field of a help file.

The I argument is used to override some of the special functions available in the editor.

The J argument indicates the maximum number of lines the file can have.


Other Magic Pages

CDS Attributes Emailing Messages Emailing Scheduled Reports
@W. prompts String Manipulation NPR Viewer
Text Edit/View Attribute Meds in an NUR CDS NUI Desktop Icons
Field Attributes Lab results in a CDS Ultimate Message Box
Display Message Attributes Run NPR Report Attribute Multiple Query Checklist
NUR Canned Text in Query Mousetributes LAB TEST VIEW GROUP
OA Msg from PHA Rule FTP Standard Report HTML Pt Master Logs
PCI NPR Report


Don't see what you need?  Visit one of these other sites

Iatric Systems web site
Attributes
NPR tips
NPR reports
Magic and C/S Products
ITNurse.net
Hosted by Daniel Davis
Resources for the IT Nurse
Articles and Discussion Forum
Debbie Kelly web site
Attributes
Meditech tips
Nursing Informatics
Debbie Bate-Travis web site
Attributes
Links to other attribute sites
Links to Nursing Informatics sites
Magic NPR Report Writing
Blogged by John Sharpe
NPR Report Writing Tips

or email me your request (tomt at thomast357.com).