unit uVitals;
{ Old class TVital currently not used - commented out at bottom of unit }

interface

uses
  SysUtils, Dialogs, Controls, Windows, Classes, ORClasses, ORCtrls, ORFn, Forms
  , TRPCB
  ;

const
  NoVitalOverrideValue = '^None^';

type
  TVitalType = (vtUnknown, vtTemp, vtPulse, vtResp, vtBP, vtHeight, vtWeight, vtPain,
                vtPO2, vtCVP, vtCircum);
  TValidVitalTypes = vtTemp..vtCircum;

procedure InitPainCombo(cboPain: TORComboBox);
procedure ConvertVital(VType: TVitalType; var VValue, VUnit: string);
function GetVitalStr(VType: TVitalType; rte, unt, UserStr, DateStr: string): string;
function GetVitalUser: string;
procedure AssignVitals2List(List: TStrings; ADateTime: TFMDateTime;
                       ALocation, ABP, ATemp, ATempUnits,
                       AResp, APulse, AHeight, AHeightUnits,
                       AWeight, AWeightUnits, APain: string);
function VitalInvalid(VitalControl: TControl; UnitsControl: TControl = nil;
                      OverrideValue: string = NoVitalOverrideValue): boolean;
function VitalControlTag(VType: TVitalType; UnitControl: boolean = FALSE): integer;
function ConvertHeight2Inches(Ht: string): string;
function FormatVitalForNote(VitalStr: string):String;
function ConvertVitalData(const Value: string; VitalType: TVitalType; UnitType: string = ''): string;
procedure VitalsFrameCreated(Frame: TFrame);
function ValidVitalsDate(var ADate: TFMDateTime; SkipFirst: boolean = FALSE; Show: boolean = true): boolean;
function IsNumericWeight(const x: string): Boolean;
procedure CloseVitalsDLL;

const
  VitalPCECodes: array[TValidVitalTypes] of string =
                                        { vtTemp      } ('TMP',
                                        { vtPulse     }  'PU',
                                        { vtResp      }  'RS',
                                        { vtBP        }  'BP',
                                        { vtHeight    }  'HT',
                                        { vtWeight    }  'WT',
                                        { vtPain      }  'PN',
                                        { vtPO2       }  'PO2',
                                        { vtCVP       }  'CVP',
                                        { vtCircum    }  'CG');


  VitalCodes: array[TValidVitalTypes] of string =
                                        { vtTemp      } ('T',
                                        { vtPulse     }  'P',
                                        { vtResp      }  'R',
                                        { vtBP        }  'BP',
                                        { vtHeight    }  'HT',
                                        { vtWeight    }  'WT',
                                        { vtPain      }  'PN',
                                        { vtPO2       }  'PO2',
                                        { vtCVP       }  'CVP',
                                        { vtCircum    }  'CG');

  TAG_VITTEMP    = 2;
  TAG_VITPULSE   = 4;
  TAG_VITRESP    = 3;
  TAG_VITBP      = 1;
  TAG_VITHEIGHT  = 5;
  TAG_VITWEIGHT  = 6;
  TAG_VITTEMPUNIT= 7;
  TAG_VITHTUNIT  = 8;
  TAG_VITWTUNIT  = 9;
  TAG_VITPAIN    = 10;
  TAG_VITDATE    = 11;

  VitalDateStr     = 'VST^DT^';
  VitalPatientStr  = 'VST^PT^';
  VitalLocationStr = 'VST^HL^';

  GMV_CONTEXT = 'OR CPRS GUI CHART';
  GMV_APP_SIGNATURE = 'CPRS';
  GMV_DEFAULT_TEMPLATE = '';

type
  VitalTags = TAG_VITBP..TAG_VITPAIN;

  TGMV_VitalsEnterForm = function(
        aBroker:TRPCBroker;
        aPatient, aLocation, aTemplate,aSignature:String;
        aDateTime:TDateTime): TCustomForm; stdcall;

  TGMV_VitalsEnterDLG = function(
        aBroker:TRPCBroker;
        aDFN, aLocation, aTemplate,aSignature:String;
        aDateTime:TDateTime;
        aName,anInfo:String): Integer; stdcall;

  TGFM_VitalsViewDLG = function(
        aBroker:TRPCBroker;
        aDFN, aLocation,
        DateStart, DateStop,
        aSignature,
        aContextIn,aContextOut,
        aName,anInfo,aHospitalName:String): Integer; stdcall;

  TGMV_VitalsViewForm = function(
        aBroker:TRPCBroker;
        aDFN, aLocation,
        DateStart, DateStop,
        aSignature,
        aContextIn,aContextOut,
        aName,anInfo,
        aDynamicParameter {HospitolName^Vital Type Abbreviation} :String): TCustomForm; stdcall;

  TGMV_LatestVitalsList = function (
        aBroker:TRPCBroker;
        aDFN,
        aDelim:String;
        bSilent:Boolean
        ): TStringList; stdcall;

  TGMV_VitalsExit = Procedure;

