RxKinetics.Net issues

My web host sent out a notification last week that they were changing servers and updating from Windows Server 2003 to 2012.

To make this transition easier for you we have moved your entire account under the new environment using an offline migration meaning that your old website will remain active until you will verify that all your contents/databases have been moved under the new environment and finally switch your nameservers to have your domains pointed over under the new servers.

We are also afraid that as this was an automated process using a migration tool, some web/database contents might not be synchronized between our old and new shared windows servers. We urge you to check your contents as soon as possible and update your connection strings/nameservers as soon as possible to have your entire domain pointed over to the new environment as your old account under the old shared windows server will be removed in 15 days.

Yeah, right, their automated tool failed miserably for me. First of all, they didn’t transfer my old password, so that took 2 days to straighten out.

When I was finally able to log in I noticed they didn’t transfer any of my databases. Really? That took another day.

I changed the database connection string in my webconfig file and FTP’d the updated file to the server.

Then I asked, “how do I test the web site before go live?” The sysadmin responded:

update your name servers from your domain registrar then you test your website working fine on new servers or not.

To which I replied “So you’re telling me I have to go live in order to test it? I’d prefer not to do that if possible. If not, well, I guess I’ll jump in both feet.”

No response, so I did the DNS change. Well, of course it crashed.
rxkinetics_net_500

Much later I received a response from the “good” sysadmin:

You can always test your website using your local hosts file. Simply add the following lines under your C:\Windows\System32\Drivers\etc\Hosts file:
==
XX.XXX.XXX.X rxkinetics.net
==
Then clear your browser cache, flush your DNS and you should be able to browse your website hosted under our new environment before you will have your nameservers updated.

I’ve since switched back to the old DNS, but that take will take another day to revert back to the old server. And I’m worried that it will not actually revert back.

I should have known better. Something like this always happens to me with ASP.NET. Make one little change and it stops working, and doesn’t give any clue as to why. I haven’t touched this code in three years because I didn’t want to break it.

I’m pretty sure it’s a setting on the admin side, which I have no control over. I believe it’s related to this problem I blogged about 3 years ago Fun with ASP dot NET.

Complex non-steady-state analysis

This is a feature that I have wanted to add to my PK programs for quite sometime. In the real world doses don’t get hung on time and dosages get changed before levels are drawn.

Complex is the keyword. This was incredibly complex to code, both the logic of data entry and analysis. It has taken weeks of intense work to complete. Currently in the testing phase, I hope to bring it to production soon.
Complex_nonSS

When you select “Complex non-steady-state analysis” the program displays two grids, one for entering doses and the other for serum levels.
Complex_nonSS_data
You may enter up to five doses of various amounts, given at different rates and at different times. You may enter up to two serum levels. Of course there are limitations (which the data entry routines catch). For example, you cannot enter a date/time for a level that conflicts with a dose date/time.

If the first serum level is drawn before the first dose it will be considered a baseline level. This is also something that I have wanted to add for years. It will be most useful when performing a follow-up pk consult on a long-term patient.

Another feature added from my to-do list is the ability to select an infusion rate for your recommended dose.
complex_recommend

You will still see Bayesian fails. This new data entry methodology will not prevent them from happening. That is the nature of Bayesian and the highly variable nature of Vancomycin kinetics.

Even people who have used my programs for years fail to comprehend what Bayesian analysis involves. The first thing to realize is that Bayesian analysis begins with your population model. It then attempts to fit your measured levels to incremental variations of your population model. In classic Bayesian, if the data cannot be fitted to the model within reasonable statistical limits, then the data is thrown out. I do not believe that rule should be applied to clinical pharmacokinetics. So I leave it up to you to decide how to proceed by showing the “Bayesian analysis failed” dialog.
Complex_fail
Essentially this dialog is telling you that your measured level differs too much from the model predicted level. You can find out what level the population model predicted by visiting the prospective tab. If you measured 6 but the model predicted 24, then never the twain shall meet. Either (1) you have chosen the wrong model for your patient, or (2) the measured level is wrong. That is your decision, not one to be made by a computer program, period.

KinPlot Update

