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):

1
if (Printer.PrinterIndex > 0)then
2
  begin
3
     // Print the consult
4
  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:

01
procedure TfrmMain.SavePackDate();
02
var
03
   APKini  : TIniFile;
04
   strDateFormat : string;
05
   strTemp : string;
06
   OutputBuffer: PChar;
07
   SelectedLCID: LCID;
08
begin
09
   try
10
       // Determine local short date format
11
       OutputBuffer := StrAlloc(255);
12
       SelectedLCID := GetUserDefaultLCID;
13
       GetLocaleInfo(SelectedLCID, LOCALE_SSHORTDATE, OutputBuffer, 255);
14
       strTemp := string(OutputBuffer);
15
       // if first letter is d then set to 'dd/MM/yyyy' else 'MM/dd/yyyy'
16
       if Copy(strTemp, 1, 1) = 'd' then
17
          strDateFormat := 'dd/MM/yyyy'
18
       else
19
          strDateFormat := 'MM/dd/yyyy';
20
       try
21
          APKini := TIniFile.Create(PathName + 'apk.ini');
22
          try
23
             APKini.WriteString('dbMaint', 'Pack date', FormatDateTime(strDateFormat, Now));
24
          finally
25
             APKini.Free;
26
          end;
27
       except
28
          on E:Exception do begin
29
          MessageDlg('Error saving pack date',
30
                      E.Message,
31
                      mtError, [mbOK], 690, dckActiveForm);
32
          end;
33
       end;
34
   finally
35
      // Free up the string list memory
36
      StrDispose(OutputBuffer);
37
   end;
38
end;

After I moved the code into an inline function there were no more memory leaks:

01
procedure TfrmMain.SavePackDate();
02
var
03
   APKini  : TIniFile;
04
   strDateFormat : string;
05
   strTemp : string;
06
   function ShortDateFormat: string;
07
   var
08
      OutputBuffer: array[0..255] of Char;
09
      SelectedLCID: LCID; 
10
   begin
11
      SelectedLCID := GetUserDefaultLCID;
12
      GetLocaleInfo(SelectedLCID, LOCALE_SSHORTDATE, OutputBuffer, 255);
13
      Result := OutputBuffer;
14
   end;
15
begin
16
   try
17
      // Determine local short date format
18
      strTemp := ShortDateFormat;
19
      // if first letter is d then set to 'dd/MM/yyyy' else 'MM/dd/yyyy'
20
      if Copy(strTemp, 1, 1) = 'd' then
21
         strDateFormat := 'dd/MM/yyyy'
22
      else
23
         strDateFormat := 'MM/dd/yyyy';
24
      APKini := TIniFile.Create(PathName + 'apk.ini');
25
      try
26
          APKini.WriteString('dbMaint', 'Pack date', FormatDateTime(strDateFormat, Now));
27
      finally
28
          APKini.Free;
29
      end;
30
   except
31
      on E:Exception do begin
32
          MessageDlg('Error saving pack date',
33
                      E.Message,
34
                      mtError, [mbOK], 690, dckActiveForm);
35
      end;
36
   end;
37
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:

01
02
function MedianVd: double;
03
var
04
    n,i,middle : integer;
05
    TempDouble : double;
06
    TempArray  : array of Double;
07
begin
08
    // Set Length of TempArray
09
    SetLength(TempArray,TotalPatients);
10
    // Copy first column of RegressTable array
11
    for i := 0 to TotalForArray do begin
12
       TempArray[i] := RegressTable[i,0];
13
    end;
14
    // use truncated selection sort to find median
15
    middle := (TotalForArray+1) div 2;
16
    for i := 0 to middle do begin
17
       for n := 1 to TotalForArray-i do begin
18
          if TempArray[n] > TempArray[n-1] then
19
             begin
20
                TempDouble := TempArray[n];
21
                TempArray[n] := TempArray[n-1];
22
                TempArray[n-1] := TempDouble
23
             end
24
       end
25
    end;
26
    // find median
27
    try
28
       if odd(high(TempArray)) then
29
          // when high(TempArray) is odd, there are an even number of elements in array.
30
          // define median as average of two middle values.
31
          result := (TempArray[middle] + TempArray[middle-1]) / 2
32
       else
33
          // when high(TempArray) is even, there are an odd number of elements in array.
34
          // median is the middle value.
35
          result := TempArray[middle];
36
    finally
37
       // Free memory used for TempArray
38
       SetLength(TempArray,0);
39
    end;
40
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”:

Comments are closed.