var
  VitalsDLLHandle : THandle = 0;
//  DLLForceClose : Boolean = False;  // jm - removed as part of timeout fix


const
  VitalTagSet = [TAG_VITBP..TAG_VITPAIN];
  VitalDateTagSet = [TAG_VITBP..TAG_VITDATE];

  VitalTagCodes: array[VitalTags] of TVitalType =
                            { TAG_VITBP         } (vtBP,
                            { TAG_VITTEMP       }  vtTemp,
                            { TAG_VITRESP       }  vtResp,
                            { TAG_VITPULSE      }  vtPulse,
                            { TAG_VITHEIGHT     }  vtHeight,
                            { TAG_VITWEIGHT     }  vtWeight,
                            { TAG_VITTEMPUNIT   }  vtTemp,
                            { TAG_VITHTUNIT     }  vtHeight,
                            { TAG_VITWTUNIT     }  vtWeight,
                            { TAG_VITPAIN       }  vtPain);

  VitalDesc: array[TVitalType] of string =
                                  { vtUnknown   } ('Unknown',
                                  { vtTemp      }  'Temperature',
                                  { vtPulse     }  'Pulse',
                                  { vtResp      }  'Respiration',
                                  { vtBP        }  'Blood Pressure',
                                  { vtHeight    }  'Height',
                                  { vtWeight    }  'Weight',
                                  { vtPain      }  'Pain Score',
                                  { vtPO2       }  'Pulse Oximetry',
                                  { vtCVP       }  'Central Venous Pressure',
                                  { vtCircum    }  'Circumference/Girth');

  VitalFormatedDesc: array[TValidVitalTypes] of string =
                                  { vtTemp      } ('Temperature ',
                                  { vtPulse     }  'Pulse       ',
                                  { vtResp      }  'Resp        ',
                                  { vtBP        }  'Blood Press. ',
                                  { vtHeight    }  'Height      ',
                                  { vtWeight    }  'Weight      ',
                                  { vtPain      }  'Pain Scale. ',
                                  { vtPO2       }  'Pulse Ox.   ',
                                  { vtCVP       }  'Cnt Vns Pres ',
                                  { vtCircum    }  'Circum/Girth ');
  vnumType  = 2;
  vnumValue = 3;
  vnumDate  = 4;

implementation

uses
  uCore, rCore, rVitals, Contnrs, fVitalsDate, VAUtils;
  
var
  uVitalFrames: TComponentList = nil;

procedure CloseVitalsDLL;
var
  VitalsExit : TGMV_VitalsExit;
begin
  if VitalsDLLHandle <> 0 then
  begin
    @VitalsExit := GetProcAddress(VitalsDLLHandle,PChar('GMV_VitalsExit'));
    if assigned(VitalsExit) then
      VitalsExit();
    FreeLibrary(VitalsDLLHandle);
    VitalsDLLHandle := 0;
//    DLLForceClose := True;   // jm - removed as part of timeout fix
  end;
end;

function VitalErrorText(VType: TVitalType): string;
begin
  case VType of
    vtTemp, vtHeight, vtWeight:
      Result := '- check rate and unit.';
    else
      Result := 'reading entered.';
  end;
  Result := 'Invalid ' + VitalDesc[VType] + ' ' + Result;
end;

procedure InitPainCombo(cboPain: TORComboBox);
begin
  cboPain.Items.Clear;
  cboPain.Items.Add('0^  - no pain');
  cboPain.Items.Add('1^  - slightly uncomfortable');
  cboPain.Items.Add('2^');
  cboPain.Items.Add('3^');
  cboPain.Items.Add('4^');
  cboPain.Items.Add('5^');
  cboPain.Items.Add('6^');
  cboPain.Items.Add('7^');
  cboPain.Items.Add('8^');
  cboPain.Items.Add('9^');
  cboPain.Items.Add('10^  - worst imaginable');
  cboPain.Items.Add('99^ - unable to respond');
end;

