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
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
48
59
60
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
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
89
90 self._TCTRL_allergies.Bind(wx.EVT_LEFT_DCLICK, self._on_allergies_dclicked)
91
92
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
100
108
109
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
117 if kwds['pk_identity'] != self.curr_pat.ID:
118 return True
119
120 if kwds['table'] == 'dem.identity':
121
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
147 wx.CallAfter(self.__on_post_patient_selection)
148
149
151 self.__update_age_label()
152 self.__update_allergies()
153 self.__update_tags()
154 self.__update_lab()
155 self.Layout()
156
157
159 wx.CallAfter(self._TCTRL_patient_selector.SetFocus)
160
161
162
163
166
167
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
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
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
226
227
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
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
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
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
269 now = gmDateTime.pydt_now_here()
270
271
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:
295 template = '%(sex)s %(dob)s (%(age)s)'
296
297
298
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
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
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
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
345 display = []
346 for allergy in emr.get_allergies():
347
348 if allergy['type'] == 'allergy':
349 display.append(allergy['descriptor'][:10].strip() + gmTools.u_ellipsis)
350
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