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 rrs = self.curr_pat.emr.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_rr_quantity, max_no_of_results = 1) 177 if len(rrs) == 0: 178 tests2show.append(_('RR ?')) 179 else: 180 tests2show.append(rrs[0]['unified_val']) 181 182 hrs = self.curr_pat.emr.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_heart_rate_quantity, max_no_of_results = 1) 183 if len(hrs) > 0: 184 tests2show.append('%s %s' % (hrs[0]['abbrev_tt'], hrs[0]['unified_val'])) 185 186 bmi = self.curr_pat.emr.bmi 187 if bmi.numeric_value is not None: 188 tests2show.append(_('BMI %s') % bmi.numeric_value.quantize(decimal.Decimal('1.'))) 189 else: 190 weights = self.curr_pat.emr.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_weight, max_no_of_results = 1) 191 if len(weights) == 0: 192 tests2show.append(_('BMI ?')) 193 else: 194 tests2show.append('%s%s' % (weights[0]['unified_val'], weights[0]['val_unit'])) 195 196 gfr_or_crea = self.curr_pat.emr.best_gfr_or_crea 197 if gfr_or_crea is None: 198 tests2show.append(_('GFR ?')) 199 else: 200 try: 201 tests2show.append(_('GFR %s') % gfr_or_crea.numeric_value.quantize(decimal.Decimal('1.'))) 202 except AttributeError: 203 tests2show.append('%s %s' % (gfr_or_crea['abbrev_tt'], gfr_or_crea['unified_val'])) 204 205 edc = self.curr_pat.emr.EDC 206 if edc is not None: 207 if self.curr_pat.emr.EDC_is_fishy: 208 tests2show.append(_('?EDC %s') % gmDateTime.pydt_strftime(edc, '%Y-%b-%d', accuracy = gmDateTime.acc_days)) 209 else: 210 tests2show.append(_('EDC %s') % gmDateTime.pydt_strftime(edc, '%Y-%b-%d', accuracy = gmDateTime.acc_days)) 211 212 inrs = self.curr_pat.emr.get_most_recent_results_in_loinc_group(loincs = gmLOINC.LOINC_inr_quantity, max_no_of_results = 1) 213 if len(inrs) > 0: 214 tests2show.append('%s %s' % (inrs[0]['abbrev_tt'], inrs[0]['unified_val'])) 215 216 # include panel if configured, only show if exist 217 if self.__lab_panel is not None: 218 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): 219 tests2show.append('%s %s' % (result['abbrev_tt'], result['unified_val'])) 220 221 self._LBL_lab.SetLabel('; '.join(tests2show))
222 223 #-------------------------------------------------------
224 - def __update_age_label(self):
225 226 # no patient 227 if not self.curr_pat.connected: 228 self._LBL_age.SetLabel(_('<Age>')) 229 self._LBL_age.SetToolTip(_('no patient selected')) 230 return 231 232 # gender is always known 233 tt = _('Gender: %s (%s) - %s\n') % ( 234 self.curr_pat.gender_symbol, 235 gmTools.coalesce(self.curr_pat['gender'], '?'), 236 self.curr_pat.gender_string 237 ) 238 239 # dob is not known 240 if self.curr_pat['dob'] is None: 241 age = '%s %s' % ( 242 self.curr_pat.gender_symbol, 243 self.curr_pat.get_formatted_dob() 244 ) 245 self._LBL_age.SetLabel(age) 246 self._LBL_age.SetToolTip(tt) 247 return 248 249 tt += _('Born: %s\n') % self.curr_pat.get_formatted_dob(format = '%d %b %Y') 250 251 # patient is dead 252 if self.curr_pat['deceased'] is not None: 253 tt += _('Died: %s\n') % gmDateTime.pydt_strftime(self.curr_pat['deceased'], '%d %b %Y') 254 tt += _('At age: %s\n') % self.curr_pat['medical_age'] 255 age = '%s %s - %s (%s)' % ( 256 self.curr_pat.gender_symbol, 257 self.curr_pat.get_formatted_dob(format = '%d %b %Y'), 258 gmDateTime.pydt_strftime(self.curr_pat['deceased'], '%d %b %Y'), 259 self.curr_pat['medical_age'] 260 ) 261 if self.curr_pat['dob_is_estimated']: 262 tt += _(' (date of birth and age are estimated)\n') 263 self._LBL_age.SetLabel(age) 264 self._LBL_age.SetToolTip(tt) 265 return 266 267 # patient alive 268 now = gmDateTime.pydt_now_here() 269 270 # patient birthday ? 271 if self.curr_pat.get_formatted_dob(format = '%m-%d', honor_estimation = False) == now.strftime('%m-%d'): 272 template = _('%(sex)s %(dob)s (%(age)s today !)') 273 tt += _("\nToday is the patient's birthday !\n\n") 274 tt += _('Age: %s\n') % self.curr_pat['medical_age'] 275 else: 276 tt += _('Age: %s, birthday:\n') % self.curr_pat['medical_age'] 277 if self.curr_pat.current_birthday_passed is True: 278 template = '%(sex)s %(dob)s%(l_arr)s (%(age)s)' 279 tt += ' ' + _('last: %s ago (this year)') % gmDateTime.format_apparent_age_medically ( 280 age = gmDateTime.calculate_apparent_age(start = self.curr_pat.birthday_this_year, end = now) 281 ) + '\n' 282 tt += ' ' + _('next: in %s (next year)') % gmDateTime.format_apparent_age_medically ( 283 age = gmDateTime.calculate_apparent_age(start = now, end = self.curr_pat.birthday_next_year) 284 ) + '\n' 285 elif self.curr_pat.current_birthday_passed is False: 286 template = '%(sex)s %(r_arr)s%(dob)s (%(age)s)' 287 tt += ' ' + _('next: in %s (this year)') % gmDateTime.format_apparent_age_medically ( 288 age = gmDateTime.calculate_apparent_age(start = now, end = self.curr_pat.birthday_this_year) 289 ) + '\n' 290 tt += ' ' + _('last: %s ago (last year)') % gmDateTime.format_apparent_age_medically ( 291 age = gmDateTime.calculate_apparent_age(start = self.curr_pat.birthday_last_year, end = now) 292 ) + '\n' 293 else: # None, unknown 294 template = '%(sex)s %(dob)s (%(age)s)' 295 296 # FIXME: if the age is below, say, 2 hours we should fire 297 # a timer here that updates the age in increments of 1 minute ... :-) 298 age = template % { 299 'sex': self.curr_pat.gender_symbol, 300 'dob': self.curr_pat.get_formatted_dob(format = '%d %b %Y'), 301 'age': self.curr_pat['medical_age'], 302 'r_arr': gmTools.u_arrow2right, 303 'l_arr': gmTools.u_left_arrow 304 } 305 306 # Easter Egg ;-) 307 if self.curr_pat['lastnames'] == 'Leibner': 308 if self.curr_pat['firstnames'] == 'Steffi': 309 if self.curr_pat['preferred'] == 'Wildfang': 310 age = '%s %s' % (gmTools.u_black_heart, age) 311 312 if self.curr_pat['dob_is_estimated']: 313 tt += _(' (date of birth and age are estimated)\n') 314 315 self._LBL_age.SetLabel(age) 316 self._LBL_age.SetToolTip(tt)
317 318 #-------------------------------------------------------
319 - def __update_allergies(self, **kwargs):
320 321 if not self.curr_pat.connected: 322 self._LBL_allergies.SetForegroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT)) 323 self._TCTRL_allergies.SetForegroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT)) 324 self._TCTRL_allergies.SetValue('') 325 self._TCTRL_allergies.SetToolTip('') 326 return 327 328 show_red = True 329 330 emr = self.curr_pat.emr 331 state = emr.allergy_state 332 333 # state in tooltip 334 if state['last_confirmed'] is None: 335 confirmed = _('never') 336 else: 337 confirmed = gmDateTime.pydt_strftime(state['last_confirmed'], '%Y %b %d') 338 tt = (state.state_string + (90 * ' '))[:90] + '\n' 339 tt += _('last confirmed %s\n') % confirmed 340 tt += gmTools.coalesce(state['comment'], '', _('Comment (%s): %%s') % state['modified_by']) 341 tt += '\n' 342 343 # allergies 344 display = [] 345 for allergy in emr.get_allergies(): 346 # in field: "true" allergies only, not intolerances 347 if allergy['type'] == 'allergy': 348 display.append(allergy['descriptor'][:10].strip() + gmTools.u_ellipsis) 349 # in tooltip 350 if allergy['definite']: 351 certainty = _('definite') 352 else: 353 certainty = _('suspected') 354 reaction = gmTools.coalesce(allergy['reaction'], _('reaction not recorded')) 355 if len(reaction) > 50: 356 reaction = reaction[:50] + gmTools.u_ellipsis 357 tt += '%s (%s, %s): %s\n' % ( 358 allergy['descriptor'], 359 allergy['l10n_type'], 360 certainty, 361 reaction 362 ) 363 364 if len(display) == 0: 365 display = state.state_symbol 366 if display == gmTools.u_diameter: 367 show_red = False 368 else: 369 display = ','.join(display) 370 371 if state['last_confirmed'] is not None: 372 display += gmDateTime.pydt_strftime(state['last_confirmed'], ' (%Y %b)') 373 374 if show_red: 375 self._LBL_allergies.SetForegroundColour('red') 376 self._TCTRL_allergies.SetForegroundColour('red') 377 else: 378 self._LBL_allergies.SetForegroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT)) 379 self._TCTRL_allergies.SetForegroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT)) 380 381 self._TCTRL_allergies.SetValue(display) 382 self._TCTRL_allergies.SetToolTip(tt)
383 384 #=========================================================== 385 if __name__ == "__main__": 386 app = wxPyWidgetTester(size = (400, 200)) 387 app.SetWidget(cMainTopPanel, -1) 388 app.SetWidget(cTopPanel, -1) 389 app.MainLoop() 390 #=========================================================== 391