Pulled an all nighter to update both the Windows and the Web versions of KinPlot. This is a simple tool for learning pharmacokinetic concepts by depicting the effects that various pk parameters have on serum levels, leading to a better understanding of drug regimen design.

I had a request for IV push modeling from a Cornell professor. The simplest solution was to utilize a 6 minute infusion to approximate IV push administration (the math is easier with decimals, i.e., 6 min = 0.1 hr).

Since they use Macs I needed to update the Web App as well.
KinPlot_web

Web version here.

The Windows version adds a few more bells and whistles: the ability to model oral pk and to save/load graphs.
kinplot_win_23

Windows version here.

Enjoy.

Hacked again

Hacked again

Received an email from Google today:

  • Hacking suspected: http://rxkinetics.net/
  • Unfortunately, it appears that your site has been hacked.

What does it mean to have pages marked with the hacked site type “URL injection” in Search Console?

This means a hacker has created new pages on your site, often containing spammy words or links.

Typically, hackers modify your site in one of these ways:

  • By gaining access to an insecure directory on your server. For example, you may have inadvertently left a directory with open permissions. Nope, no anonymous FTP is allowed.
  • By exploiting a vulnerability in software running on your site, such as a content management system. For example, you might be running an older, insecure version of WordPress. Nope, don’t have WordPress or any content management system.
  • By hacking third-party plugins that you use on your site, such as visitor counters. Nope, no 3rd party plugins on this site.

None of the above are true, I believe they discerned my password yet again with a brute force attack.

List of alerts from Google:
Google_analytics_messages

It looks like they’ve been attacking my web site since March when it was defaced:
Page_Not_Found_Errors

Apparently they were searching for pages they could target. They searched for links common on most web sites, e.g. “Cart” and “FAQ” and “Feed back”. None of which exist on this web site.

I removed the spammy content, and changed my password yet again. I plan on changing password every week now. I wonder how long it takes Google to remove my site from their hacked sites list (sigh).

LunarPages support response was helpful, “we are planning to upgrade the Operating System that is used by your shared windows environment within the next months that will ensure that your Operating System is updated and will add a new layer of protection on your website security.” And they scanned my site for malware:
Scan_results

I’m beginning to think all this hassle just isn’t worth it anymore. It’s nearly impossible to get an email through anymore. I’ve resorted to using Gmail as my mail server, but even then many of my emails replies are rejected. My ListServ is a joke, the only requests are from spammers and scammers pushing the 3-P’s (Pills, porn and poker).

The terrorists have won.
Kim_Jong

Hacked!

Got home from a long night at work and checked my email to find this friendly FYI:

Hi Rick,
This is Steve ####.
Hope life is treating you well
FYI- your site rxkinetics.net coming up with “something unexpected” on home page.
Regards,
Steve

So I went to rxkinetics.net and found this lovely NSFW message (without the scambling).

The graffiti

Wow, hacked! Just like the big boys, I feel so special now .

Really? I think these losers would have something better to do than vandalize my insignificant little corner of the internet.

I sent a support ticket to Lunarpages, then discovered that typing in the full url bypassed the graffiti:
http://rxkinetics.net/default.aspx

It was a long night and I was dead tired, so I emailed Steve back and went to sleep.

About noon I woke up and checked my email. There was no reply from Lunarpages, so I got out of bed and dug into the web site files.

There were four suspicious .asp files on the site. They stuck out like a sore thumb because I don’t use .asp files on this web site:

  • pageface.asp
  • sin.asp
  • crx.asp
  • default.asp

None of the correct files were changed. I deleted the four rogue files and the site went back to normal.

Then I changed my ftp password and asked Lunarpages if there was anything else I needed to do. Well, it’s been over eight hours since I started the support ticket and I’ve yet to receive a response from Lunarpages. I’m pretty disappointed, because up to this point, they’ve always been quick to reply and provide help.

All of this makes me wonder if this was an inside job from a disgruntled employee at Lunarpages.

Facing mortality

George_slusher
I don’t know why my neighbor’s death has affected me so deeply. Maybe it’s because I just turned 60 and I’ve realized that it’s all downhill from here. Or maybe it’s because his death was so sudden and unexpected. Or maybe it’s because I never got a chance to thank him for the lovely and tasty bouquet of lettuce that he left on our doorstep last Wednesday. Or maybe it’s because he was the best neighbor anyone could ask for. George helped us countless times over the past twenty years and never asked for or accepted anything in return.

