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