Hi, again! I am having a really frustrating time doing something that I've done before and haven't had any problems. This time, I have spent an embarrassing number of hours troubleshooting a simple function, and am now going insane.
I have a test patient with a previous cholesterol value, dated 10/12/2007 - obsterm 'CHOLESTEROL'.
This same patient has a previous value of 03/01/2019 in the 'LAST CHOL DT' obsterm.
This is the function/code I'm using to bring in the most current date for the patient's last cholesterol test.
{fn fngetlastdate(obprvdt, lsoddt)
{cond
case obprvdt <> "" and lsoddt == "" return obprvdt
case obprvdt == "" and lsoddt <> "" return lsoddt
case obprvdt <> "" and lsoddt <> "" and durationdays(lsoddt, obprvdt) >= 0 return obprvdt
case obprvdt <> "" and lsoddt <> "" and durationdays(lsoddt, obprvdt) < 0 return lsoddt
endcond}}
{local strdat = ""
if OBSNOW("LAST CHOL DT") == "" then
strdat = fngetlastdate(OBSPREV("LAST CHOL DT"), LAST_SIGNED_OBS_DATE("CHOLESTEROL"))
else "" endif
OBSNOW("LAST CHOL DT", strdat)}
I have data display fields that validate the code works and returns the previous values and a positive # with the durationdays data symbol. However, either 10/12/2007 is the date that keeps getting pulled in to the current field, or nothing is pulled in. The MEL trace file keeps reporting that the obsprev("LAST CHOL DT"), and sometimes the LAST_SIGNED_OBS_DATE("CHOLESTEROL"), values are NULL. They're not! I can see them in the flowsheet and in the data display fields of the form. I have tried hours worth of trying different things, including using str commands around the date fields, not using a local variable, not passing values in the function, and the list goes on. I've even tried this in both CPS and the 9.8 single user version of the EMR.
I've also attached a very simple form, in case someone wants to take a look at it. It's probably something very simple, but I'm getting the same result whether I use case statements or if statements. with a function and without....
I will appreciate any help that anyone can give me. My sanity is already gone, but maybe I can get the form to work.
VFE 9.20
Thank you.
Robin Tardif
We may be over simplifying this.
But it looks like you have your second "strdat" variable with an "e" on the end.
Thank you for catching that, but fixing it didn't resolve the problem with pulling in the wrong date. Using the local variable was my last ditch effort at 2:00am, and I was obviously very tired and frustrated.
I was even hoping that shutting down my computer over night would maybe fix the problem. It didn't.
Thank you, again, though.
Robin
Blessed are the undocumented features.
On initialization, when referencing obsterms as a trigger (parameter or argument - whichever you prefer to call them), ONLY the first obsterm is initialized, the other obsterms are ignored. This is unlike using declared variables and document variables, which are properly valued on initialization. This change occurred several years ago when 'GE' was looking for ways to minimize recursive refresh cycles when working with obsterms. The end result is the 'NULL' value you are seeing.
To work around this 'feature', use the obsterm reference for function arguments as a 'trigger' only (you may still want them should the values change during the update) and assign a local variable inside the function to a direct obsterm call. By using a direct call inside the function definition, all of the obsterms initialize properly and continue to work after initialization (if so written).
Example:
{fn fngetlastdate(obprvdt, lsoddt)
{
local cholObs = ""
local lstdtObs = ""
cholObs = last_signed_obs_date("CHOLESTEROL)
lstdtObs = last_signed_obs_value("LAST CHOL DT")
cond
case lstdtObs <> "" and cholObs == "" return obprvdt
case lstdtObs == "" and cholObs <> "" return lsoddt
case lstdtObs <> "" and cholObs <> "" and durationdays(lstdtObs, cholObs) >= 0 return cholObs
case lstdtObs <> "" and cholObs <> "" and durationdays(lstdtObs, cholObs) < 0 return lstdtObs
endcond}}
Thanks, AGAIN, Lee. But that leads me to ask, how did you know that? Do you have a file of secret 'features' like this? The only document I have of this type is from 2005, and it's full of mistakes and typos. I haven't seen anything published since that concerns MEL programming.
I'd really like to find documentation that explains the order of things as a form is loaded. I have one form in which I created a function to do a one-time build of an array of data and store it in a document variable. I then use that document variable in other functions elsewhere in the form. I need to run that function first, but putting it towards the top of the form didn't work. It wouldn't work until I put the function hidden in a visibility region at the bottom of the form. I only know this because of running numerous MEL traces.
And, by the way, I've read the Beatitudes. I don't remember seeing, "Blessed are the undocumented features." 🙂
Thank you, again. I know we all appreciate your help.
Robin
With nearly 20 years of experience working with the product, researching and developing advanced projects for clients, finding new functionality to include in VFE, and general curiosity, I've discovered many undocumented workflows that can be leveraged and some that should be avoided. I've considered writing a 'book' from time to time, but for the last decade or so we have been hearing 'MEL is dead' with renewed emphasis on 'soon' each time I thought I would just go ahead with it . End result, it never happened.
Form load occurs in the following order:
In VFE - the 'MEL window' or right widow is processed first.
Any MEL code that leads with an exclamation point is processed.
All other MEL code in the window is processed.
All MEL code in the form proper is processed.
All form translation code is processed.
All form element display code is processed.
One important aspect of the 'order' that code should appear is an easy to remember positioning:
BOTTOM to TOP for initialization then TOP to BOTTOM for subsequent executions.
My standard workflow is (starting at the bottom of the 'MEL window'):
Declare all global variables with a ! character to ensure they are allocated an address in memory.
Declare all global DOCUMENT. variables with a ! to ensure they are allocated an address in memory.
Declare all functions with a ! character to ensure they are loaded into memory.
All of the above is below my 'watcher' expressions as they appear in the 'MEL window'.
Next I have my 'watcher expressions' (actionable code that begins with the ! character).
At the very top, I have my non-watcher actionable code.
I also avoid placing function definitions in buttons or form items for better control over when it is executed. In my experience this enhances form stability. Michael has done a lot of work to mitigate these issues by wrapping the code in a function of its own, but 'old habits...' ya know.
Other important notes are:
Referencing undeclared variables are a critical error for MEL. Always account for your variable names and be certain to declare them as local unless you specifically want them to be global. Unintended variable crossover is VERY bad and that includes loop counter variables!
Referencing a non-existent index in an array is a critical error for MEL. Best practice is to include an error trap buy evaluating the size of an array is at least a large as the highest index number you are referencing in the code. You can/should include a userok to notify the user/coder that the array size is not what was expected therefore the 'result' should not be 'trusted'.
When troubleshooting, understand that a single error can create the appearance that something else is broken. Fix code one issue at a time and retrace after each fix to save on headaches.
Some errors never reveal themselves leading to a false sense of 'it's working'. This is often seen in 'else' workflows that are less common in day to day practice. Always test the 'else' condition to be certain your code is working as intended.
Not the 'reference' you are looking for, but it is a start, right?
Thank you to Lee, again.
I have updated the function. This works if anyone wants to pull in the most recent date when comparing the date a test was run, such as a mammo date, and a date stored in an obsterm, such as the LAST MAM DAT obsterm.
{!fn fngettstdate(dte1, dte2)
{local tstobs = ""
local lstdtObs = ""
tstobs = last_signed_obs_date(dte1)
lstdtObs = last_signed_obs_value(dte2)
cond
case lstdtObs <> "" and tstobs == "" return lsoddt
case lstdtObs == "" and tstobs <> "" return tstobs
case lstdtObs <> "" and tstobs <> "" and durationdays(lstdtObs, tstobs) >= 0 return tstobs
case lstdtObs <> "" and tstobs <> "" and durationdays(lstdtObs, tstobs) < 0 return lstdtObs
endcond}}
function example: fngettstdate("MAMMOGRAM", "LAST MAM DAT")
Robin