Whatever the reason, I’m stunned by it and my heart is heavy.

George was not in the best of health, but he never gave up. He had survived a heart attack back in the seventies when treatment was practically nil. The result of that was a badly damaged heart and CHF. Because he was a long term smoker he also had developed COPD. But he coped and he found good doctors at the VA who fine-tuned his medicine. He looked great, and he stayed busy, always working in his garden, his shop or his greenhouse. And he never missed a good fishing day. He may have been retired, but he was always coming up with some new project. He had a very creative and fertile mind.

I last saw George when Connie and I were walking last Wednesday afternoon. He was driving his little pickup down our street, when he saw us he waved and flashed a big smile. I am so grateful that is the last image we have of him. I had wanted to tell him how great his lettuce bouquet idea was and that I thought it would be a big hit at the City Market.
lettuce

George was an artist, he painted and created intricate wood crafts. His cactus planters were amazing. He had perfected a system of growing cactus in his greenhouse, he had it down to a science. And he created these fantastic wooden planters that he artfully arranged the cactus in. They were his best seller at the City Market, he could hardly make enough of them.
Cactus

He painted the logo for our Tharpalooza burning man, and furnished enough wood for an inferno:
George

He loved going to the Farmer’s market at the KC City Market. He sold flowers, vegetables, and his wood crafts. He was amazed by how much city people would pay for his stuff and he thought they would buy anything. To test his hypothesis, he potted a thistle weed that was growing in his garden and sold it as an exotic flower for $25. That’s one of my favorite stories of his.

I loved visiting with George and listening to his stories. He was a great storyteller, a talent that I wish I had. George was active duty Air Force back in the fifties crewing a tanker. That was during the infancy of air refueling, when they were trying to perfect the technique. On one flight his plane went down in the North Atlantic, and he was the only survivor. I can’t imagine being alone in a life preserver in the middle of the frigid ocean and living to tell about it.

Needless to say, he was a tough old bird who had cheated death more than once. I worry that they put him into hospice too soon. I mean, the day after he checked into the VA, after driving himself down there? Did they just give up on him? I don’t know the particulars and the family didn’t know any, but I worry that hospice is sometimes code for euthanasia, just my opinion.

Regardless, I am thankful for twenty years of friendship with George, but my world is going to be a much colder place without him.

APK 3.1.20

I’ve spent much of my ‘free’ time during the last month been pouring over error reports received during the past year. Unfortunately most of the reports are vague and completely untraceable as to their cause. Focusing on the most commonly reported errors I’ve tried to deduce their causes, but I’m likely to be completely off base.

TfrmMain, TfrmPrint: EConvertError

‘Not a valid floating point value’. All of these error reports came from version .18. This was fixed in version .19 by changing all calls to StrToFloat to TryStrToFloat. Come on folks, updates are free, please keep your copies up-to-date.

TfrmMain: Range check error
There were several Range errors which I believe originate in the Topaz database engine (no trace available). I re-compiled Topaz with Range checking turned off, hopefully that will tone down the frequency of these error reports.

TfrmPrint: Multiple errors

Multiple errors were reported originating in the TfrmPrint class. Procedures flagged include: SaveConsult, SaveLevels, SavePatient, ValidPatData, GraphPrep, and ReDrawSerumLevelPlot. Hopefully recompiling Topaz will fix most of the Save errors. But, to add another layer of safety, I’ve added try..try..except..finally blocks to each procedure and changed the SerumLevel array from fixed to dynamic.

TfrmMonitor: No default printer selected
This was reported twice. I cannot imagine how or why someone would not have a default printer selected in Windows. Regardless, I’ve added this tiny bit of code to hopefully catch that condition (unfortunately I have no way of testing this):
if (Printer.PrinterIndex > 0)then
begin
// Print the consult
end;

TfrmPKPD: Division by Zero
The function AUC was flagged in one report. I cannot imagine how this procedure gets called without valid parameters, but, once again, I added try..try..except..finally block that will prevent the program from crashing.

