Package Gnumed :: Package wxpython :: Module gmMeasurementWidgets
[frames] | no frames]

Source Code for Module Gnumed.wxpython.gmMeasurementWidgets

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