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