Gnumed.wxpython.gmTopPanel

Package Gnumed :: Package wxpython :: Module gmTopPanel |
[frames] | no frames]

# Source Code for Module Gnumed.wxpython.gmTopPanel

      1  # GNUmed
      2
      3  #===========================================================
      4  __author__  = "R.Terry <rterry@gnumed.net>, I.Haywood <i.haywood@ugrad.unimelb.edu.au>, K.Hilbert <Karsten.Hilbert@gmx.net>"
      5  __license__ = "GPL v2 or later"
      6
      7
      8  import sys
      9  import os.path
     10  import datetime as pyDT
     11  import logging
     12  import decimal
     13
     14
     15  import wx
     16
     17
     18  from Gnumed.pycommon import gmGuiBroker
     19  from Gnumed.pycommon import gmDispatcher
     20  from Gnumed.pycommon import gmTools
     21  from Gnumed.pycommon import gmCfg
     22  from Gnumed.pycommon import gmCfg2
     23  from Gnumed.pycommon import gmDateTime
     24  from Gnumed.pycommon import gmI18N
     25  from Gnumed.pycommon import gmExceptions
     26
     27  from Gnumed.business import gmPerson
     28  from Gnumed.business import gmEMRStructItems
     29  from Gnumed.business import gmAllergy
     30  from Gnumed.business import gmLOINC
     31  from Gnumed.business import gmClinicalCalculator
     32  from Gnumed.business import gmPathLab
     33  from Gnumed.business import gmPraxis
     34
     35  from Gnumed.wxpython import gmGuiHelpers
     36  from Gnumed.wxpython import gmDemographicsWidgets
     37  from Gnumed.wxpython import gmAllergyWidgets
     38  from Gnumed.wxpython import gmPatSearchWidgets
     39  from Gnumed.wxpython import gmEMRStructWidgets
     40  from Gnumed.wxpython import gmPatPicWidgets
     41
     42
     43  _log = logging.getLogger('gm.ui')
     44
     45  #===========================================================
     46  from Gnumed.wxGladeWidgets import wxgTopPnl
     47
     48 -class cTopPnl(wxgTopPnl.wxgTopPnl):
     49
     50 -        def __init__(self, *args, **kwargs):
     51
     52                  wxgTopPnl.wxgTopPnl.__init__(self, *args, **kwargs)
     53
     54                  self.__gb = gmGuiBroker.GuiBroker()
     55
     56                  self.curr_pat = gmPerson.gmCurrentPatient()
     57
     58                  self.__init_ui()
     59                  self.__register_interests()
     60
     61          #-------------------------------------------------------
     62 -        def __init_ui(self):
     63                  cfg = gmCfg2.gmCfgData()
     64                  if cfg.get(option = 'slave'):
     65                          self._TCTRL_patient_selector.SetEditable(0)
     66                          self._TCTRL_patient_selector.SetToolTip(None)
     67
     68                  if sys.platform == 'darwin':
     69                          _log.debug('adjusting font size on Mac for top panel parts')
     70                          for ctrl in [self._TCTRL_patient_selector, self._LBL_age, self._LBL_allergies, self._TCTRL_allergies]:
     71                                  curr_font = ctrl.GetFont()
     72                                  mac_font = wx.Font(curr_font.GetNativeFontInfo())
     73                                  mac_font.SetPointSize(pointSize = int(curr_font.GetPointSize() / 0.8))
     74                                  ctrl.SetFont(mac_font)
     75
     76                  self.__lab_panel = self.__get_lab_panel()
     77
     78          #-------------------------------------------------------
     79 -        def __register_interests(self):
     80                  # events
     81                  self._TCTRL_allergies.Bind(wx.EVT_LEFT_DCLICK, self._on_allergies_dclicked)
     82
     83                  # client internal signals
     84                  gmDispatcher.connect(signal = 'post_patient_selection', receiver = self._on_post_patient_selection)
     85                  gmDispatcher.connect(signal = 'focus_patient_search', receiver = self._on_focus_patient_search)
     86
     87                  gmDispatcher.connect(signal = 'gm_table_mod', receiver = self._on_database_signal)
     88
     89          #----------------------------------------------
     90          # event handling
     91          #----------------------------------------------
     92 -        def _on_allergies_dclicked(self, evt):
     93                  if not self.curr_pat.connected:
     94                          gmDispatcher.send('statustext', msg = _('Cannot activate Allergy Manager. No active patient.'))
     95                          return
     96                  dlg = gmAllergyWidgets.cAllergyManagerDlg(parent=self, id=-1)
     97                  dlg.ShowModal()
     98                  return
     99
    100          #----------------------------------------------
    101 -        def _on_database_signal(self, **kwds):
    102
    103                  if kwds['table'] not in ['dem.identity', 'dem.names', 'dem.identity_tag', 'clin.allergy', 'clin.allergy_state', 'clin.test_result', 'clin.patient']:
    104                          return True
    105
    106                  if self.curr_pat.connected:
    107                          # signal is not about our patient: ignore signal
    108                          if kwds['pk_identity'] != self.curr_pat.ID:
    109                                  return True
    110
    111                  if kwds['table'] == 'dem.identity':
    112                          # we don't care about newly INSERTed or DELETEd patients
    113                          if kwds['operation'] != 'UPDATE':
    114                                  return True
    115                          self.__update_age_label()
    116                          return True
    117
    118                  if kwds['table'] == 'dem.names':
    119                          self.__update_age_label()
    120                          return True
    121
    122                  if kwds['table'] == 'dem.identity_tag':
    123                          self.__update_tags()
    124                          return True
    125
    126                  if kwds['table'] in ['clin.allergy', 'clin.allergy_state']:
    127                          self.__update_allergies()
    128                          return True
    129
    130                  if kwds['table'] in ['clin.test_result', 'clin.patient']:
    131                          self.__update_lab()
    132                          return True
    133
    134                  return True
    135
    136          #----------------------------------------------
    137 -        def _on_post_patient_selection(self, **kwargs):
    138                  wx.CallAfter(self.__on_post_patient_selection)
    139
    140          #-------------------------------------------------------
    141 -        def __on_post_patient_selection(self):
    142                  self.__update_age_label()
    143                  self.__update_allergies()
    144                  self.__update_tags()
    145                  self.__update_lab()
    146                  self.Layout()
    147
    148          #-------------------------------------------------------
    149 -        def _on_focus_patient_search(self, **kwargs):
    150                  wx.CallAfter(self._TCTRL_patient_selector.SetFocus)
    151
    152          #-------------------------------------------------------
    153          # internal API
    154          #-------------------------------------------------------
    155 -        def __get_lab_panel(self):
    156                  # get panel to use
    157                  dbcfg = gmCfg.cCfgSQL()
    158                  pk_panel = dbcfg.get2 (
    159                          option = u'horstspace.top_panel.lab_panel',
    160                          workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
    161                          bias = 'user'
    162                  )
    163                  if pk_panel is None:
    164                          return None
    165
    166                  try:
    167                          panel = gmPathLab.cTestPanel(aPK_obj = pk_panel)
    168                  except gmExceptions.ConstructorError:
    169                          _log.exception('cannot load configured test panel')
    170                          panel = None
    171                  if panel is not None:
    172                          return panel
    173
    174                  _log.error('Cannot load test panel [#%s] configured for patient pane (horstspace.top_panel.lab_panel).', pk_panel)
    175                  gmGuiHelpers.gm_show_error (
    176                          title = _('GNUmed startup'),
    177                          error = _(
    178                                  'Cannot load test panel [#%s] configured\n'
    179                                  'for the top pane with option\n'
    180                                  '\n'
    181                                  ' <horstspace.top_panel.lab_panel>\n'
    182                                  '\n'
    183                                  'Please reconfigure.'
    184                          ) % pk_panel
    185                  )
    186                  return None
    187
    188          #-------------------------------------------------------
    189 -        def __update_tags(self):
    190                  self._PNL_tags.refresh(patient = self.curr_pat)
    191
    192          #-------------------------------------------------------
    193 -        def __update_lab(self):
    194
    195                  if not self.curr_pat.connected:
    196                          self._LBL_lab.SetLabel('')
    197                          return
    198
    199                  tests2show = []
    200
    201                  rrs = self.curr_pat.emr.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_rr_quantity, max_no_of_results = 1)
    202                  if len(rrs) == 0:
    203                          tests2show.append(_('RR ?'))
    204                  else:
    205                          tests2show.append(rrs[0]['unified_val'])
    206
    207                  hrs = self.curr_pat.emr.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_heart_rate_quantity, max_no_of_results = 1)
    208                  if len(hrs) > 0:
    209                          tests2show.append('%s %s' % (hrs[0]['abbrev_tt'], hrs[0]['unified_val']))
    210
    211                  bmi = self.curr_pat.emr.bmi
    212                  if bmi.numeric_value is not None:
    213                          tests2show.append(_('BMI %s') % bmi.numeric_value.quantize(decimal.Decimal('1.')))
    214                  else:
    215                          weights = self.curr_pat.emr.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_weight, max_no_of_results = 1)
    216                          if len(weights) == 0:
    217                                  tests2show.append(_('BMI ?'))
    218                          else:
    219                                  tests2show.append('%s%s' % (weights[0]['unified_val'], weights[0]['val_unit']))
    220
    221                  gfr_or_crea = self.curr_pat.emr.best_gfr_or_crea
    222                  if gfr_or_crea is None:
    223                          tests2show.append(_('GFR ?'))
    224                  else:
    225                          try:
    226                                  tests2show.append(_('GFR %s') % gfr_or_crea.numeric_value.quantize(decimal.Decimal('1.')))
    227                          except AttributeError:
    228                                  tests2show.append('%s %s' % (gfr_or_crea['abbrev_tt'], gfr_or_crea['unified_val']))
    229
    230                  edc = self.curr_pat.emr.EDC
    231                  if edc is not None:
    232                          if self.curr_pat.emr.EDC_is_fishy:
    233                                  tests2show.append(_('?EDC %s') % gmDateTime.pydt_strftime(edc, '%Y-%b-%d', accuracy = gmDateTime.acc_days))
    234                          else:
    235                                  tests2show.append(_('EDC %s') % gmDateTime.pydt_strftime(edc, '%Y-%b-%d', accuracy = gmDateTime.acc_days))
    236
    237                  inrs = self.curr_pat.emr.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_inr_quantity, max_no_of_results = 1)
    238                  if len(inrs) > 0:
    239                          tests2show.append('%s %s' % (inrs[0]['abbrev_tt'], inrs[0]['unified_val']))
    240
    241                  # include panel if configured, only show if exist
    242                  if self.__lab_panel is not None:
    243                          for result in self.__lab_panel.get_most_recent_results(pk_patient = self.curr_pat.ID, order_by = 'unified_abbrev', group_by_meta_type = True):
    244                                  tests2show.append('%s %s' % (result['abbrev_tt'], result['unified_val']))
    245
    246                  self._LBL_lab.SetLabel('; '.join(tests2show))
    247
    248          #-------------------------------------------------------
    249 -        def __update_age_label(self):
    250
    251                  # no patient
    252                  if not self.curr_pat.connected:
    253                          self._LBL_age.SetLabel(_('<Age>'))
    254                          self._LBL_age.SetToolTip(_('no patient selected'))
    255                          return
    256
    257                  # gender is always known
    258                  tt = _('Gender: %s (%s) - %s\n') % (
    259                          self.curr_pat.gender_symbol,
    260                          gmTools.coalesce(self.curr_pat['gender'], '?'),
    261                          self.curr_pat.gender_string
    262                  )
    263
    264                  # dob is not known
    265                  if self.curr_pat['dob'] is None:
    266                          age = '%s  %s' % (
    267                                  self.curr_pat.gender_symbol,
    268                                  self.curr_pat.get_formatted_dob()
    269                          )
    270                          self._LBL_age.SetLabel(age)
    271                          self._LBL_age.SetToolTip(tt)
    272                          return
    273
    274                  tt += _('Born: %s\n') % self.curr_pat.get_formatted_dob(format = '%d %b %Y')
    275
    276                  # patient is dead
    277                  if self.curr_pat['deceased'] is not None:
    278                          tt += _('Died: %s\n') % gmDateTime.pydt_strftime(self.curr_pat['deceased'], '%d %b %Y')
    279                          tt += _('At age: %s\n') % self.curr_pat['medical_age']
    280                          age = '%s  %s - %s (%s)' % (
    281                                  self.curr_pat.gender_symbol,
    282                                  self.curr_pat.get_formatted_dob(format = '%d %b %Y'),
    283                                  gmDateTime.pydt_strftime(self.curr_pat['deceased'], '%d %b %Y'),
    284                                  self.curr_pat['medical_age']
    285                          )
    286                          if self.curr_pat['dob_is_estimated']:
    287                                  tt += _(' (date of birth and age are estimated)\n')
    288                          self._LBL_age.SetLabel(age)
    289                          self._LBL_age.SetToolTip(tt)
    290                          return
    291
    292                  # patient alive
    293                  now = gmDateTime.pydt_now_here()
    294
    295                  # patient birthday ?
    296                  if self.curr_pat.get_formatted_dob(format = '%m-%d', honor_estimation = False) == now.strftime('%m-%d'):
    297                          template = _('%(sex)s  %(dob)s (%(age)s today !)')
    298                          tt += _("\nToday is the patient's birthday !\n\n")
    299                          tt += _('Age: %s\n') % self.curr_pat['medical_age']
    300                  else:
    301                          tt += _('Age: %s, birthday:\n') % self.curr_pat['medical_age']
    302                          if self.curr_pat.current_birthday_passed is True:
    303                                  template = '%(sex)s  %(dob)s%(l_arr)s (%(age)s)'
    304                                  tt += ' ' + _('last: %s ago (this year)') % gmDateTime.format_apparent_age_medically (
    305                                          age = gmDateTime.calculate_apparent_age(start = self.curr_pat.birthday_this_year, end = now)
    306                                  ) + '\n'
    307                                  tt += ' ' + _('next: in %s (next year)') % gmDateTime.format_apparent_age_medically (
    308                                          age = gmDateTime.calculate_apparent_age(start = now, end = self.curr_pat.birthday_next_year)
    309                                  ) + '\n'
    310                          elif self.curr_pat.current_birthday_passed is False:
    311                                  template = '%(sex)s  %(r_arr)s%(dob)s (%(age)s)'
    312                                  tt += ' ' + _('next: in %s (this year)') % gmDateTime.format_apparent_age_medically (
    313                                          age = gmDateTime.calculate_apparent_age(start = now, end = self.curr_pat.birthday_this_year)
    314                                  ) + '\n'
    315                                  tt += ' ' + _('last: %s ago (last year)') % gmDateTime.format_apparent_age_medically (
    316                                          age = gmDateTime.calculate_apparent_age(start = self.curr_pat.birthday_last_year, end = now)
    317                                  ) + '\n'
    318                          else:   # None, unknown
    319                                  template = '%(sex)s  %(dob)s (%(age)s)'
    320
    321                  # FIXME: if the age is below, say, 2 hours we should fire
    322                  # a timer here that updates the age in increments of 1 minute ... :-)
    323                  age = template % {
    324                          'sex': self.curr_pat.gender_symbol,
    325                          'dob': self.curr_pat.get_formatted_dob(format = '%d %b %Y'),
    326                          'age': self.curr_pat['medical_age'],
    327                          'r_arr': gmTools.u_arrow2right,
    328                          'l_arr': gmTools.u_left_arrow
    329                  }
    330
    331                  # Easter Egg ;-)
    332                  if self.curr_pat['lastnames'] == 'Leibner':
    333                          if self.curr_pat['firstnames'] == 'Steffi':
    334                                  if self.curr_pat['preferred'] == 'Wildfang':
    335                                          age = '%s %s' % (gmTools.u_black_heart, age)
    336
    337                  if self.curr_pat['dob_is_estimated']:
    338                          tt += _(' (date of birth and age are estimated)\n')
    339
    340                  self._LBL_age.SetLabel(age)
    341                  self._LBL_age.SetToolTip(tt)
    342
    343          #-------------------------------------------------------
    344 -        def __update_allergies(self, **kwargs):
    345
    346                  if not self.curr_pat.connected:
    347                          self._LBL_allergies.SetForegroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT))
    348                          self._TCTRL_allergies.SetForegroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT))
    349                          self._TCTRL_allergies.SetValue('')
    350                          self._TCTRL_allergies.SetToolTip('')
    351                          return
    352
    353                  show_red = True
    354
    355                  emr = self.curr_pat.emr
    356                  state = emr.allergy_state
    357
    358                  # state in tooltip
    359                  if state['last_confirmed'] is None:
    360                          confirmed = _('never')
    361                  else:
    362                          confirmed = gmDateTime.pydt_strftime(state['last_confirmed'], '%Y %b %d')
    363                  tt = (state.state_string + (90 * ' '))[:90] + '\n'
    364                  tt += _('last confirmed %s\n') % confirmed
    365                  tt += gmTools.coalesce(state['comment'], '', _('Comment (%s): %%s') % state['modified_by'])
    366                  tt += '\n'
    367
    368                  # allergies
    369                  display = []
    370                  for allergy in emr.get_allergies():
    371                          # in field: "true" allergies only, not intolerances
    372                          if allergy['type'] == 'allergy':
    373                                  display.append(allergy['descriptor'][:10].strip() + gmTools.u_ellipsis)
    374                          # in tooltip
    375                          if allergy['definite']:
    376                                  certainty = _('definite')
    377                          else:
    378                                  certainty = _('suspected')
    379                          reaction = gmTools.coalesce(allergy['reaction'], _('reaction not recorded'))
    380                          if len(reaction) > 50:
    381                                  reaction = reaction[:50] + gmTools.u_ellipsis
    382                          tt += '%s (%s, %s): %s\n' % (
    383                                  allergy['descriptor'],
    384                                  allergy['l10n_type'],
    385                                  certainty,
    386                                  reaction
    387                          )
    388
    389                  if len(display) == 0:
    390                          display = state.state_symbol
    391                          if display == gmTools.u_diameter:
    392                                  show_red = False
    393                  else:
    394                          display = ','.join(display)
    395
    396                  if state['last_confirmed'] is not None:
    397                          display += gmDateTime.pydt_strftime(state['last_confirmed'], ' (%Y %b)')
    398
    399                  if show_red:
    400                          self._LBL_allergies.SetForegroundColour('red')
    401                          self._TCTRL_allergies.SetForegroundColour('red')
    402                  else:
    403                          self._LBL_allergies.SetForegroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT))
    404                          self._TCTRL_allergies.SetForegroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT))
    405
    406                  self._TCTRL_allergies.SetValue(display)
    407                  self._TCTRL_allergies.SetToolTip(tt)
    408
    409  #===========================================================
    410  if __name__ == "__main__":
    411          app = wxPyWidgetTester(size = (400, 200))
    412          app.SetWidget(cMainTopPanel, -1)
    413          app.SetWidget(cTopPanel, -1)
    414          app.MainLoop()
    415  #===========================================================
    416