| Trees | Indices | Help |
|
|---|
|
|
1 """GNUmed measurement widgets.
2 """
3 #================================================================
4 # $Source: /cvsroot/gnumed/gnumed/gnumed/client/wxpython/gmMeasurementWidgets.py,v $
5 # $Id: gmMeasurementWidgets.py,v 1.66 2010/02/02 13:55:33 ncq Exp $
6 __version__ = "$Revision: 1.66 $"
7 __author__ = "Karsten Hilbert <Karsten.Hilbert@gmx.net>"
8 __license__ = "GPL"
9
10
11 import sys, logging, datetime as pyDT, decimal, os
12
13
14 import wx, wx.grid, wx.lib.hyperlink
15
16
17 if __name__ == '__main__':
18 sys.path.insert(0, '../../')
19 from Gnumed.business import gmPerson, gmPathLab, gmSurgery, gmLOINC
20 from Gnumed.pycommon import gmTools, gmDispatcher, gmMatchProvider, gmDateTime, gmI18N, gmCfg, gmShellAPI
21 from Gnumed.wxpython import gmRegetMixin, gmPhraseWheel, gmEditArea, gmGuiHelpers, gmListWidgets, gmAuthWidgets, gmPatSearchWidgets
22 from Gnumed.wxGladeWidgets import wxgMeasurementsPnl, wxgMeasurementsReviewDlg
23 from Gnumed.wxGladeWidgets import wxgMeasurementEditAreaPnl
24
25
26 _log = logging.getLogger('gm.ui')
27 _log.info(__version__)
28 #================================================================
29 # LOINC related widgets
30 #================================================================
32
33 wx.BeginBusyCursor()
34
35 gmDispatcher.send(signal = 'statustext', msg = _('Updating LOINC data can take quite a while...'), beep = True)
36
37 # download
38 downloaded = gmShellAPI.run_command_in_shell(command = 'gm-download_loinc', blocking = True)
39 if not downloaded:
40 wx.EndBusyCursor()
41 gmGuiHelpers.gm_show_warning (
42 aTitle = _('Downloading LOINC'),
43 aMessage = _(
44 'Running <gm-download_loinc> to retrieve\n'
45 'the latest LOINC data failed.\n'
46 )
47 )
48 return False
49
50 # split and import
51 data_fname, license_fname = gmLOINC.split_LOINCDBTXT(input_fname = '/tmp/LOINCDB.TXT')
52
53 wx.EndBusyCursor()
54
55 conn = gmAuthWidgets.get_dbowner_connection(procedure = _('importing LOINC reference data'))
56 if conn is None:
57 return False
58
59 wx.BeginBusyCursor()
60
61 if gmLOINC.loinc_import(data_fname = data_fname, license_fname = license_fname, conn = conn):
62 gmDispatcher.send(signal = 'statustext', msg = _('Successfully imported LOINC reference data.'))
63 try:
64 os.remove(data_fname)
65 os.remove(license_fname)
66 except OSError:
67 _log.error('unable to remove [%s] or [%s]', data_fname, license_fname)
68 else:
69 gmDispatcher.send(signal = 'statustext', msg = _('Importing LOINC reference data failed.'), beep = True)
70
71 wx.EndBusyCursor()
72 return True
73 #================================================================
74 # convenience functions
75 #================================================================
77 ea = cMeasurementEditAreaPnl(parent = parent, id = -1)
78 ea.data = measurement
79 ea.mode = gmTools.coalesce(measurement, 'new', 'edit')
80 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea)
81 dlg.SetTitle(gmTools.coalesce(measurement, _('Adding new measurement'), _('Editing measurement')))
82 if dlg.ShowModal() == wx.ID_OK:
83 dlg.Destroy()
84 return True
85 dlg.Destroy()
86 return False
87 #================================================================
88 #from Gnumed.wxGladeWidgets import wxgPrimaryCareVitalsInputPnl
89
90 # Taillenumfang: Mitte zwischen unterster Rippe und
91 # hoechstem Teil des Beckenkamms
92 # Maenner: maessig: 94-102, deutlich: > 102 .. erhoeht
93 # Frauen: maessig: 80-88, deutlich: > 88 .. erhoeht
94
95 #================================================================
96 # display widgets
97 #================================================================
99 """A grid class for displaying measurment results.
100
101 - does NOT listen to the currently active patient
102 - thereby it can display any patient at any time
103 """
104 # FIXME: sort-by-battery
105 # FIXME: filter-by-battery
106 # FIXME: filter out empty
107 # FIXME: filter by tests of a selected date
108 # FIXME: dates DESC/ASC by cfg
109 # FIXME: mouse over column header: display date info
111
112 wx.grid.Grid.__init__(self, *args, **kwargs)
113
114 self.__patient = None
115 self.__cell_data = {}
116 self.__row_label_data = []
117
118 self.__prev_row = None
119 self.__prev_col = None
120 self.__prev_label_row = None
121 self.__date_format = str((_('lab_grid_date_format::%Y\n%b %d')).lstrip('lab_grid_date_format::'))
122
123 self.__init_ui()
124 self.__register_events()
125 #------------------------------------------------------------
126 # external API
127 #------------------------------------------------------------
129 if not self.IsSelection():
130 gmDispatcher.send(signal = u'statustext', msg = _('No results selected for deletion.'))
131 return True
132
133 selected_cells = self.get_selected_cells()
134 if len(selected_cells) > 20:
135 results = None
136 msg = _(
137 'There are %s results marked for deletion.\n'
138 '\n'
139 'Are you sure you want to delete these results ?'
140 ) % len(selected_cells)
141 else:
142 results = self.__cells_to_data(cells = selected_cells, exclude_multi_cells = False)
143 txt = u'\n'.join([ u'%s %s (%s): %s %s%s' % (
144 r['clin_when'].strftime('%x %H:%M').decode(gmI18N.get_encoding()),
145 r['unified_abbrev'],
146 r['unified_name'],
147 r['unified_val'],
148 r['val_unit'],
149 gmTools.coalesce(r['abnormality_indicator'], u'', u' (%s)')
150 ) for r in results
151 ])
152 msg = _(
153 'The following results are marked for deletion:\n'
154 '\n'
155 '%s\n'
156 '\n'
157 'Are you sure you want to delete these results ?'
158 ) % txt
159
160 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
161 self,
162 -1,
163 caption = _('Deleting test results'),
164 question = msg,
165 button_defs = [
166 {'label': _('Delete'), 'tooltip': _('Yes, delete all the results.'), 'default': False},
167 {'label': _('Cancel'), 'tooltip': _('No, do NOT delete any results.'), 'default': True}
168 ]
169 )
170 decision = dlg.ShowModal()
171
172 if decision == wx.ID_YES:
173 if results is None:
174 results = self.__cells_to_data(cells = selected_cells, exclude_multi_cells = False)
175 for result in results:
176 gmPathLab.delete_test_result(result)
177 #------------------------------------------------------------
179 if not self.IsSelection():
180 gmDispatcher.send(signal = u'statustext', msg = _('Cannot sign results. No results selected.'))
181 return True
182
183 selected_cells = self.get_selected_cells()
184 if len(selected_cells) > 10:
185 test_count = len(selected_cells)
186 tests = None
187 else:
188 test_count = None
189 tests = self.__cells_to_data(cells = selected_cells, exclude_multi_cells = False)
190
191 dlg = cMeasurementsReviewDlg (
192 self,
193 -1,
194 tests = tests,
195 test_count = test_count
196 )
197 decision = dlg.ShowModal()
198
199 if decision == wx.ID_APPLY:
200 wx.BeginBusyCursor()
201
202 if dlg._RBTN_confirm_abnormal.GetValue():
203 abnormal = None
204 elif dlg._RBTN_results_normal.GetValue():
205 abnormal = False
206 else:
207 abnormal = True
208
209 if dlg._RBTN_confirm_relevance.GetValue():
210 relevant = None
211 elif dlg._RBTN_results_not_relevant.GetValue():
212 relevant = False
213 else:
214 relevant = True
215
216 if tests is None:
217 tests = self.__cells_to_data(cells = selected_cells, exclude_multi_cells = False)
218
219 comment = None
220 if len(tests) == 1:
221 comment = dlg._TCTRL_comment.GetValue()
222
223 for test in tests:
224 test.set_review (
225 technically_abnormal = abnormal,
226 clinically_relevant = relevant,
227 comment = comment,
228 make_me_responsible = dlg._CHBOX_responsible.IsChecked()
229 )
230
231 wx.EndBusyCursor()
232
233 dlg.Destroy()
234 #------------------------------------------------------------
236
237 sel_block_top_left = self.GetSelectionBlockTopLeft()
238 sel_block_bottom_right = self.GetSelectionBlockBottomRight()
239 sel_cols = self.GetSelectedCols()
240 sel_rows = self.GetSelectedRows()
241
242 selected_cells = []
243
244 # individually selected cells (ctrl-click)
245 selected_cells += self.GetSelectedCells()
246
247 # selected rows
248 selected_cells += list (
249 (row, col)
250 for row in sel_rows
251 for col in xrange(self.GetNumberCols())
252 )
253
254 # selected columns
255 selected_cells += list (
256 (row, col)
257 for row in xrange(self.GetNumberRows())
258 for col in sel_cols
259 )
260
261 # selection blocks
262 for top_left, bottom_right in zip(self.GetSelectionBlockTopLeft(), self.GetSelectionBlockBottomRight()):
263 selected_cells += [
264 (row, col)
265 for row in xrange(top_left[0], bottom_right[0] + 1)
266 for col in xrange(top_left[1], bottom_right[1] + 1)
267 ]
268
269 return set(selected_cells)
270 #------------------------------------------------------------
271 - def select_cells(self, unsigned_only=False, accountables_only=False, keep_preselections=False):
272 """Select a range of cells according to criteria.
273
274 unsigned_only: include only those which are not signed at all yet
275 accountable_only: include only those for which the current user is responsible
276 keep_preselections: broaden (rather than replace) the range of selected cells
277
278 Combinations are powerful !
279 """
280 wx.BeginBusyCursor()
281 self.BeginBatch()
282
283 if not keep_preselections:
284 self.ClearSelection()
285
286 for col_idx in self.__cell_data.keys():
287 for row_idx in self.__cell_data[col_idx].keys():
288 # loop over results in cell and only include
289 # this multi-value cells that are not ambigous
290 do_not_include = False
291 for result in self.__cell_data[col_idx][row_idx]:
292 if unsigned_only:
293 if result['reviewed']:
294 do_not_include = True
295 break
296 if accountables_only:
297 if not result['you_are_responsible']:
298 do_not_include = True
299 break
300 if do_not_include:
301 continue
302
303 self.SelectBlock(row_idx, col_idx, row_idx, col_idx, addToSelected = True)
304
305 self.EndBatch()
306 wx.EndBusyCursor()
307 #------------------------------------------------------------
309
310 self.empty_grid()
311 if self.__patient is None:
312 return
313
314 emr = self.__patient.get_emr()
315
316 self.__row_label_data = emr.get_test_types_for_results()
317 test_type_labels = [ u'%s (%s)' % (test['unified_abbrev'], test['unified_name']) for test in self.__row_label_data ]
318 if len(test_type_labels) == 0:
319 return
320
321 test_date_labels = [ date[0].strftime(self.__date_format) for date in emr.get_dates_for_results() ]
322 results = emr.get_test_results_by_date()
323
324 self.BeginBatch()
325
326 # rows
327 self.AppendRows(numRows = len(test_type_labels))
328 for row_idx in range(len(test_type_labels)):
329 self.SetRowLabelValue(row_idx, test_type_labels[row_idx])
330
331 # columns
332 self.AppendCols(numCols = len(test_date_labels))
333 for date_idx in range(len(test_date_labels)):
334 self.SetColLabelValue(date_idx, test_date_labels[date_idx])
335
336 # cell values (list of test results)
337 for result in results:
338 row = test_type_labels.index(u'%s (%s)' % (result['unified_abbrev'], result['unified_name']))
339 col = test_date_labels.index(result['clin_when'].strftime(self.__date_format))
340
341 try:
342 self.__cell_data[col]
343 except KeyError:
344 self.__cell_data[col] = {}
345
346 # the tooltip always shows the youngest sub result details
347 if self.__cell_data[col].has_key(row):
348 self.__cell_data[col][row].append(result)
349 self.__cell_data[col][row].sort(key = lambda x: x['clin_when'], reverse = True)
350 else:
351 self.__cell_data[col][row] = [result]
352
353 # rebuild cell display string
354 vals2display = []
355 for sub_result in self.__cell_data[col][row]:
356
357 # is the sub_result technically abnormal ?
358 ind = gmTools.coalesce(sub_result['abnormality_indicator'], u'').strip()
359 if ind != u'':
360 lab_abnormality_indicator = u' (%s)' % ind[:3]
361 else:
362 lab_abnormality_indicator = u''
363 # - if noone reviewed - use what the lab thinks
364 if sub_result['is_technically_abnormal'] is None:
365 abnormality_indicator = lab_abnormality_indicator
366 # - if someone reviewed and decreed normality - use that
367 elif sub_result['is_technically_abnormal'] is False:
368 abnormality_indicator = u''
369 # - if someone reviewed and decreed abnormality ...
370 else:
371 # ... invent indicator if the lab did't use one
372 if lab_abnormality_indicator == u'':
373 # FIXME: calculate from min/max/range
374 abnormality_indicator = u' (%s)' % gmTools.u_plus_minus
375 # ... else use indicator the lab used
376 else:
377 abnormality_indicator = lab_abnormality_indicator
378
379 # is the sub_result relevant clinically ?
380 # FIXME: take into account primary_GP once we support that
381 sub_result_relevant = sub_result['is_clinically_relevant']
382 if sub_result_relevant is None:
383 # FIXME: calculate from clinical range
384 sub_result_relevant = False
385
386 missing_review = False
387 # warn on missing review if
388 # a) no review at all exists or
389 if not sub_result['reviewed']:
390 missing_review = True
391 # b) there is a review but
392 else:
393 # current user is reviewer and hasn't reviewed
394 if sub_result['you_are_responsible'] and not sub_result['review_by_you']:
395 missing_review = True
396
397 # can we display the full sub_result length ?
398 if len(sub_result['unified_val']) > 8:
399 tmp = u'%.7s%s' % (sub_result['unified_val'][:7], gmTools.u_ellipsis)
400 else:
401 tmp = u'%.8s' % sub_result['unified_val'][:8]
402
403 # abnormal ?
404 tmp = u'%s%.6s' % (tmp, abnormality_indicator)
405
406 # is there a comment ?
407 has_sub_result_comment = gmTools.coalesce (
408 gmTools.coalesce(sub_result['note_test_org'], sub_result['comment']),
409 u''
410 ).strip() != u''
411 if has_sub_result_comment:
412 tmp = u'%s %s' % (tmp, gmTools.u_ellipsis)
413
414 # lacking a review ?
415 if missing_review:
416 tmp = u'%s %s' % (tmp, gmTools.u_writing_hand)
417
418 # part of a multi-result cell ?
419 if len(self.__cell_data[col][row]) > 1:
420 tmp = u'%s %s' % (sub_result['clin_when'].strftime('%H:%M'), tmp)
421
422 vals2display.append(tmp)
423
424 self.SetCellValue(row, col, u'\n'.join(vals2display))
425 self.SetCellAlignment(row, col, horiz = wx.ALIGN_RIGHT, vert = wx.ALIGN_CENTRE)
426 # font = self.GetCellFont(row, col)
427 # if not font.IsFixedWidth():
428 # font.SetFamily(family = wx.FONTFAMILY_MODERN)
429 # FIXME: what about partial sub results being relevant ??
430 if sub_result_relevant:
431 font = self.GetCellFont(row, col)
432 self.SetCellTextColour(row, col, 'firebrick')
433 font.SetWeight(wx.FONTWEIGHT_BOLD)
434 self.SetCellFont(row, col, font)
435 # self.SetCellFont(row, col, font)
436
437 self.AutoSize()
438 self.EndBatch()
439 return
440 #------------------------------------------------------------
442 self.BeginBatch()
443 self.ClearGrid()
444 # Windows cannot do nothing, it rather decides to assert()
445 # on thinking it is supposed to do nothing
446 if self.GetNumberRows() > 0:
447 self.DeleteRows(pos = 0, numRows = self.GetNumberRows())
448 if self.GetNumberCols() > 0:
449 self.DeleteCols(pos = 0, numCols = self.GetNumberCols())
450 self.EndBatch()
451 self.__cell_data = {}
452 self.__row_label_data = []
453 #------------------------------------------------------------
455 # display test info (unified, which tests are grouped, which panels they belong to
456 # include details about test types included,
457 # most recent value in this row, etc
458 # test_details, td_idx = emr.get_test_types_details()
459
460 tt = self.__row_label_data[row]
461 tip = u''
462 tip += _('Details about %s (%s)%s\n') % (tt['unified_name'], tt['unified_abbrev'], gmTools.coalesce(tt['unified_loinc'], u'', u' [%s]'))
463 tip += u'\n'
464 tip += _('Meta type:\n')
465 tip += _(' Name: %s (%s)%s #%s\n') % (tt['name_meta'], tt['abbrev_meta'], gmTools.coalesce(tt['loinc_meta'], u'', u' [%s]'), tt['pk_meta_test_type'])
466 tip += gmTools.coalesce(tt['conversion_unit'], u'', _(' Conversion unit: %s\n'))
467 tip += gmTools.coalesce(tt['comment_meta'], u'', _(' Comment: %s\n'))
468 tip += u'\n'
469 tip += _('Test type:\n')
470 tip += _(' Name: %s (%s)%s #%s\n') % (tt['name_tt'], tt['abbrev_tt'], gmTools.coalesce(tt['loinc_tt'], u'', u' [%s]'), tt['pk_test_type'])
471 tip += gmTools.coalesce(tt['comment_tt'], u'', _(' Comment: %s\n'))
472 tip += gmTools.coalesce(tt['code_tt'], u'', _(' Code: %s\n'))
473 tip += gmTools.coalesce(tt['coding_system_tt'], u'', _(' Code: %s\n'))
474 result = tt.get_most_recent_result(pk_patient = self.__patient.ID)
475 if result is not None:
476 tip += u'\n'
477 tip += _('Most recent result:\n')
478 tip += _(' %s: %s%s%s') % (
479 result['clin_when'].strftime('%Y-%m-%d'),
480 result['unified_val'],
481 gmTools.coalesce(result['val_unit'], u'', u' %s'),
482 gmTools.coalesce(result['abnormality_indicator'], u'', u' (%s)')
483 )
484
485 return tip
486 #------------------------------------------------------------
488 # FIXME: add panel/battery, request details
489
490 try:
491 d = self.__cell_data[col][row]
492 except KeyError:
493 # FIXME: maybe display the most recent or when the most recent was ?
494 d = None
495
496 if d is None:
497 return u''
498
499 is_multi_cell = False
500 if len(d) > 1:
501 is_multi_cell = True
502
503 d = d[0]
504
505 has_normal_min_or_max = (d['val_normal_min'] is not None) or (d['val_normal_max'] is not None)
506 if has_normal_min_or_max:
507 normal_min_max = u'%s - %s' % (
508 gmTools.coalesce(d['val_normal_min'], u'?'),
509 gmTools.coalesce(d['val_normal_max'], u'?')
510 )
511 else:
512 normal_min_max = u''
513
514 has_clinical_min_or_max = (d['val_target_min'] is not None) or (d['val_target_max'] is not None)
515 if has_clinical_min_or_max:
516 clinical_min_max = u'%s - %s' % (
517 gmTools.coalesce(d['val_target_min'], u'?'),
518 gmTools.coalesce(d['val_target_max'], u'?')
519 )
520 else:
521 clinical_min_max = u''
522
523 # header
524 if is_multi_cell:
525 tt = _(u'Measurement details of most recent (topmost) result: \n')
526 else:
527 tt = _(u'Measurement details: \n')
528
529 # basics
530 tt += u' ' + _(u'Date: %s\n') % d['clin_when'].strftime('%c').decode(gmI18N.get_encoding())
531 tt += u' ' + _(u'Type: "%(name)s" (%(code)s) [#%(pk_type)s]\n') % ({
532 'name': d['name_tt'],
533 'code': d['code_tt'],
534 'pk_type': d['pk_test_type']
535 })
536 tt += u' ' + _(u'Result: %(val)s%(unit)s%(ind)s [#%(pk_result)s]\n') % ({
537 'val': d['unified_val'],
538 'unit': gmTools.coalesce(d['val_unit'], u'', u' %s'),
539 'ind': gmTools.coalesce(d['abnormality_indicator'], u'', u' (%s)'),
540 'pk_result': d['pk_test_result']
541 })
542 tmp = (u'%s%s' % (
543 gmTools.coalesce(d['name_test_org'], u''),
544 gmTools.coalesce(d['contact_test_org'], u'', u' (%s)'),
545 )).strip()
546 if tmp != u'':
547 tt += u' ' + _(u'Source: %s\n') % tmp
548 tt += u'\n'
549
550 # clinical evaluation
551 norm_eval = None
552 if d['val_num'] is not None:
553 # 1) normal range
554 # lowered ?
555 if (d['val_normal_min'] is not None) and (d['val_num'] < d['val_normal_min']):
556 try:
557 percent = (d['val_num'] * 100) / d['val_normal_min']
558 except ZeroDivisionError:
559 percent = None
560 if percent is not None:
561 if percent < 6:
562 norm_eval = _(u'%.1f %% of the normal lower limit') % percent
563 else:
564 norm_eval = _(u'%.0f %% of the normal lower limit') % percent
565 # raised ?
566 if (d['val_normal_max'] is not None) and (d['val_num'] > d['val_normal_max']):
567 try:
568 x_times = d['val_num'] / d['val_normal_max']
569 except ZeroDivisionError:
570 x_times = None
571 if x_times is not None:
572 if x_times < 10:
573 norm_eval = _(u'%.1f times the normal upper limit') % x_times
574 else:
575 norm_eval = _(u'%.0f times the normal upper limit') % x_times
576 if norm_eval is not None:
577 tt += u' (%s)\n' % norm_eval
578 # #-------------------------------------
579 # # this idea was shot down on the list
580 # #-------------------------------------
581 # # bandwidth of deviation
582 # if None not in [d['val_normal_min'], d['val_normal_max']]:
583 # normal_width = d['val_normal_max'] - d['val_normal_min']
584 # deviation_from_normal_range = None
585 # # below ?
586 # if d['val_num'] < d['val_normal_min']:
587 # deviation_from_normal_range = d['val_normal_min'] - d['val_num']
588 # # above ?
589 # elif d['val_num'] > d['val_normal_max']:
590 # deviation_from_normal_range = d['val_num'] - d['val_normal_max']
591 # if deviation_from_normal_range is None:
592 # try:
593 # times_deviation = deviation_from_normal_range / normal_width
594 # except ZeroDivisionError:
595 # times_deviation = None
596 # if times_deviation is not None:
597 # if times_deviation < 10:
598 # tt += u' (%s)\n' % _(u'deviates by %.1f times of the normal range') % times_deviation
599 # else:
600 # tt += u' (%s)\n' % _(u'deviates by %.0f times of the normal range') % times_deviation
601 # #-------------------------------------
602
603 # 2) clinical target range
604 norm_eval = None
605 # lowered ?
606 if (d['val_target_min'] is not None) and (d['val_num'] < d['val_target_min']):
607 try:
608 percent = (d['val_num'] * 100) / d['val_target_min']
609 except ZeroDivisionError:
610 percent = None
611 if percent is not None:
612 if percent < 6:
613 norm_eval = _(u'%.1f %% of the target lower limit') % percent
614 else:
615 norm_eval = _(u'%.0f %% of the target lower limit') % percent
616 # raised ?
617 if (d['val_target_max'] is not None) and (d['val_num'] > d['val_target_max']):
618 try:
619 x_times = d['val_num'] / d['val_target_max']
620 except ZeroDivisionError:
621 x_times = None
622 if x_times is not None:
623 if x_times < 10:
624 norm_eval = _(u'%.1f times the target upper limit') % x_times
625 else:
626 norm_eval = _(u'%.0f times the target upper limit') % x_times
627 if norm_eval is not None:
628 tt += u' (%s)\n' % norm_eval
629 # #-------------------------------------
630 # # this idea was shot down on the list
631 # #-------------------------------------
632 # # bandwidth of deviation
633 # if None not in [d['val_target_min'], d['val_target_max']]:
634 # normal_width = d['val_target_max'] - d['val_target_min']
635 # deviation_from_target_range = None
636 # # below ?
637 # if d['val_num'] < d['val_target_min']:
638 # deviation_from_target_range = d['val_target_min'] - d['val_num']
639 # # above ?
640 # elif d['val_num'] > d['val_target_max']:
641 # deviation_from_target_range = d['val_num'] - d['val_target_max']
642 # if deviation_from_target_range is None:
643 # try:
644 # times_deviation = deviation_from_target_range / normal_width
645 # except ZeroDivisionError:
646 # times_deviation = None
647 # if times_deviation is not None:
648 # if times_deviation < 10:
649 # tt += u' (%s)\n' % _(u'deviates by %.1f times of the target range') % times_deviation
650 # else:
651 # tt += u' (%s)\n' % _(u'deviates by %.0f times of the target range') % times_deviation
652 # #-------------------------------------
653
654 # ranges
655 tt += u' ' + _(u'Standard normal range: %(norm_min_max)s%(norm_range)s \n') % ({
656 'norm_min_max': normal_min_max,
657 'norm_range': gmTools.coalesce (
658 d['val_normal_range'],
659 u'',
660 gmTools.bool2subst (
661 has_normal_min_or_max,
662 u' / %s',
663 u'%s'
664 )
665 )
666 })
667 if d['norm_ref_group'] is not None:
668 tt += u' ' + _(u'Reference group: %s\n') % d['norm_ref_group']
669 tt += u' ' + _(u'Clinical target range: %(clin_min_max)s%(clin_range)s \n') % ({
670 'clin_min_max': clinical_min_max,
671 'clin_range': gmTools.coalesce (
672 d['val_target_range'],
673 u'',
674 gmTools.bool2subst (
675 has_clinical_min_or_max,
676 u' / %s',
677 u'%s'
678 )
679 )
680 })
681
682 # metadata
683 if d['comment'] is not None:
684 tt += u' ' + _(u'Doc: %s\n') % _(u'\n Doc: ').join(d['comment'].split(u'\n'))
685 if d['note_test_org'] is not None:
686 tt += u' ' + _(u'Lab: %s\n') % _(u'\n Lab: ').join(d['note_test_org'].split(u'\n'))
687 tt += u' ' + _(u'Episode: %s\n') % d['episode']
688 if d['health_issue'] is not None:
689 tt += u' ' + _(u'Issue: %s\n') % d['health_issue']
690 if d['material'] is not None:
691 tt += u' ' + _(u'Material: %s\n') % d['material']
692 if d['material_detail'] is not None:
693 tt += u' ' + _(u'Details: %s\n') % d['material_detail']
694 tt += u'\n'
695
696 # review
697 if d['reviewed']:
698 review = d['last_reviewed'].strftime('%c').decode(gmI18N.get_encoding())
699 else:
700 review = _('not yet')
701 tt += _(u'Signed (%(sig_hand)s): %(reviewed)s\n') % ({
702 'sig_hand': gmTools.u_writing_hand,
703 'reviewed': review
704 })
705 tt += u' ' + _(u'Responsible clinician: %s\n') % gmTools.bool2subst(d['you_are_responsible'], _('you'), d['responsible_reviewer'])
706 if d['reviewed']:
707 tt += u' ' + _(u'Last reviewer: %(reviewer)s\n') % ({'reviewer': gmTools.bool2subst(d['review_by_you'], _('you'), gmTools.coalesce(d['last_reviewer'], u'?'))})
708 tt += u' ' + _(u' Technically abnormal: %(abnormal)s\n') % ({'abnormal': gmTools.bool2subst(d['is_technically_abnormal'], _('yes'), _('no'), u'?')})
709 tt += u' ' + _(u' Clinically relevant: %(relevant)s\n') % ({'relevant': gmTools.bool2subst(d['is_clinically_relevant'], _('yes'), _('no'), u'?')})
710 if d['review_comment'] is not None:
711 tt += u' ' + _(u' Comment: %s\n') % d['review_comment'].strip()
712 tt += u'\n'
713
714 # type
715 tt += _(u'Test type details:\n')
716 tt += u' ' + _(u'Grouped under "%(name_meta)s" (%(abbrev_meta)s) [#%(pk_u_type)s]\n') % ({
717 'name_meta': gmTools.coalesce(d['name_meta'], u''),
718 'abbrev_meta': gmTools.coalesce(d['abbrev_meta'], u''),
719 'pk_u_type': d['pk_meta_test_type']
720 })
721 if d['comment_tt'] is not None:
722 tt += u' ' + _(u'Type comment: %s\n') % _(u'\n Type comment:').join(d['comment_tt'].split(u'\n'))
723 if d['comment_meta'] is not None:
724 tt += u' ' + _(u'Group comment: %s\n') % _(u'\n Group comment: ').join(d['comment_meta'].split(u'\n'))
725 tt += u'\n'
726
727 tt += _(u'Revisions: %(row_ver)s, last %(mod_when)s by %(mod_by)s.') % ({
728 'row_ver': d['row_version'],
729 'mod_when': d['modified_when'].strftime('%c').decode(gmI18N.get_encoding()),
730 'mod_by': d['modified_by']
731 })
732
733 return tt
734 #------------------------------------------------------------
735 # internal helpers
736 #------------------------------------------------------------
738 self.CreateGrid(0, 1)
739 self.EnableEditing(0)
740 self.EnableDragGridSize(1)
741
742 # setting this screws up the labels: they are cut off and displaced
743 #self.SetColLabelAlignment(wx.ALIGN_CENTER, wx.ALIGN_BOTTOM)
744
745 #self.SetRowLabelSize(wx.GRID_AUTOSIZE) # starting with 2.8.8
746 self.SetRowLabelSize(150)
747 self.SetRowLabelAlignment(horiz = wx.ALIGN_LEFT, vert = wx.ALIGN_CENTRE)
748
749 # add link to left upper corner
750 dbcfg = gmCfg.cCfgSQL()
751 url = dbcfg.get2 (
752 option = u'external.urls.measurements_encyclopedia',
753 workplace = gmSurgery.gmCurrentPractice().active_workplace,
754 bias = 'user',
755 default = u'http://www.laborlexikon.de'
756 )
757
758 self.__WIN_corner = self.GetGridCornerLabelWindow() # a wx.Window instance
759
760 LNK_lab = wx.lib.hyperlink.HyperLinkCtrl (
761 self.__WIN_corner,
762 -1,
763 label = _('Reference'),
764 style = wx.HL_DEFAULT_STYLE # wx.TE_READONLY|wx.TE_CENTRE| wx.NO_BORDER |
765 )
766 LNK_lab.SetURL(url)
767 LNK_lab.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_BACKGROUND))
768 LNK_lab.SetToolTipString(_(
769 'Navigate to an encyclopedia of measurements\n'
770 'and test methods on the web.\n'
771 '\n'
772 ' <%s>'
773 ) % url)
774
775 SZR_inner = wx.BoxSizer(wx.HORIZONTAL)
776 SZR_inner.Add((20, 20), 1, wx.EXPAND, 0) # spacer
777 SZR_inner.Add(LNK_lab, 0, wx.ALIGN_CENTER_VERTICAL, 0) #wx.ALIGN_CENTER wx.EXPAND
778 SZR_inner.Add((20, 20), 1, wx.EXPAND, 0) # spacer
779
780 SZR_corner = wx.BoxSizer(wx.VERTICAL)
781 SZR_corner.Add((20, 20), 1, wx.EXPAND, 0) # spacer
782 SZR_corner.AddWindow(SZR_inner, 0, wx.EXPAND) # inner sizer with centered hyperlink
783 SZR_corner.Add((20, 20), 1, wx.EXPAND, 0) # spacer
784
785 self.__WIN_corner.SetSizer(SZR_corner)
786 SZR_corner.Fit(self.__WIN_corner)
787 #------------------------------------------------------------
790 #------------------------------------------------------------
792 """List of <cells> must be in row / col order."""
793 data = []
794 for row, col in cells:
795 try:
796 # cell data is stored col / row
797 data_list = self.__cell_data[col][row]
798 except KeyError:
799 continue
800
801 if len(data_list) == 1:
802 data.append(data_list[0])
803 continue
804
805 if exclude_multi_cells:
806 gmDispatcher.send(signal = u'statustext', msg = _('Excluding multi-result field from further processing.'))
807 continue
808
809 data_to_include = self.__get_choices_from_multi_cell(cell_data = data_list)
810
811 if data_to_include is None:
812 continue
813
814 data.extend(data_to_include)
815
816 return data
817 #------------------------------------------------------------
819 data = gmListWidgets.get_choices_from_list (
820 parent = self,
821 msg = _(
822 'Your selection includes a field with multiple results.\n'
823 '\n'
824 'Please select the individual results you want to work on:'
825 ),
826 caption = _('Selecting test results'),
827 choices = [ [d['clin_when'], d['unified_abbrev'], d['unified_name'], d['unified_val']] for d in cell_data ],
828 columns = [_('Date / Time'), _('Code'), _('Test'), _('Result')],
829 data = cell_data,
830 single_selection = single_selection
831 )
832 return data
833 #------------------------------------------------------------
834 # event handling
835 #------------------------------------------------------------
837 # dynamic tooltips: GridWindow, GridRowLabelWindow, GridColLabelWindow, GridCornerLabelWindow
838 self.GetGridWindow().Bind(wx.EVT_MOTION, self.__on_mouse_over_cells)
839 self.GetGridRowLabelWindow().Bind(wx.EVT_MOTION, self.__on_mouse_over_row_labels)
840 #self.GetGridColLabelWindow().Bind(wx.EVT_MOTION, self.__on_mouse_over_col_labels)
841
842 # sizing left upper corner window
843 self.Bind(wx.EVT_SIZE, self.__resize_corner_window)
844
845 # editing cells
846 self.Bind(wx.grid.EVT_GRID_CELL_LEFT_DCLICK, self.__on_cell_left_dclicked)
847 #------------------------------------------------------------
849 col = evt.GetCol()
850 row = evt.GetRow()
851
852 # empty cell, perhaps ?
853 try:
854 self.__cell_data[col][row]
855 except KeyError:
856 # FIXME: invoke editor for adding value for day of that column
857 # FIMXE: and test of that row
858 return
859
860 if len(self.__cell_data[col][row]) > 1:
861 data = self.__get_choices_from_multi_cell(cell_data = self.__cell_data[col][row], single_selection = True)
862 else:
863 data = self.__cell_data[col][row][0]
864
865 if data is None:
866 return
867
868 edit_measurement(parent = self, measurement = data)
869 #------------------------------------------------------------
870 # def OnMouseMotionRowLabel(self, evt):
871 # x, y = self.CalcUnscrolledPosition(evt.GetPosition())
872 # row = self.YToRow(y)
873 # label = self.table().GetRowHelpValue(row)
874 # self.GetGridRowLabelWindow().SetToolTipString(label or "")
875 # evt.Skip()
877
878 # Use CalcUnscrolledPosition() to get the mouse position within the
879 # entire grid including what's offscreen
880 x, y = self.CalcUnscrolledPosition(evt.GetX(), evt.GetY())
881
882 row = self.YToRow(y)
883
884 if self.__prev_label_row == row:
885 return
886
887 self.__prev_label_row == row
888
889 evt.GetEventObject().SetToolTipString(self.get_row_tooltip(row = row))
890 #------------------------------------------------------------
891 # def OnMouseMotionColLabel(self, evt):
892 # x, y = self.CalcUnscrolledPosition(evt.GetPosition())
893 # col = self.XToCol(x)
894 # label = self.table().GetColHelpValue(col)
895 # self.GetGridColLabelWindow().SetToolTipString(label or "")
896 # evt.Skip()
897 #------------------------------------------------------------
899 """Calculate where the mouse is and set the tooltip dynamically."""
900
901 # Use CalcUnscrolledPosition() to get the mouse position within the
902 # entire grid including what's offscreen
903 x, y = self.CalcUnscrolledPosition(evt.GetX(), evt.GetY())
904
905 # use this logic to prevent tooltips outside the actual cells
906 # apply to GetRowSize, too
907 # tot = 0
908 # for col in xrange(self.NumberCols):
909 # tot += self.GetColSize(col)
910 # if xpos <= tot:
911 # self.tool_tip.Tip = 'Tool tip for Column %s' % (
912 # self.GetColLabelValue(col))
913 # break
914 # else: # mouse is in label area beyond the right-most column
915 # self.tool_tip.Tip = ''
916
917 row, col = self.XYToCell(x, y)
918
919 if (row == self.__prev_row) and (col == self.__prev_col):
920 return
921
922 self.__prev_row = row
923 self.__prev_col = col
924
925 evt.GetEventObject().SetToolTipString(self.get_cell_tooltip(col=col, row=row))
926 #------------------------------------------------------------
927 # properties
928 #------------------------------------------------------------
932
933 patient = property(lambda x:x, _set_patient)
934 #================================================================
935 -class cMeasurementsPnl(wxgMeasurementsPnl.wxgMeasurementsPnl, gmRegetMixin.cRegetOnPaintMixin):
936
937 """Panel holding a grid with lab data. Used as notebook page."""
938
940
941 wxgMeasurementsPnl.wxgMeasurementsPnl.__init__(self, *args, **kwargs)
942 gmRegetMixin.cRegetOnPaintMixin.__init__(self)
943 self.__init_ui()
944 self.__register_interests()
945 #--------------------------------------------------------
946 # event handling
947 #--------------------------------------------------------
949 gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._on_pre_patient_selection)
950 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
951 gmDispatcher.connect(signal = u'test_result_mod_db', receiver = self._schedule_data_reget)
952 gmDispatcher.connect(signal = u'reviewed_test_results_mod_db', receiver = self._schedule_data_reget)
953 #--------------------------------------------------------
956 #--------------------------------------------------------
959 #--------------------------------------------------------
962 #--------------------------------------------------------
964 self.data_grid.patient = None
965 #--------------------------------------------------------
968 #--------------------------------------------------------
974 #--------------------------------------------------------
976 self.data_grid.sign_current_selection()
977 #--------------------------------------------------------
979 self.data_grid.delete_current_selection()
980 #--------------------------------------------------------
981 # internal API
982 #--------------------------------------------------------
984 self.__action_button_popup = wx.Menu(title = _('Act on selected results'))
985
986 menu_id = wx.NewId()
987 self.__action_button_popup.AppendItem(wx.MenuItem(self.__action_button_popup, menu_id, _('Review and &sign')))
988 wx.EVT_MENU(self.__action_button_popup, menu_id, self.__on_sign_current_selection)
989
990 menu_id = wx.NewId()
991 self.__action_button_popup.AppendItem(wx.MenuItem(self.__action_button_popup, menu_id, _('Export to &file')))
992 #wx.EVT_MENU(self.__action_button_popup, menu_id, self.data_grid.current_selection_to_file)
993 self.__action_button_popup.Enable(id = menu_id, enable = False)
994
995 menu_id = wx.NewId()
996 self.__action_button_popup.AppendItem(wx.MenuItem(self.__action_button_popup, menu_id, _('Export to &clipboard')))
997 #wx.EVT_MENU(self.__action_button_popup, menu_id, self.data_grid.current_selection_to_clipboard)
998 self.__action_button_popup.Enable(id = menu_id, enable = False)
999
1000 menu_id = wx.NewId()
1001 self.__action_button_popup.AppendItem(wx.MenuItem(self.__action_button_popup, menu_id, _('&Delete')))
1002 wx.EVT_MENU(self.__action_button_popup, menu_id, self.__on_delete_current_selection)
1003 #--------------------------------------------------------
1004 # reget mixin API
1005 #--------------------------------------------------------
1007 """Populate fields in pages with data from model."""
1008 pat = gmPerson.gmCurrentPatient()
1009 if pat.connected:
1010 self.data_grid.patient = pat
1011 else:
1012 self.data_grid.patient = None
1013 return True
1014 #================================================================
1015 # editing widgets
1016 #================================================================
1018
1020
1021 try:
1022 tests = kwargs['tests']
1023 del kwargs['tests']
1024 test_count = len(tests)
1025 try: del kwargs['test_count']
1026 except KeyError: pass
1027 except KeyError:
1028 tests = None
1029 test_count = kwargs['test_count']
1030 del kwargs['test_count']
1031
1032 wxgMeasurementsReviewDlg.wxgMeasurementsReviewDlg.__init__(self, *args, **kwargs)
1033
1034 if tests is None:
1035 msg = _('%s results selected. Too many to list individually.') % test_count
1036 else:
1037 msg = ' // '.join (
1038 [ u'%s: %s %s (%s)' % (
1039 t['unified_abbrev'],
1040 t['unified_val'],
1041 t['val_unit'],
1042 t['clin_when'].strftime('%x').decode(gmI18N.get_encoding())
1043 ) for t in tests
1044 ]
1045 )
1046
1047 self._LBL_tests.SetLabel(msg)
1048
1049 if test_count == 1:
1050 self._TCTRL_comment.Enable(True)
1051 self._TCTRL_comment.SetValue(gmTools.coalesce(tests[0]['review_comment'], u''))
1052 if tests[0]['you_are_responsible']:
1053 self._CHBOX_responsible.Enable(False)
1054
1055 self.Fit()
1056 #--------------------------------------------------------
1057 # event handling
1058 #--------------------------------------------------------
1064 #================================================================
1065 -class cMeasurementEditAreaPnl(wxgMeasurementEditAreaPnl.wxgMeasurementEditAreaPnl, gmEditArea.cGenericEditAreaMixin):
1066 """This edit area saves *new* measurements into the active patient only."""
1067
1069
1070 try:
1071 self.__default_date = kwargs['date']
1072 del kwargs['date']
1073 except KeyError:
1074 self.__default_date = None
1075
1076 wxgMeasurementEditAreaPnl.wxgMeasurementEditAreaPnl.__init__(self, *args, **kwargs)
1077 gmEditArea.cGenericEditAreaMixin.__init__(self)
1078
1079 self.__register_interests()
1080
1081 self.successful_save_msg = _('Successfully saved measurement.')
1082 #--------------------------------------------------------
1083 # generic edit area mixin API
1084 #--------------------------------------------------------
1086 self._PRW_test.SetText(u'', None, True)
1087 self._TCTRL_result.SetValue(u'')
1088 self._PRW_units.SetText(u'', None, True)
1089 self._PRW_abnormality_indicator.SetText(u'', None, True)
1090 if self.__default_date is None:
1091 self._DPRW_evaluated.SetData(data = pyDT.datetime.now(tz = gmDateTime.gmCurrentLocalTimezone))
1092 else:
1093 self._DPRW_evaluated.SetData(data = None)
1094 self._TCTRL_note_test_org.SetValue(u'')
1095 self._PRW_intended_reviewer.SetData()
1096 self._PRW_problem.SetData()
1097 self._TCTRL_narrative.SetValue(u'')
1098 self._CHBOX_review.SetValue(False)
1099 self._CHBOX_abnormal.SetValue(False)
1100 self._CHBOX_relevant.SetValue(False)
1101 self._CHBOX_abnormal.Enable(False)
1102 self._CHBOX_relevant.Enable(False)
1103 self._TCTRL_review_comment.SetValue(u'')
1104 self._TCTRL_normal_min.SetValue(u'')
1105 self._TCTRL_normal_max.SetValue(u'')
1106 self._TCTRL_normal_range.SetValue(u'')
1107 self._TCTRL_target_min.SetValue(u'')
1108 self._TCTRL_target_max.SetValue(u'')
1109 self._TCTRL_target_range.SetValue(u'')
1110 self._TCTRL_norm_ref_group.SetValue(u'')
1111
1112 self._PRW_test.SetFocus()
1113 #--------------------------------------------------------
1115 self._PRW_test.SetData(data = self.data['pk_test_type'])
1116 self._TCTRL_result.SetValue(self.data['unified_val'])
1117 self._PRW_units.SetText(self.data['val_unit'], self.data['val_unit'], True)
1118 self._PRW_abnormality_indicator.SetText (
1119 gmTools.coalesce(self.data['abnormality_indicator'], u''),
1120 gmTools.coalesce(self.data['abnormality_indicator'], u''),
1121 True
1122 )
1123 self._DPRW_evaluated.SetData(data = self.data['clin_when'])
1124 self._TCTRL_note_test_org.SetValue(gmTools.coalesce(self.data['note_test_org'], u''))
1125 self._PRW_intended_reviewer.SetData(self.data['pk_intended_reviewer'])
1126 self._PRW_problem.SetData(self.data['pk_episode'])
1127 self._TCTRL_narrative.SetValue(gmTools.coalesce(self.data['comment'], u''))
1128 self._CHBOX_review.SetValue(False)
1129 self._CHBOX_abnormal.SetValue(gmTools.coalesce(self.data['is_technically_abnormal'], False))
1130 self._CHBOX_relevant.SetValue(gmTools.coalesce(self.data['is_clinically_relevant'], False))
1131 self._CHBOX_abnormal.Enable(False)
1132 self._CHBOX_relevant.Enable(False)
1133 self._TCTRL_review_comment.SetValue(gmTools.coalesce(self.data['review_comment'], u''))
1134 self._TCTRL_normal_min.SetValue(unicode(gmTools.coalesce(self.data['val_normal_min'], u'')))
1135 self._TCTRL_normal_max.SetValue(unicode(gmTools.coalesce(self.data['val_normal_max'], u'')))
1136 self._TCTRL_normal_range.SetValue(gmTools.coalesce(self.data['val_normal_range'], u''))
1137 self._TCTRL_target_min.SetValue(unicode(gmTools.coalesce(self.data['val_target_min'], u'')))
1138 self._TCTRL_target_max.SetValue(unicode(gmTools.coalesce(self.data['val_target_max'], u'')))
1139 self._TCTRL_target_range.SetValue(gmTools.coalesce(self.data['val_target_range'], u''))
1140 self._TCTRL_norm_ref_group.SetValue(gmTools.coalesce(self.data['norm_ref_group'], u''))
1141
1142 self._TCTRL_result.SetFocus()
1143 #--------------------------------------------------------
1145 self._refresh_from_existing()
1146
1147 self._PRW_test.SetText(u'', None, True)
1148 self._TCTRL_result.SetValue(u'')
1149 self._PRW_units.SetText(u'', None, True)
1150 self._PRW_abnormality_indicator.SetText(u'', None, True)
1151 # self._DPRW_evaluated
1152 self._TCTRL_note_test_org.SetValue(u'')
1153 self._TCTRL_narrative.SetValue(u'')
1154 self._CHBOX_review.SetValue(False)
1155 self._CHBOX_abnormal.SetValue(False)
1156 self._CHBOX_relevant.SetValue(False)
1157 self._CHBOX_abnormal.Enable(False)
1158 self._CHBOX_relevant.Enable(False)
1159 self._TCTRL_review_comment.SetValue(u'')
1160 self._TCTRL_normal_min.SetValue(u'')
1161 self._TCTRL_normal_max.SetValue(u'')
1162 self._TCTRL_normal_range.SetValue(u'')
1163 self._TCTRL_target_min.SetValue(u'')
1164 self._TCTRL_target_max.SetValue(u'')
1165 self._TCTRL_target_range.SetValue(u'')
1166 self._TCTRL_norm_ref_group.SetValue(u'')
1167
1168 self._PRW_test.SetFocus()
1169 #--------------------------------------------------------
1171
1172 validity = True
1173
1174 if not self._DPRW_evaluated.is_valid_timestamp():
1175 self._DPRW_evaluated.display_as_valid(False)
1176 validity = False
1177 else:
1178 self._DPRW_evaluated.display_as_valid(True)
1179
1180 if self._TCTRL_result.GetValue().strip() == u'':
1181 self._TCTRL_result.SetBackgroundColour(gmPhraseWheel.color_prw_invalid)
1182 validity = False
1183 else:
1184 self._TCTRL_result.SetBackgroundColour(gmPhraseWheel.color_prw_valid)
1185
1186 if self._PRW_problem.GetValue().strip() == u'':
1187 self._PRW_problem.display_as_valid(False)
1188 validity = False
1189 else:
1190 self._PRW_problem.display_as_valid(True)
1191
1192 if self._PRW_test.GetValue().strip() == u'':
1193 self._PRW_test.display_as_valid(False)
1194 validity = False
1195 else:
1196 self._PRW_test.display_as_valid(True)
1197
1198 if self._PRW_intended_reviewer.GetData() is None:
1199 self._PRW_intended_reviewer.display_as_valid(False)
1200 validity = False
1201 else:
1202 self._PRW_intended_reviewer.display_as_valid(True)
1203
1204 if self._PRW_units.GetValue().strip() == u'':
1205 self._PRW_units.display_as_valid(False)
1206 validity = False
1207 else:
1208 self._PRW_units.display_as_valid(True)
1209
1210 ctrls = [self._TCTRL_normal_min, self._TCTRL_normal_max, self._TCTRL_target_min, self._TCTRL_target_max]
1211 for widget in ctrls:
1212 val = widget.GetValue().strip()
1213 if val == u'':
1214 continue
1215 try:
1216 decimal.Decimal(val.replace(',', u'.', 1))
1217 widget.SetBackgroundColour(gmPhraseWheel.color_prw_valid)
1218 except:
1219 widget.SetBackgroundColour(gmPhraseWheel.color_prw_invalid)
1220 validity = False
1221
1222 if validity is False:
1223 gmDispatcher.send(signal = 'statustext', msg = _('Cannot save result. Invalid or missing essential input.'))
1224
1225 return validity
1226 #--------------------------------------------------------
1228
1229 emr = gmPerson.gmCurrentPatient().get_emr()
1230
1231 try:
1232 v_num = decimal.Decimal(self._TCTRL_result.GetValue().strip().replace(',', '.', 1))
1233 v_al = None
1234 except:
1235 v_num = None
1236 v_al = self._TCTRL_result.GetValue().strip()
1237
1238 pk_type = self._PRW_test.GetData()
1239 if pk_type is None:
1240 tt = gmPathLab.create_measurement_type (
1241 lab = None,
1242 abbrev = self._PRW_test.GetValue().strip(),
1243 name = self._PRW_test.GetValue().strip(),
1244 unit = gmTools.none_if(self._PRW_units.GetValue().strip(), u'')
1245 )
1246 pk_type = tt['pk_test_type']
1247
1248 tr = emr.add_test_result (
1249 episode = self._PRW_problem.GetData(can_create=True, is_open=False),
1250 type = pk_type,
1251 intended_reviewer = self._PRW_intended_reviewer.GetData(),
1252 val_num = v_num,
1253 val_alpha = v_al,
1254 unit = self._PRW_units.GetValue()
1255 )
1256
1257 tr['clin_when'] = self._DPRW_evaluated.GetData().get_pydt()
1258
1259 ctrls = [
1260 ('abnormality_indicator', self._PRW_abnormality_indicator),
1261 ('note_test_org', self._TCTRL_note_test_org),
1262 ('comment', self._TCTRL_narrative),
1263 ('val_normal_min', self._TCTRL_normal_min),
1264 ('val_normal_max', self._TCTRL_normal_max),
1265 ('val_normal_range', self._TCTRL_normal_range),
1266 ('val_target_min', self._TCTRL_target_min),
1267 ('val_target_max', self._TCTRL_target_max),
1268 ('val_target_range', self._TCTRL_target_range),
1269 ('norm_ref_group', self._TCTRL_norm_ref_group)
1270 ]
1271 for field, widget in ctrls:
1272 val = widget.GetValue().strip()
1273 if val != u'':
1274 tr[field] = val
1275
1276 tr.save_payload()
1277
1278 if self._CHBOX_review.GetValue() is True:
1279 tr.set_review (
1280 technically_abnormal = self._CHBOX_abnormal.GetValue(),
1281 clinically_relevant = self._CHBOX_relevant.GetValue(),
1282 comment = gmTools.none_if(self._TCTRL_review_comment.GetValue().strip(), u''),
1283 make_me_responsible = False
1284 )
1285
1286 self.data = tr
1287
1288 return True
1289 #--------------------------------------------------------
1291
1292 success, result = gmTools.input2decimal(self._TCTRL_result.GetValue())
1293 if success:
1294 v_num = result
1295 v_al = None
1296 else:
1297 v_num = None
1298 v_al = self._TCTRL_result.GetValue().strip()
1299
1300 pk_type = self._PRW_test.GetData()
1301 if pk_type is None:
1302 tt = gmPathLab.create_measurement_type (
1303 lab = None,
1304 abbrev = self._PRW_test.GetValue().strip(),
1305 name = self._PRW_test.GetValue().strip(),
1306 unit = gmTools.none_if(self._PRW_units.GetValue().strip(), u'')
1307 )
1308 pk_type = tt['pk_test_type']
1309
1310 tr = self.data
1311
1312 tr['pk_episode'] = self._PRW_problem.GetData(can_create=True, is_open=False)
1313 tr['pk_test_type'] = pk_type
1314 tr['pk_intended_reviewer'] = self._PRW_intended_reviewer.GetData()
1315 tr['val_num'] = v_num
1316 tr['val_alpha'] = v_al
1317 tr['val_unit'] = self._PRW_units.GetValue().strip()
1318 tr['clin_when'] = self._DPRW_evaluated.GetData().get_pydt()
1319 tr['abnormality_indicator'] = self._PRW_abnormality_indicator.GetValue().strip()
1320
1321 ctrls = [
1322 ('note_test_org', self._TCTRL_note_test_org),
1323 ('comment', self._TCTRL_narrative),
1324 ('val_normal_min', self._TCTRL_normal_min),
1325 ('val_normal_max', self._TCTRL_normal_max),
1326 ('val_normal_range', self._TCTRL_normal_range),
1327 ('val_target_min', self._TCTRL_target_min),
1328 ('val_target_max', self._TCTRL_target_max),
1329 ('val_target_range', self._TCTRL_target_range),
1330 ('norm_ref_group', self._TCTRL_norm_ref_group)
1331 ]
1332 for field, widget in ctrls:
1333 val = widget.GetValue().strip()
1334 if val != u'':
1335 tr[field] = val
1336
1337 tr.save_payload()
1338
1339 if self._CHBOX_review.GetValue() is True:
1340 tr.set_review (
1341 technically_abnormal = self._CHBOX_abnormal.GetValue(),
1342 clinically_relevant = self._CHBOX_relevant.GetValue(),
1343 comment = gmTools.none_if(self._TCTRL_review_comment.GetValue().strip(), u''),
1344 make_me_responsible = False
1345 )
1346
1347 return True
1348 #--------------------------------------------------------
1349 # event handling
1350 #--------------------------------------------------------
1352 self._PRW_test.add_callback_on_lose_focus(self._on_leave_test_prw)
1353 self._PRW_abnormality_indicator.add_callback_on_lose_focus(self._on_leave_indicator_prw)
1354 #--------------------------------------------------------
1356 pk_type = self._PRW_test.GetData()
1357 # units context
1358 if pk_type is None:
1359 self._PRW_units.unset_context(context = u'pk_type')
1360 else:
1361 self._PRW_units.set_context(context = u'pk_type', val = pk_type)
1362 #--------------------------------------------------------
1364 # if the user hasn't explicitly enabled reviewing
1365 if not self._CHBOX_review.GetValue():
1366 self._CHBOX_abnormal.SetValue(self._PRW_abnormality_indicator.GetValue().strip() != u'')
1367 #--------------------------------------------------------
1372 #================================================================
1373 # measurement type handling
1374 #================================================================
1376
1377 if parent is None:
1378 parent = wx.GetApp().GetTopWindow()
1379
1380 #------------------------------------------------------------
1381 def edit(test_type=None):
1382 ea = cMeasurementTypeEAPnl(parent = parent, id = -1, type = test_type)
1383 dlg = gmEditArea.cGenericEditAreaDlg2 (
1384 parent = parent,
1385 id = -1,
1386 edit_area = ea,
1387 single_entry = gmTools.bool2subst((test_type is None), False, True)
1388 )
1389 dlg.SetTitle(gmTools.coalesce(test_type, _('Adding measurement type'), _('Editing measurement type')))
1390
1391 if dlg.ShowModal() == wx.ID_OK:
1392 dlg.Destroy()
1393 return True
1394
1395 dlg.Destroy()
1396 return False
1397 #------------------------------------------------------------
1398 def refresh(lctrl):
1399 mtypes = gmPathLab.get_measurement_types(order_by = 'name, abbrev')
1400 items = [ [
1401 m['abbrev'],
1402 m['name'],
1403 gmTools.coalesce(m['loinc'], u''),
1404 gmTools.coalesce(m['conversion_unit'], u''),
1405 gmTools.coalesce(m['comment_type'], u''),
1406 gmTools.coalesce(m['internal_name_org'], _('in-house')),
1407 gmTools.coalesce(m['comment_org'], u''),
1408 m['pk_test_type']
1409 ] for m in mtypes ]
1410 lctrl.set_string_items(items)
1411 lctrl.set_data(mtypes)
1412 #------------------------------------------------------------
1413 def delete(measurement_type):
1414 if measurement_type.in_use:
1415 gmDispatcher.send (
1416 signal = 'statustext',
1417 beep = True,
1418 msg = _('Cannot delete measurement type [%s (%s)] because it is in use.') % (measurement_type['name'], measurement_type['abbrev'])
1419 )
1420 return False
1421 gmPathLab.delete_measurement_type(measurement_type = measurement_type['pk_test_type'])
1422 return True
1423 #------------------------------------------------------------
1424 msg = _(
1425 '\n'
1426 'These are the measurement types currently defined in GNUmed.\n'
1427 '\n'
1428 )
1429
1430 gmListWidgets.get_choices_from_list (
1431 parent = parent,
1432 msg = msg,
1433 caption = _('Showing measurement types.'),
1434 columns = [_('Abbrev'), _('Name'), _('LOINC'), _('Base unit'), _('Comment'), _('Org'), _('Comment'), u'#'],
1435 single_selection = True,
1436 refresh_callback = refresh,
1437 edit_callback = edit,
1438 new_callback = edit,
1439 delete_callback = delete
1440 )
1441 #----------------------------------------------------------------
1443
1445
1446 query = u"""
1447 (
1448 select
1449 pk_test_type,
1450 name_tt
1451 || ' ('
1452 || coalesce (
1453 (select internal_name from clin.test_org cto where cto.pk = vcutt.pk_test_org),
1454 '%(in_house)s'
1455 )
1456 || ')'
1457 as name
1458 from clin.v_unified_test_types vcutt
1459 where
1460 name_meta %%(fragment_condition)s
1461
1462 ) union (
1463
1464 select
1465 pk_test_type,
1466 name_tt
1467 || ' ('
1468 || coalesce (
1469 (select internal_name from clin.test_org cto where cto.pk = vcutt.pk_test_org),
1470 '%(in_house)s'
1471 )
1472 || ')'
1473 as name
1474 from clin.v_unified_test_types vcutt
1475 where
1476 name_tt %%(fragment_condition)s
1477
1478 ) union (
1479
1480 select
1481 pk_test_type,
1482 name_tt
1483 || ' ('
1484 || coalesce (
1485 (select internal_name from clin.test_org cto where cto.pk = vcutt.pk_test_org),
1486 '%(in_house)s'
1487 )
1488 || ')'
1489 as name
1490 from clin.v_unified_test_types vcutt
1491 where
1492 abbrev_meta %%(fragment_condition)s
1493
1494 ) union (
1495
1496 select
1497 pk_test_type,
1498 name_tt
1499 || ' ('
1500 || coalesce (
1501 (select internal_name from clin.test_org cto where cto.pk = vcutt.pk_test_org),
1502 '%(in_house)s'
1503 )
1504 || ')'
1505 as name
1506 from clin.v_unified_test_types vcutt
1507 where
1508 code_tt %%(fragment_condition)s
1509 )
1510
1511 order by name
1512 limit 50""" % {'in_house': _('in house lab')}
1513
1514 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
1515 mp.setThresholds(1, 2, 4)
1516 mp.word_separators = '[ \t:@]+'
1517 gmPhraseWheel.cPhraseWheel.__init__ (
1518 self,
1519 *args,
1520 **kwargs
1521 )
1522 self.matcher = mp
1523 self.SetToolTipString(_('Select the type of measurement.'))
1524 self.selection_only = False
1525 #----------------------------------------------------------------
1527
1529
1530 query = u"""
1531 select distinct on (internal_name)
1532 pk,
1533 internal_name
1534 from clin.test_org
1535 where
1536 internal_name %(fragment_condition)s
1537 order by internal_name
1538 limit 50"""
1539 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
1540 mp.setThresholds(1, 2, 4)
1541 #mp.word_separators = '[ \t:@]+'
1542 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
1543 self.matcher = mp
1544 self.SetToolTipString(_('The name of the path lab/diagnostic organisation.'))
1545 self.selection_only = False
1546 #------------------------------------------------------------
1548 if self.data is not None:
1549 _log.debug('data already set, not creating')
1550 return
1551
1552 if self.GetValue().strip() == u'':
1553 _log.debug('cannot create new lab, missing name')
1554 return
1555
1556 lab = gmPathLab.create_test_org(name = self.GetValue().strip())
1557 self.SetText(value = lab['internal_name'], data = lab['pk'])
1558 return
1559 #----------------------------------------------------------------
1560 from Gnumed.wxGladeWidgets import wxgMeasurementTypeEAPnl
1561
1562 -class cMeasurementTypeEAPnl(wxgMeasurementTypeEAPnl.wxgMeasurementTypeEAPnl, gmEditArea.cGenericEditAreaMixin):
1563
1565
1566 try:
1567 data = kwargs['type']
1568 del kwargs['type']
1569 except KeyError:
1570 data = None
1571
1572 wxgMeasurementTypeEAPnl.wxgMeasurementTypeEAPnl.__init__(self, *args, **kwargs)
1573 gmEditArea.cGenericEditAreaMixin.__init__(self)
1574 self.mode = 'new'
1575 self.data = data
1576 if data is not None:
1577 self.mode = 'edit'
1578
1579 self.__init_ui()
1580
1581 #----------------------------------------------------------------
1583
1584 # name phraseweel
1585 query = u"""
1586 select distinct on (name)
1587 pk,
1588 name
1589 from clin.test_type
1590 where
1591 name %(fragment_condition)s
1592 order by name
1593 limit 50"""
1594 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
1595 mp.setThresholds(1, 2, 4)
1596 self._PRW_name.matcher = mp
1597 self._PRW_name.selection_only = False
1598
1599 # abbreviation
1600 query = u"""
1601 select distinct on (abbrev)
1602 pk,
1603 abbrev
1604 from clin.test_type
1605 where
1606 abbrev %(fragment_condition)s
1607 order by abbrev
1608 limit 50"""
1609 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
1610 mp.setThresholds(1, 2, 3)
1611 self._PRW_abbrev.matcher = mp
1612 self._PRW_abbrev.selection_only = False
1613
1614 # unit
1615 # FIXME: use units from test_result
1616 query = u"""
1617 select distinct on (conversion_unit)
1618 conversion_unit,
1619 conversion_unit
1620 from clin.test_type
1621 where
1622 conversion_unit %(fragment_condition)s
1623 order by conversion_unit
1624 limit 50"""
1625 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
1626 mp.setThresholds(1, 2, 3)
1627 self._PRW_conversion_unit.matcher = mp
1628 self._PRW_conversion_unit.selection_only = False
1629
1630 # loinc
1631 query = u"""
1632 select distinct on (term)
1633 loinc,
1634 term
1635 from ((
1636 select
1637 loinc,
1638 (loinc || ': ' || abbrev || ' (' || name || ')') as term
1639 from clin.test_type
1640 where loinc %(fragment_condition)s
1641 limit 50
1642 ) union all (
1643 select
1644 code as loinc,
1645 (code || ': ' || term) as term
1646 from ref.v_coded_terms
1647 where
1648 coding_system = 'LOINC'
1649 and
1650 lang = i18n.get_curr_lang()
1651 and
1652 (code %(fragment_condition)s
1653 or
1654 term %(fragment_condition)s)
1655 limit 50
1656 ) union all (
1657 select
1658 code as loinc,
1659 (code || ': ' || term) as term
1660 from ref.v_coded_terms
1661 where
1662 coding_system = 'LOINC'
1663 and
1664 lang = 'en_EN'
1665 and
1666 (code %(fragment_condition)s
1667 or
1668 term %(fragment_condition)s)
1669 limit 50
1670 ) union all (
1671 select
1672 code as loinc,
1673 (code || ': ' || term) as term
1674 from ref.v_coded_terms
1675 where
1676 coding_system = 'LOINC'
1677 and
1678 (code %(fragment_condition)s
1679 or
1680 term %(fragment_condition)s)
1681 limit 50
1682 )
1683 ) as all_known_loinc
1684 order by term
1685 limit 50"""
1686 mp = gmMatchProvider.cMatchProvider_SQL2(queries = query)
1687 mp.setThresholds(1, 2, 4)
1688 self._PRW_loinc.matcher = mp
1689 self._PRW_loinc.selection_only = False
1690 self._PRW_loinc.add_callback_on_lose_focus(callback = self._on_loinc_lost_focus)
1691
1692 # # test org
1693 # query = u"""
1694 #select distinct on (internal_name)
1695 # pk,
1696 # internal_name
1697 #from clin.test_org
1698 #where
1699 # internal_name %(fragment_condition)s
1700 #order by internal_name
1701 #limit 50"""
1702 # mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
1703 # mp.setThresholds(1, 2, 4)
1704 # self._PRW_test_org.matcher = mp
1705 # self._PRW_test_org.selection_only = False
1706 #----------------------------------------------------------------
1708 loinc = self._PRW_loinc.GetData()
1709
1710 if loinc is None:
1711 self._TCTRL_loinc_info.SetValue(u'')
1712 return
1713
1714 info = gmLOINC.loinc2info(loinc = loinc)
1715 if len(info) == 0:
1716 self._TCTRL_loinc_info.SetValue(u'')
1717 return
1718
1719 self._TCTRL_loinc_info.SetValue(info[0])
1720 #----------------------------------------------------------------
1721 # generic Edit Area mixin API
1722 #----------------------------------------------------------------
1724
1725 has_errors = False
1726 for field in [self._PRW_name, self._PRW_abbrev, self._PRW_conversion_unit]:
1727 if field.GetValue().strip() in [u'', None]:
1728 has_errors = True
1729 field.display_as_valid(valid = False)
1730 else:
1731 field.display_as_valid(valid = True)
1732 field.Refresh()
1733
1734 return (not has_errors)
1735 #----------------------------------------------------------------
1737
1738 pk_org = self._PRW_test_org.GetData()
1739 if pk_org is None:
1740 pk_org = gmPathLab.create_measurement_org (
1741 name = gmTools.none_if(self._PRW_test_org.GetValue().strip(), u''),
1742 comment = gmTools.none_if(self._TCTRL_comment_org.GetValue().strip(), u'')
1743 )
1744
1745 tt = gmPathLab.create_measurement_type (
1746 lab = pk_org,
1747 abbrev = self._PRW_abbrev.GetValue().strip(),
1748 name = self._PRW_name.GetValue().strip(),
1749 unit = gmTools.none_if(self._PRW_conversion_unit.GetValue().strip(), u'')
1750 )
1751 tt['loinc'] = gmTools.none_if(self._PRW_loinc.GetValue().strip(), u'')
1752 tt['comment_type'] = gmTools.none_if(self._TCTRL_comment_type.GetValue().strip(), u'')
1753 tt.save()
1754
1755 self.data = tt
1756
1757 return True
1758 #----------------------------------------------------------------
1760
1761 pk_org = self._PRW_test_org.GetData()
1762 if pk_org is None:
1763 pk_org = gmPathLab.create_measurement_org (
1764 name = gmTools.none_if(self._PRW_test_org.GetValue().strip(), u''),
1765 comment = gmTools.none_if(self._TCTRL_comment_org.GetValue().strip(), u'')
1766 )
1767
1768 self.data['pk_test_org'] = pk_org
1769 self.data['abbrev'] = self._PRW_abbrev.GetValue().strip()
1770 self.data['name'] = self._PRW_name.GetValue().strip()
1771 self.data['conversion_unit'] = gmTools.none_if(self._PRW_conversion_unit.GetValue().strip(), u'')
1772 self.data['loinc'] = gmTools.none_if(self._PRW_loinc.GetValue().strip(), u'')
1773 self.data['comment_type'] = gmTools.none_if(self._TCTRL_comment_type.GetValue().strip(), u'')
1774 self.data.save()
1775
1776 return True
1777 #----------------------------------------------------------------
1779 self._PRW_name.SetText(u'', None, True)
1780 self._PRW_abbrev.SetText(u'', None, True)
1781 self._PRW_conversion_unit.SetText(u'', None, True)
1782 self._PRW_loinc.SetText(u'', None, True)
1783 self._TCTRL_loinc_info.SetValue(u'')
1784 self._TCTRL_comment_type.SetValue(u'')
1785 self._PRW_test_org.SetText(u'', None, True)
1786 self._TCTRL_comment_org.SetValue(u'')
1787 #----------------------------------------------------------------
1789 self._PRW_name.SetText(self.data['name'], self.data['name'], True)
1790 self._PRW_abbrev.SetText(self.data['abbrev'], self.data['abbrev'], True)
1791 self._PRW_conversion_unit.SetText (
1792 gmTools.coalesce(self.data['conversion_unit'], u''),
1793 self.data['conversion_unit'],
1794 True
1795 )
1796 self._PRW_loinc.SetText (
1797 gmTools.coalesce(self.data['loinc'], u''),
1798 self.data['loinc'],
1799 True
1800 )
1801 self._TCTRL_loinc_info.SetValue(u'') # FIXME: properly set
1802 self._TCTRL_comment_type.SetValue(gmTools.coalesce(self.data['comment_type'], u''))
1803 self._PRW_test_org.SetText (
1804 gmTools.coalesce(self.data['pk_test_org'], u'', self.data['internal_name_org']),
1805 self.data['pk_test_org'],
1806 True
1807 )
1808 self._TCTRL_comment_org.SetValue(gmTools.coalesce(self.data['comment_org'], u''))
1809 #----------------------------------------------------------------
1818 #================================================================
1820
1822
1823 query = u"""
1824 select distinct val_unit,
1825 val_unit, val_unit
1826 from clin.v_test_results
1827 where
1828 (
1829 val_unit %(fragment_condition)s
1830 or
1831 conversion_unit %(fragment_condition)s
1832 )
1833 %(ctxt_test_name)s
1834 %(ctxt_test_pk)s
1835 order by val_unit
1836 limit 25"""
1837
1838 ctxt = {
1839 'ctxt_test_name': {
1840 'where_part': u'and %(test)s in (name_tt, name_meta, code_tt, abbrev_meta)',
1841 'placeholder': u'test'
1842 },
1843 'ctxt_test_pk': {
1844 'where_part': u'and pk_test_type = %(pk_type)s',
1845 'placeholder': u'pk_type'
1846 }
1847 }
1848
1849 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query, context=ctxt)
1850 mp.setThresholds(1, 2, 4)
1851 gmPhraseWheel.cPhraseWheel.__init__ (
1852 self,
1853 *args,
1854 **kwargs
1855 )
1856 self.matcher = mp
1857 self.SetToolTipString(_('Select the unit of the test result.'))
1858 self.selection_only = False
1859
1860 #================================================================
1861
1862 #================================================================
1864
1866
1867 query = u"""
1868 select distinct abnormality_indicator,
1869 abnormality_indicator, abnormality_indicator
1870 from clin.v_test_results
1871 where
1872 abnormality_indicator %(fragment_condition)s
1873 order by abnormality_indicator
1874 limit 25"""
1875
1876 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query)
1877 mp.setThresholds(1, 1, 2)
1878 mp.ignored_chars = "[.'\\\[\]#$%_]+" + '"'
1879 mp.word_separators = '[ \t&:]+'
1880 gmPhraseWheel.cPhraseWheel.__init__ (
1881 self,
1882 *args,
1883 **kwargs
1884 )
1885 self.matcher = mp
1886 self.SetToolTipString(_('Select an indicator for the level of abnormality.'))
1887 self.selection_only = False
1888 #================================================================
1889 # measurement org widgets / functions
1890 #----------------------------------------------------------------
1892 ea = cMeasurementOrgEAPnl(parent = parent, id = -1)
1893 ea.data = org
1894 ea.mode = gmTools.coalesce(org, 'new', 'edit')
1895 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea)
1896 dlg.SetTitle(gmTools.coalesce(org, _('Adding new diagnostic org'), _('Editing diagnostic org')))
1897 if dlg.ShowModal() == wx.ID_OK:
1898 dlg.Destroy()
1899 return True
1900 dlg.Destroy()
1901 return False
1902 #----------------------------------------------------------------
1904
1905 if parent is None:
1906 parent = wx.GetApp().GetTopWindow()
1907
1908 #------------------------------------------------------------
1909 def edit(org=None):
1910 return edit_measurement_org(parent = parent, org = org)
1911 #------------------------------------------------------------
1912 def refresh(lctrl):
1913 orgs = gmPathLab.get_test_orgs()
1914 lctrl.set_string_items ([
1915 (o['internal_name'], gmTools.coalesce(o['contact'], u''), gmTools.coalesce(o['comment']), o['pk'])
1916 for o in orgs
1917 ])
1918 lctrl.set_data(orgs)
1919 #------------------------------------------------------------
1920 def delete(measurement_type):
1921 if measurement_type.in_use:
1922 gmDispatcher.send (
1923 signal = 'statustext',
1924 beep = True,
1925 msg = _('Cannot delete measurement type [%s (%s)] because it is in use.') % (measurement_type['name'], measurement_type['abbrev'])
1926 )
1927 return False
1928 gmPathLab.delete_measurement_type(measurement_type = measurement_type['pk_test_type'])
1929 return True
1930 #------------------------------------------------------------
1931 gmListWidgets.get_choices_from_list (
1932 parent = parent,
1933 msg = _('\nThese are the diagnostic orgs (path labs etc) currently defined in GNUmed.\n\n'),
1934 caption = _('Showing diagnostic orgs.'),
1935 columns = [_('Name'), _('Contact'), _('Comment'), u'#'],
1936 single_selection = True,
1937 refresh_callback = refresh,
1938 edit_callback = edit,
1939 new_callback = edit
1940 # ,delete_callback = delete
1941 )
1942
1943
1944 #----------------------------------------------------------------
1945 from Gnumed.wxGladeWidgets import wxgMeasurementOrgEAPnl
1946
1947 -class cMeasurementOrgEAPnl(wxgMeasurementOrgEAPnl.wxgMeasurementOrgEAPnl, gmEditArea.cGenericEditAreaMixin):
1948
1950
1951 try:
1952 data = kwargs['org']
1953 del kwargs['org']
1954 except KeyError:
1955 data = None
1956
1957 wxgMeasurementOrgEAPnl.wxgMeasurementOrgEAPnl.__init__(self, *args, **kwargs)
1958 gmEditArea.cGenericEditAreaMixin.__init__(self)
1959
1960 # Code using this mixin should set mode and data
1961 # after instantiating the class:
1962 self.mode = 'new'
1963 self.data = data
1964 if data is not None:
1965 self.mode = 'edit'
1966
1967 #self.__init_ui()
1968 #----------------------------------------------------------------
1969 # def __init_ui(self):
1970 # # adjust phrasewheels etc
1971 #----------------------------------------------------------------
1972 # generic Edit Area mixin API
1973 #----------------------------------------------------------------
1975 has_errors = False
1976 if self._PRW_name.GetValue().strip() == u'':
1977 has_errors = True
1978 self._PRW_name.display_as_valid(valid = False)
1979 else:
1980 self._PRW_name.display_as_valid(valid = True)
1981
1982 return (not has_errors)
1983 #----------------------------------------------------------------
1985 # save the data as a new instance
1986 data = self._PRW_name.GetData(can_create = True)
1987
1988 data['contact'] = self._TCTRL_contact.GetValue().strip()
1989 data['comment'] = self._TCTRL_comment.GetValue().strip()
1990 data.save()
1991
1992 # must be done very late or else the property access
1993 # will refresh the display such that later field
1994 # access will return empty values
1995 self.data = data
1996
1997 return True
1998 #----------------------------------------------------------------
2000 self.data['internal_name'] = self._PRW_name.GetValue().strip()
2001 self.data['contact'] = self._TCTRL_contact.GetValue().strip()
2002 self.data['comment'] = self._TCTRL_comment.GetValue().strip()
2003 self.data.save()
2004 return True
2005 #----------------------------------------------------------------
2007 self._PRW_name.SetText(value = u'', data = None)
2008 self._TCTRL_contact.SetValue(u'')
2009 self._TCTRL_comment.SetValue(u'')
2010 #----------------------------------------------------------------
2012 self._PRW_name.SetText(value = self.data['internal_name'], data = self.data['pk'])
2013 self._TCTRL_contact.SetValue(gmTools.coalesce(self.data['contact'], u''))
2014 self._TCTRL_comment.SetValue(gmTools.coalesce(self.data['comment'], u''))
2015 #----------------------------------------------------------------
2018 #================================================================
2020
2021 if parent is None:
2022 parent = wx.GetApp().GetTopWindow()
2023
2024 msg = _(
2025 '\n'
2026 'These are the meta test types currently defined in GNUmed.\n'
2027 '\n'
2028 'Meta test types allow you to aggregate several actual test types used\n'
2029 'by pathology labs into one logical type.\n'
2030 '\n'
2031 'This is useful for grouping together results of tests which come under\n'
2032 'different names but really are the same thing. This often happens when\n'
2033 'you switch labs or the lab starts using another test method.\n'
2034 )
2035
2036 mtts = gmPathLab.get_meta_test_types()
2037
2038 gmListWidgets.get_choices_from_list (
2039 parent = parent,
2040 msg = msg,
2041 caption = _('Showing meta test types.'),
2042 columns = [_('Abbrev'), _('Name'), _('LOINC'), _('Comment'), u'#'],
2043 choices = [ [
2044 m['abbrev'],
2045 m['name'],
2046 gmTools.coalesce(m['loinc'], u''),
2047 gmTools.coalesce(m['comment'], u''),
2048 m['pk']
2049 ] for m in mtts ],
2050 data = mtts,
2051 single_selection = True,
2052 #edit_callback = edit,
2053 #new_callback = edit,
2054 #delete_callback = delete,
2055 #refresh_callback = refresh
2056 )
2057 #================================================================
2058 # main
2059 #----------------------------------------------------------------
2060 if __name__ == '__main__':
2061
2062 from Gnumed.pycommon import gmLog2
2063
2064 gmI18N.activate_locale()
2065 gmI18N.install_domain()
2066 gmDateTime.init()
2067
2068 #------------------------------------------------------------
2070 pat = gmPerson.ask_for_patient()
2071 app = wx.PyWidgetTester(size = (500, 300))
2072 lab_grid = cMeasurementsGrid(parent = app.frame, id = -1)
2073 lab_grid.patient = pat
2074 app.frame.Show()
2075 app.MainLoop()
2076 #------------------------------------------------------------
2078 pat = gmPerson.ask_for_patient()
2079 gmPatSearchWidgets.set_active_patient(patient=pat)
2080 app = wx.PyWidgetTester(size = (500, 300))
2081 ea = cMeasurementEditAreaPnl(parent = app.frame, id = -1)
2082 app.frame.Show()
2083 app.MainLoop()
2084 #------------------------------------------------------------
2085 # def test_primary_care_vitals_pnl():
2086 # app = wx.PyWidgetTester(size = (500, 300))
2087 # pnl = wxgPrimaryCareVitalsInputPnl.wxgPrimaryCareVitalsInputPnl(parent = app.frame, id = -1)
2088 # app.frame.Show()
2089 # app.MainLoop()
2090 #------------------------------------------------------------
2091 if (len(sys.argv) > 1) and (sys.argv[1] == 'test'):
2092 #test_grid()
2093 test_test_ea_pnl()
2094 #test_primary_care_vitals_pnl()
2095
2096 #================================================================
2097 # $Log: gmMeasurementWidgets.py,v $
2098 # Revision 1.66 2010/02/02 13:55:33 ncq
2099 # - much improved results tooltip
2100 # - manage diagnostic orgs
2101 #
2102 # Revision 1.65 2010/01/31 18:19:11 ncq
2103 # - fix faulty access to value of abnormality indicator PRW
2104 #
2105 # Revision 1.64 2009/12/21 15:12:29 ncq
2106 # - cleanup
2107 # - fix typo
2108 # - missing return
2109 #
2110 # Revision 1.63 2009/12/03 17:50:20 ncq
2111 # - row label tooltips
2112 #
2113 # Revision 1.62 2009/12/01 21:54:04 ncq
2114 # - cleanup
2115 #
2116 # Revision 1.61 2009/10/28 16:42:53 ncq
2117 # - make grid draggable
2118 #
2119 # Revision 1.60 2009/09/17 21:54:55 ncq
2120 # - properly access test type pk
2121 # - check for use before deleting test type
2122 #
2123 # Revision 1.59 2009/09/01 22:33:25 ncq
2124 # - order test types in list
2125 #
2126 # Revision 1.58 2009/08/24 20:11:27 ncq
2127 # - bump db version
2128 # - fix tag creation
2129 # - provider inbox:
2130 # enable filter-to-active-patient,
2131 # listen to new signal,
2132 # use cInboxMessage class
2133 # - properly constrain LOINC phrasewheel SQL
2134 # - include v12 scripts in release
2135 # - install arriba jar to /usr/local/bin/
2136 # - check for table existence in audit schema generator
2137 # - include dem.message inbox with additional generic signals
2138 #
2139 # Revision 1.57 2009/08/11 10:49:23 ncq
2140 # - cleanup
2141 # - remove LOINC files after import
2142 # - row labels now "abbrev (desc)", again
2143 # - Encyclopedia -> Reference
2144 # - improved LOINC matcher and use loinc to set loinc info
2145 #
2146 # Revision 1.56 2009/08/08 12:18:12 ncq
2147 # - setup phrasewheels in measurement type EA
2148 #
2149 # Revision 1.55 2009/08/03 20:50:48 ncq
2150 # - properly support adding/editing measurement type
2151 #
2152 # Revision 1.54 2009/07/20 20:33:35 ncq
2153 # - start implementing test type management
2154 #
2155 # Revision 1.53 2009/07/15 12:22:46 ncq
2156 # - fix incomplete validity check for new-result problem
2157 #
2158 # Revision 1.52 2009/07/06 17:15:45 ncq
2159 # - row labels only test name until proper support for abbrev is there
2160 # - improved formatting of test result for display in cell
2161 # - only remind of display being most-recent only if cell actually is multi-result in cell tooltip
2162 # - use successful-save message on EA
2163 # - safer refresh after save-and-next-value
2164 #
2165 # Revision 1.51 2009/07/02 20:54:05 ncq
2166 # - fix bug where second patient didn't show measurements on patient change
2167 #
2168 # Revision 1.50 2009/06/22 09:26:49 ncq
2169 # - people didn't like the bandwidth calculation
2170 #
2171 # Revision 1.49 2009/06/20 22:38:05 ncq
2172 # - factor out cell tooltip creation and only do it on mouse over
2173 #
2174 # Revision 1.48 2009/06/11 12:37:25 ncq
2175 # - much simplified initial setup of list ctrls
2176 #
2177 # Revision 1.47 2009/06/04 16:19:00 ncq
2178 # - re-adjust to test table changes
2179 # - update loinc
2180 # - adjust to list widget changes (refresh)
2181 #
2182 # Revision 1.47 2009/05/28 10:53:40 ncq
2183 # - adjust to test tables changes
2184 #
2185 # Revision 1.46 2009/05/24 16:29:14 ncq
2186 # - support (meta) test types
2187 #
2188 # Revision 1.45 2009/04/24 12:05:20 ncq
2189 # - properly display lab link in grid corner
2190 #
2191 # Revision 1.44 2009/04/21 17:01:12 ncq
2192 # - try various other things to try to center the lab link
2193 #
2194 # Revision 1.43 2009/04/19 22:28:23 ncq
2195 # - put hyperlink in upper left corner of lab grid
2196 #
2197 # Revision 1.42 2009/04/14 18:35:27 ncq
2198 # - HCI screening revealed test types scroll off when
2199 # moving horizontall so fix that
2200 #
2201 # Revision 1.41 2009/04/03 09:50:21 ncq
2202 # - comment
2203 #
2204 # Revision 1.40 2009/03/18 14:30:47 ncq
2205 # - improved result tooltip
2206 #
2207 # Revision 1.39 2009/03/01 18:15:55 ncq
2208 # - lots of missing u'', decode strftime results
2209 # - adjust word separators in test type match provider
2210 #
2211 # Revision 1.38 2009/02/20 15:43:21 ncq
2212 # - u''ify
2213 #
2214 # Revision 1.37 2009/02/17 17:47:31 ncq
2215 # - comment out primary care vitals
2216 #
2217 # Revision 1.36 2009/02/12 16:23:39 ncq
2218 # - start work on primary care vitals input
2219 #
2220 # Revision 1.35 2009/01/28 11:27:56 ncq
2221 # - slightly better naming and comments
2222 #
2223 # Revision 1.34 2009/01/02 11:40:27 ncq
2224 # - properly check for numericity of value/range input
2225 #
2226 # Revision 1.33 2008/10/22 12:21:57 ncq
2227 # - use %x in strftime where appropriate
2228 #
2229 # Revision 1.32 2008/08/31 18:21:54 ncq
2230 # - work around Windows' inability to do nothing when
2231 # there's nothing to do
2232 #
2233 # Revision 1.31 2008/08/31 18:04:30 ncq
2234 # - properly handle cell data now being list in select_cells()
2235 #
2236 # Revision 1.30 2008/08/31 17:13:50 ncq
2237 # - don't crash on double-clicking empty test results cell
2238 #
2239 # Revision 1.29 2008/08/31 17:04:17 ncq
2240 # - need to cast val_normal/target_min/max to unicode before display
2241 #
2242 # Revision 1.28 2008/08/15 15:57:10 ncq
2243 # - indicate data revisions in tooltip
2244 #
2245 # Revision 1.27 2008/08/08 13:31:58 ncq
2246 # - better results layout
2247 #
2248 # Revision 1.26 2008/08/05 16:21:30 ncq
2249 # - support multiple values per cell
2250 #
2251 # Revision 1.25 2008/07/17 21:41:36 ncq
2252 # - cleanup
2253 #
2254 # Revision 1.24 2008/07/14 13:47:36 ncq
2255 # - explicitely set focus after refresh per user request
2256 #
2257 # Revision 1.23 2008/07/13 16:13:33 ncq
2258 # - add_new_measurement -> edit_measurement
2259 # - use cGenericEditAreaMixin on results edit area
2260 # - invoked results edit area via double-click on result in grid
2261 #
2262 # Revision 1.22 2008/07/07 13:43:17 ncq
2263 # - current patient .connected
2264 #
2265 # Revision 1.21 2008/06/24 14:00:09 ncq
2266 # - action button popup menu
2267 # - handle result deletion
2268 #
2269 # Revision 1.20 2008/06/23 21:50:26 ncq
2270 # - create test types on the fly
2271 #
2272 # Revision 1.19 2008/06/22 17:32:39 ncq
2273 # - implement refresh on measurement ea so "Next" will work in dialog
2274 #
2275 # Revision 1.18 2008/06/19 15:26:09 ncq
2276 # - finish saving test result from edit area
2277 # - fix a few oversights in the result tooltip
2278 #
2279 # Revision 1.17 2008/06/18 15:49:22 ncq
2280 # - improve save validity check on edit area
2281 #
2282 # Revision 1.16 2008/06/16 15:03:20 ncq
2283 # - first cut at saving test results
2284 #
2285 # Revision 1.15 2008/06/15 20:43:31 ncq
2286 # - add test result indicator phrasewheel
2287 #
2288 # Revision 1.14 2008/06/09 15:36:04 ncq
2289 # - reordered for clarity
2290 # - add_new_measurement
2291 # - edit area start
2292 # - phrasewheels
2293 #
2294 # Revision 1.13 2008/05/14 15:01:43 ncq
2295 # - remove spurious evt argument in _on_pre_patient_selection()
2296 #
2297 # Revision 1.12 2008/04/26 21:40:58 ncq
2298 # - eventually support selecting certain ranges of cells
2299 #
2300 # Revision 1.11 2008/04/26 10:05:32 ncq
2301 # - in review dialog when user is already responsible
2302 # disable make_me_responsible checkbox
2303 #
2304 # Revision 1.10 2008/04/22 21:18:49 ncq
2305 # - implement signing
2306 # - improved tooltip
2307 # - properly clear grid when active patient changes
2308 #
2309 # Revision 1.9 2008/04/16 20:39:39 ncq
2310 # - working versions of the wxGlade code and use it, too
2311 # - show client version in login dialog
2312 #
2313 # Revision 1.8 2008/04/11 23:12:23 ncq
2314 # - improve docs
2315 #
2316 # Revision 1.7 2008/04/04 13:09:45 ncq
2317 # - use +/- as abnormality indicator where not available
2318 # - more complete calculation of "more result data available"
2319 #
2320 # Revision 1.6 2008/04/02 10:48:33 ncq
2321 # - cleanup, review -> sign
2322 # - support test_count in review widget
2323 # - better results formatting as per list discussion
2324 #
2325 # Revision 1.5 2008/03/29 16:19:57 ncq
2326 # - review_current_selection()
2327 # - get_selected_cells()
2328 # - bold test names
2329 # - display abnormality indicator and clinical relevance
2330 # - improve tooltip
2331 # - cMeasurementsReviewDialog()
2332 # - listen to test result database changes
2333 #
2334 # Revision 1.4 2008/03/25 19:36:30 ncq
2335 # - fix imports
2336 # - better docs
2337 # - str() wants non-u''
2338 # - cMeasurementsPnl()
2339 #
2340 # Revision 1.3 2008/03/20 15:31:40 ncq
2341 # - improve cell tooltips with review status and issue/episode information
2342 # - start row labels tooltips
2343 #
2344 # Revision 1.2 2008/03/17 14:55:41 ncq
2345 # - add lots of TODOs
2346 # - better layout
2347 # - set grid cell tooltips
2348 #
2349 # Revision 1.1 2008/03/16 11:57:47 ncq
2350 # - first iteration
2351 #
2352 #
2353
| Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Tue Feb 9 04:02:03 2010 | http://epydoc.sourceforge.net |