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