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