procedure ConvertVital(VType: TVitalType; var VValue, VUnit: string);
begin
  case VType of
    vtTemp:     if(VUnit = 'C') then  //if metric, convert to standard
                begin
                  if StrToFloat(VValue) > 0 then
                    //VValue := FloatToStr(StrToFloat(VValue) * 9.0 / 5.0 +32.0);
                    VValue := FloatToStr(Round((StrToFloat(VValue) * 9.0 / 5.0 +32.0)*100)/100);
                  VUnit := 'F';
                end;
                
    vtHeight:   if VUnit = 'CM' then
                begin
                  if StrToFloat(VValue) > 0 then
                    //VValue := FloatToStr(StrtoFloat(VValue) / 2.54);
                    VValue := FloatToStr(Round((StrtoFloat(VValue) / 2.54)*1000)/1000);
                  VUnit := 'IN';
                end;

    vtWeight:   if VUnit = 'KG' then
                begin
                  if StrToFloat(VValue) > 0 then
                    //VValue := FloatToStr(StrtoFloat(VValue) * 2.2046);
                    //
                    // the vitals package uses 2.2 (not 2.2046), so the GUI needs to use the
                    // same so conversions back & forth don't lead to errors
                    // this probably shouldn't even be done here - it should be done by the
                    // vitals package - KCM
                    //
                    VValue := FloatToStr(Round(StrtoFloat(VValue) * 2.2{046} *1000)/1000);
                  VUnit := 'LB';
                end;
  end;
end;

function GetVitalStr(VType: TVitalType; rte, unt, UserStr, DateStr: string): string;
begin
  Result := '';
  ConvertVital(VType, rte, unt);
  if rte <> '' then
  begin
    if(VType = vtPain) then unt := U;
    Result := 'VIT'+U+VitalPCECodes[VType]+U+U+U+rte+U+UserStr+U+unt+U+DateStr;
  end;
end;

function GetVitalUser: string;
var
  UserID: Int64;

begin
  UserID := Encounter.Provider;
  if UserID <= 0 then
    UserID := User.DUZ;
  Result := IntToStr(UserID);
end;

procedure AssignVitals2List(List: TStrings; ADateTime: TFMDateTime;
                       ALocation, ABP, ATemp, ATempUnits,
                       AResp, APulse, AHeight, AHeightUnits,
                       AWeight, AWeightUnits, APain: string);
var
  UserStr, DateStr: string;

  procedure AddVital(VType: TVitalType; ARte: string; AUnit: string = '');
  var
    VStr: string;

  begin
    VStr := GetVitalStr(VType, ARte, AUnit, UserStr, DateStr);
    if(VStr <> '') then
      List.Add(VStr);
  end;

begin
  with List do
  begin
    UserStr := GetVitalUser;
    DateStr := FloatToStr(ADateTime);
    clear;

    Add(VitalDateStr     + DateStr);
    Add(VitalPatientStr  + Patient.DFN);       // encounter Patient  //*DFN*
    Add(VitalLocationStr + ALocation);
    AddVital(vtBP,     ABP);                   // Blood Pressure
    AddVital(vtTemp,   ATemp, ATempUnits);     // Temperature
    AddVital(vtResp,   AResp);                 // Resp
    AddVital(vtPulse,  APulse);                // Pulse
    AddVital(vtHeight, AHeight, AHeightUnits); // Height
    AddVital(vtWeight, AWeight, AWeightUnits); // Weight
    AddVital(vtPain,   APain);                 // Pain
  end;
end;

function VitalInvalid(VitalControl: TControl; UnitsControl: TControl = nil;
                       OverrideValue: string = NoVitalOverrideValue): boolean;
var
  rte, unt: string;
  Tag: integer;
  VType: TVitalType;

begin
  Tag := -1;

  if(OverrideValue = NoVitalOverrideValue) then
  begin
    if(assigned(VitalControl)) then
    begin
      rte := TORExposedControl(VitalControl).Text;
      Tag := VitalControl.Tag;
    end
    else
      rte := '';
  end
  else
  begin
    rte := OverrideValue;
    if(assigned(VitalControl)) then
      Tag := VitalControl.Tag;
  end;

  if(assigned(UnitsControl)) then
  begin
    unt := TORExposedControl(UnitsControl).Text;
    if(Tag < 0) then
      Tag := UnitsControl.Tag;
  end
  else
    unt := '';

  if(Tag >= low(VitalTags)) and (Tag <= high(VitalTags)) then
    VType := VitalTagCodes[Tag]
  else
    VType := vtUnknown;
 //pain does not need to be validated because the combo box limits the selection.
  if(VType = vtPain) then
    Result := FALSE
  else
  begin
    Result := TRUE;
    if(VType <> vtUnknown) then
    begin
      if (rte = '') then
        Result := FALSE
      else
      if (VerifyVital(VitalPCECodes[VType],rte,unt) = True) then
        Result := FALSE;
    end;
  end;
  //  GRE 2/12/03 added to disallow user entering "lb" with weight NOIS MWV-0103-22037
  if VType = vtWeight then
  begin
     if (IsNumericWeight(rte) = FALSE) then
         Result := True;
  end;
  if(Result) then
    ShowMsg(VitalErrorText(VType));
