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