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