TfrmLevels; EConvertError
‘is not a valid integer value.’ Sounding like a broken record, I simply cannot imagine how this procedure gets called without valid parameters, regardless I changed StrToInt to TryStrToInt in hopes of fixing this impossible error.

TfrmMain: Pack date invalid
This error came up twice, with no traceable cause. Because the user is able to change date settings in Windows at will, I assumed an incompatible date format is causing the issue. I added some code to force the short date format into the standard USA: ‘MM/dd/yyyy’. Originally I had declared the OutputBuffer within the var block of the function, but that was causing a memory leak on exit, even after adding a StrDispose in the cleanup (finally) block:
procedure TfrmMain.SavePackDate();
var
APKini : TIniFile;
strDateFormat : string;
strTemp : string;
OutputBuffer: PChar;
SelectedLCID: LCID;
begin
try
// Determine local short date format
OutputBuffer := StrAlloc(255);
SelectedLCID := GetUserDefaultLCID;
GetLocaleInfo(SelectedLCID, LOCALE_SSHORTDATE, OutputBuffer, 255);
strTemp := string(OutputBuffer);
// if first letter is d then set to 'dd/MM/yyyy' else 'MM/dd/yyyy'
if Copy(strTemp, 1, 1) = 'd' then
strDateFormat := 'dd/MM/yyyy'
else
strDateFormat := 'MM/dd/yyyy';
try
APKini := TIniFile.Create(PathName + 'apk.ini');
try
APKini.WriteString('dbMaint', 'Pack date', FormatDateTime(strDateFormat, Now));
finally
APKini.Free;
end;
except
on E:Exception do begin
MessageDlg('Error saving pack date',
E.Message,
mtError, [mbOK], 690, dckActiveForm);
end;
end;
finally
// Free up the string list memory
StrDispose(OutputBuffer);
end;
end;

After I moved the code into an inline function there were no more memory leaks:
procedure TfrmMain.SavePackDate();
var
APKini : TIniFile;
strDateFormat : string;
strTemp : string;
function ShortDateFormat: string;
var
OutputBuffer: array[0..255] of Char;
SelectedLCID: LCID;
begin
SelectedLCID := GetUserDefaultLCID;
GetLocaleInfo(SelectedLCID, LOCALE_SSHORTDATE, OutputBuffer, 255);
Result := OutputBuffer;
end;
begin
try
// Determine local short date format
strTemp := ShortDateFormat;
// if first letter is d then set to 'dd/MM/yyyy' else 'MM/dd/yyyy'
if Copy(strTemp, 1, 1) = 'd' then
strDateFormat := 'dd/MM/yyyy'
else
strDateFormat := 'MM/dd/yyyy';
APKini := TIniFile.Create(PathName + 'apk.ini');
try
APKini.WriteString('dbMaint', 'Pack date', FormatDateTime(strDateFormat, Now));
finally
APKini.Free;
end;
except
on E:Exception do begin
MessageDlg('Error saving pack date',
E.Message,
mtError, [mbOK], 690, dckActiveForm);
end;
end;
end;

Population analysis improvements
I did find some time to make several improvements in this version of APK. The main focus of improvements this go round is the population analysis function. Probably the biggest change is removal of the 500 record limit, it now uses dynamic arrays so the number of records that may be analyzed is virtually unlimited. I added text to display the current model for comparison to the population analysis and a regression line of the current Kel or CL calculation.

apk_analysis_report

APK Population Analysis Report

Finally, I added a median Vd calculation, which is surprisingly complex:

function MedianVd: double;
var
n,i,middle : integer;
TempDouble : double;
TempArray : array of Double;
begin
// Set Length of TempArray
SetLength(TempArray,TotalPatients);
// Copy first column of RegressTable array
for i := 0 to TotalForArray do begin
TempArray[i] := RegressTable[i,0];
end;
// use truncated selection sort to find median
middle := (TotalForArray+1) div 2;
for i := 0 to middle do begin
for n := 1 to TotalForArray-i do begin
if TempArray[n] > TempArray[n-1] then
begin
TempDouble := TempArray[n];
TempArray[n] := TempArray[n-1];
TempArray[n-1] := TempDouble
end
end
end;
// find median
try
if odd(high(TempArray)) then
// when high(TempArray) is odd, there are an even number of elements in array.
// define median as average of two middle values.
result := (TempArray[middle] + TempArray[middle-1]) / 2
else
// when high(TempArray) is even, there are an odd number of elements in array.
// median is the middle value.
result := TempArray[middle];
finally
// Free memory used for TempArray
SetLength(TempArray,0);
end;
end;