end;

function VitalControlTag(VType: TVitalType; UnitControl: boolean = FALSE): integer;
var
  i,cnt: integer;

begin
  if UnitControl then
    cnt := 0
  else
    cnt := 1;
  Result := -1;
  for i := low(VitalTags) to high(VitalTags) do
  begin
    if(VitalTagCodes[i] = VType) then
    begin
      inc(cnt);
      if(cnt = 2) then
      begin
        Result := i;
        break;
      end;
    end;
  end;
end;

function ConvertHeight2Inches(Ht: string): string;
var
 c: char;
 i: integer; //counter
 inchstr,feetstr : string;
 feet: boolean;
 v: double;

begin
  feet := False;
  result := '';
  feetstr := '';
  inchstr := '';

  // check for feet
  for i := 1 to (length(Ht)) do
  begin
    c := Ht[i];
    if (c = '''') then feet := True;
  end;

  if (feet = True) then
  begin
    i := 1;
    while (Ht[i] <> '''') do
    begin
      if (Ht[i] in ['0'..'9']) or (Ht[i] = '.') then
        feetstr := feetstr + Ht[i];
      inc(i);
    end;
    while (i <= length(Ht)) and (Ht[i] <> '"') and
     (Ht[i] <> '') do
      begin
      if (Ht[i] in ['0'..'9']) or (Ht[i] = '.') then
        inchstr := inchstr + Ht[i];
        inc(i);
      end;
    v := 0;
    if (feetstr <> '') then
      v := v + (StrTofloat(feetstr)*12);
    if(inchstr <> '') then
      v := v + StrToFloat(inchstr);
    result := floatToStr(v);
    //add here to convert to CM if CM is the unit

  end
  else //no feet
  begin
    for i := 1 to (length(Ht)) do
    begin
      c := Ht[i]; //first character
      if (c in ['0'..'9']) or (c = '.') then
        result := result + c;
      if (c = '"') then break;
    end;
  end;
end;

{
1215^T^98.6^2991108.11^98.6 F^(37.0 C)
1217^P^70^2991108.11^70
1216^R^18^2991108.11^18
1214^BP^120/70^2991108.11^120/70
1218^HT^70^2991108.11^70 in^(177.8 cm)
1219^WT^200^2991108.11^200 lb^(90.0 kg)
1220^PN^1^2991108.11^1
}
  //format string as it should appear on the PCE panel.
function FormatVitalForNote(VitalStr: string):String;
var
  Code, Value: string;
  v: TVitalType;

begin
  Code := UpperCase(Piece(VitalStr, U, vnumType));
  for v := low(TValidVitalTypes) to high(TValidVitalTypes) do
  begin
    if(Code = VitalCodes[v]) then
    begin
      Value := ConvertVitalData(Piece(VitalStr, U, vnumValue), v);
      if(v = vtPain) and (Value = '99') then
        Value := 'Unable to respond.';
      Result := VitalFormatedDesc[v] + Value + '    ' +
      FormatFmDateTime('mmm dd,yyyy hh:nn',(StrToFloat(Piece(VitalStr, U, vnumDate))));
    end
  end;
end;

function ConvertVitalData(const Value: string; VitalType: TVitalType; UnitType: string = ''): string;
var
  dbl: Double;

begin
  Result := Value;
  if(VitalType in [vtTemp, vtHeight, vtWeight]) then
  begin
    try
      dbl := StrToFloat(Value);
    except
      on EConvertError do
        dbl := 0
      else
        raise;
    end;
    if(dbl <> 0) then
    begin
      UnitType := UpperCase(UnitType);
      case VitalType of
        vtTemp:
          begin
            if(UnitType = 'C') then
            begin
              dbl := dbl * (9/5);
              dbl := dbl + 32;
              dbl := round(dbl * 10) / 10;
              Result := FloatToStr(dbl) + ' F (' + Result + ' C)';
            end
            else
            begin
              dbl := dbl - 32;
              dbl := dbl * (5/9);
              dbl := round(dbl * 10) / 10;
              Result := Result + ' F (' + FloatToStr(dbl) + ' C)';
            end;
          end;

        vtHeight:
          begin
            if(UnitType = 'CM') then
            begin
              dbl := dbl / 2.54;
              dbl := round(dbl * 10) / 10;
              Result := FloatToStr(dbl) + ' in [' + Result + ' cm)';
            end
            else
            begin
              dbl := dbl * 2.54;
              dbl := round(dbl * 10) / 10;
              Result := Result + ' in [' + FloatToStr(dbl) + ' cm)';
            end;
          end;

        vtWeight:
          begin
            if(UnitType = 'KG') then
            begin
              dbl := dbl * 2.2;
              dbl := round(dbl * 10) / 10;
              Result := FloatToStr(dbl) + ' lb (' + Result + ' kg)';
            end
            else
            begin
              dbl := dbl / 2.2;
              dbl := round(dbl * 10) / 10;
              Result := Result + ' lb (' + FloatToStr(dbl) + ' kg)';
            end;
          end;
      end;
    end;
  end;
end;

procedure VitalsFrameCreated(Frame: TFrame);
begin
  if not assigned(uVitalFrames) then
    uVitalFrames := TComponentList.Create(FALSE);
  uVitalFrames.Add(Frame);
end;

function ValidVitalsDate(var ADate: TFMDateTime; SkipFirst: boolean = FALSE; Show: boolean = true): boolean;   //AGP Change 26.1
var
  frmVitalsDate: TfrmVitalsDate;
  ok: boolean;

begin
  Result := TRUE;
  while (Result and (SkipFirst or (ADate > FMNow))) do
  begin
    if(SkipFirst) then
    begin
      ok := TRUE;
      SkipFirst := FALSE;
    end
    else
    ok := (InfoBox('Vital sign Date/Time entered (' + FormatFMDateTime('mmm dd yyyy hh:nn', ADate) +
            ') cannot be in the future.' + CRLF +
            'If you do not change the entered date/time vitals information will be lost.' + CRLF +
            'Do you want to enter a new Date/Time?',
            'Invalid Vital Entry Date/Time',
            MB_YESNO + MB_ICONWARNING) = ID_YES);
    if ok then
    begin
      frmVitalsDate := TfrmVitalsDate.Create(Application);
      try
        frmVitalsDate.dteVitals.FMDateTime := ADate;
        frmVitalsDate.btnNow.Visible := Show; //AGP Change 26.1
        if frmVitalsDate.ShowModal = mrOK then
          ADate := frmVitalsDate.dteVitals.FMDateTime;
      finally
        frmVitalsDate.Free;
      end;
    end
    else
      Result := FALSE;
  end;
end;

function IsNumericWeight(const x: string): Boolean;
var
    i: Integer;
begin
  Result := True;
  for i := 1 to Length(x) do if not (x[i] in ['0'..'9','.']) then Result := False;
end;
(* Old class currently not used
{$OPTIMIZATION OFF}                              // REMOVE AFTER UNIT IS DEBUGGED

interface

uses SysUtils, Classes;

type
  TVital = class(TObject)
  {class for vital}
  Private
    Fsend:   Boolean;  //do we need this?
  public
    Typ:      String;      //type
    Value:    Single;
    Unt:      String;      //unit
    Provider: Integer;
    procedure Assign(Src: TVital);  //will we need assign?
    procedure Clear;
    procedure SetFromString(const x: string);
    function DelimitedStr: string;
  end;


implementation

uses ORFn, fPCEEdit, uPCE;

Procedure TVital.Assign(Src: TVital);
{assigns the values from one vital to another}
begin
  Fsend    := Src.Fsend;
  Typ      := Src.Typ;
  Value    := Src.Value;
  Unt      := Src.Unt;
  provider := Src.Provider;
end;

procedure Tvital.Clear;
{clear all fields}
begin
  Fsend := False;
  Typ   := '';
  Value := 0.0;
  Unt   := '';  //will default to Inches/LBs/Farenheit on M side,
                //depending on the Type
  //Provider := UProvider;
end;

Procedure TVital.SetFromString(const X: string);
begin
  Typ      := Piece(x, U, 2);
  Value    := StrToFloat(Piece(x, U, 5));
  Provider := StrToInt(Piece(x, U, 6));
  Unt      := Piece(x, U, 7);
end;

function TVital.DelimitedStr: string;
begin
  Result := 'VIT' + U + Typ + U + U + U + FloatToStr(Value) + U +
    IntToStr(Provider) + U + Unt;
end;
*)

initialization

finalization
  KillObj(@uVitalFrames);

end.
