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