As soon as I finish testing I will release this version into the wild. Enjoy.

And here’s my current favorite song (based on a true story), “Who’s the judge to decide how much this world should punish”:

UD Labels

In 1984, shortly after the pharmacy received a new IBM-PC XT with a green screen monitor and a dot matrix printer, I wrote my first practical pharmacy program: UD Labels.

In those days unit-dose packaging was a time-consuming and messy affair as the labels were printed with a mimeograph machine. It utilized stencils which were typed up on an IBM typewriter with the ribbon removed. The stencils were small and it was nearly impossible to line them up on the typewriter roller.

Before printing the roller had to be inked. Then, depending on how well it was inked, the first run of labels was either too faint or smeared with excess ink. After trial and error, eventually you were able to print labels that were somewhat legible.

The labels were pushed into the printing area with a mechanical arm, and often jammed or misaligned within the loud and complicated Rube Goldberg contraption. If you only needed a few pills for a rarely used drug, the machine was overkill. To save time, we dispensed a few pills into a prescription vial with a typed label. Any remaining pills had to be discarded.

It came to me one day while printing a report from the PC and watching (listening) to that ridiculous packaging machine chug away at labels, that a computer printer would be much easier, and would produce labels that could be easily read.

It took me about 2 days to write a simple little program in IBM BASIC, and it worked like a charm. My regional manager Russ Collins came by for a visit a few weeks later and I showed him what I’d developed. Shortly thereafter I was getting requests from other DOPS in our region for a copy of the program. And thus, my little software publishing enterprise began.

I’ve made countless improvements to the program in the thirty years since. The latest version is secure, has full database capabilities, and has the ability to print bar codes in any format.

A few software companies have since copied my idea, and charge a ridiculous price for their intellectual thievery. Even my current employer was duped into buying one of these ridiculously over-priced and impractical pieces of crapware. I was off one week then came back to find that all traces of my program were erased, they even erased all the old data. I was not advised or consulted. Blatant disrespect that I will certainly never forget.

Which brings us to 2014, thirty years after my brainstorm, UD labels has become the most popular RxKinetics PC software. Karma.

More Power (arr, arr, argh)

Tim “the tool man” approves.

“More models”.. I get this request often, and my usual answer is, in as nice a way as possible, do the work yourself lazy ass. I gave you the tools, learn how to use them.

But most people don’t get it. This sh*t isn’t carved into stone tablets by the hand of God. The way pharmacists are taught these days everything has to be in writing somewhere. So, here it is: Wagner: Fundamentals of Pharmacokinetics, nineteen-f**king-SEVENTY-FIVE. Yup, that’s right, almost FORTY f**king years ago. You weren’t even a gleam in your daddy’s eye. So, look it up old school in an actual lie-bary (sic) because it ain’t on the f**king inter-webs.

I’ve described the Wagner method before, in great detail here . No lie-bary required!

And now I’ve done all that work for you too! All it takes is a suck-up email to appeal to my altruism: “I like your program a lot because it is serious about PK/PD and pushes the concept of PK target attainment. As such, to optimize dosing with Abx is critical and apps to me are the future. I request that you build out the PK ability to dose other common beta lactams as well as the aztreonam and ceftazidime. I realize this can be done by entering the necessary parameters in the customizable part of the app but it would be easier for us if this was built in.”

Well, here it is.

I’ve also built the functionality to download these models into the Antibiotic Kinetics for Windows program.

AbPK how to
More Models!

Eventually this functionality will be added to the iOS and Android versions.

Group sing-along:

APK Eureka

Since adding EurekaLog to APK I’ve been able to track down the causes of some perplexing errors.

EConvertError

“X is not a valid floating point value”, this error has puzzled me for years. I have actually never seen it myself in 15+ years of using the program. I am only aware of it because of scattered reports from users.

