1
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
49
60
61
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
80
81 self._TCTRL_allergies.Bind(wx.EVT_LEFT_DCLICK, self._on_allergies_dclicked)
82
83
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
91
99
100
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
108 if kwds['pk_identity'] != self.curr_pat.ID:
109 return True
110
111 if kwds['table'] == 'dem.identity':
112
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
138 wx.CallAfter(self.__on_post_patient_selection)
139
140
142 self.__update_age_label()
143 self.__update_allergies()
144 self.__update_tags()
145 self.__update_lab()
146 self.Layout()
147
148
150 wx.CallAfter(self._TCTRL_patient_selector.SetFocus)
151
152
153
154
156
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
191
192
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
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
250
251
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
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
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
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
293 now = gmDateTime.pydt_now_here()
294
295
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:
319 template = '%(sex)s %(dob)s (%(age)s)'
320
321
322
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
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
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
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
369 display = []
370 for allergy in emr.get_allergies():
371
372 if allergy['type'] == 'allergy':
373 display.append(allergy['descriptor'][:10].strip() + gmTools.u_ellipsis)
374
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