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