Once nice feature of the EurekaLog report is a screenshot of the user’s current screen.
apk_fp_error

As usual, it is the user causing the error. Instead of entering a valid decimal value, ie, [1.02], they accidentally add a decimal point or two: [1.02.] or [1..02], etc. This error has cropped up twice in only 30 reports. I never would have imagined that someone would do something so fundamentally wrong. I also believe that it is impossible to imagine every possible scenario of software usage. I think it would take a troop of monkeys randomly pounding on multiple keyboards to approximate the myriad of potential mistakes.

Regardless, any software program must be able to prevent a user’s dumb mistake from causing a fatal error. All of the input boxes for numeric values in APK are coupled to procedures to prevent non-numeric input:
procedure TfrmMain.EditRecentSCrKeyPress(Sender: TObject; var Key: Char);
begin
if not (Key in ['0'..'9', DecimalSep, #8]) then
Key := #0;
end;

However, that’s not enough. I was under the impression that the StrToFloat function was fault tolerant, i.e., if the input was wrong, it would output a zero.
RecentSCr :=0;
if EditRecentSCr.Text <> '' then
RecentSCr := StrToFloat(EditRecentSCr.Text);

Unfortunately that’s not true. Instead, execution is halted with a fatal error and the program crashes.

The little known expression SysUtils.TryStrToFloat does have fault tolerance.
RecentSCr :=0;
if EditRecentSCr.Text <> '' then
TryStrToFloat(EditRecentSCr.Text, RecentSCr);
if RecentSCr <= 0 then begin ValidPatData := False; MessageDlg('Data entry error', 'Serum creatinine is a required field.', mtError, [mbOK], 100, dckActiveForm); EditRecentSCr.SetFocus; Exit; end;

Alternatively one can test the boolean result of TryStrToFloat within an if..then block to flag an error:
KelSD:=0;
if TryStrToFloat(EditKel.Text, KelSD) = False then
begin
IsOkay := False;
ErrMsg := 'Kel SD';
end;

So, I've spent the better part of 3 days re-coding all the StrToFloat calls to TryStrToFloat (also StrToInt -> TryStrToInt). And I've been able to prevent fatal errors.

However, if the field is linked to a database, the type of error appears to be unavoidable. Although it's not a fatal error, it still generates a EurekaLog error dialog.
apk_fp_error_dialog

I feel that the EurekaLog dialog doesn't really fit this situation. I'd rather catch the error, display a dialog, and rollback the edit which produced the error. But I've hit a brick wall, unable to get to the event triggering the error. I've tried BeforePost, PostError, BeforeEdit, and EditError, none of these events are trapped when a db field is edited. In the case of database fields it appears that no event is triggered. WTF!?

EIniFileException

Another error that EurekaLog discovered is "unable to save settings". The most fundamental requirement for use of this program is to have full read and write privileges to the APK folder. Without that, you can't really use the software. Again, regardless, any software program should not allow a user's dumb mistake to cause a fatal error. So, I combed through all code, and surrounded all ini writes in try..try..finally..except blocks:
try
UserIni := TIniFile.Create(AppDataPath + 'screen.ini');
try
UserIni.WriteInteger('Window Placement', 'Main.Top', frmMain.Top);
UserIni.WriteInteger('Window Placement', 'Main.Left', frmMain.Left);
finally
UserIni.Free;
end;
except
on E:Exception do
begin
MessageDlg('Error saving window placement',
E.Message,
mtError, [mbOK], 690, dckActiveForm);
Action := caFree;
end;
end;

ERangeError

The other common error that I've addressed in this update is "range check error". Unfortunately there are not enough reports to establish a pattern, so I simply switched off range error checking. Within the compiler settings, range error checking is turned off by default. I only switched it on because some Delphi guru advised it. Well, sorry guru, in the end your advice caused more problems than it solved.

Peace out.

Post script

Today I figured out how to bypass the EurekaLog dialog for the EDatabaseError 'not a valid floating point value':

  1. Activated Exception Filters in the EurekaLog options
  2. Added EDatabaseError
  3. Changed handler to RLT

Result is this "somewhat" more user friendly dialog, without the unnecessary "Send Error Report" option.:
apk_fp_error_RTL_dialog