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