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