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, gmPathLab, gmSurgery, gmLOINC, gmForms, gmPersonSearch 
  18  from Gnumed.pycommon import gmTools, gmDispatcher, gmMatchProvider, gmDateTime, gmI18N, gmCfg, gmShellAPI 
  19  from Gnumed.pycommon import gmNetworkTools 
  20  from Gnumed.wxpython import gmRegetMixin, gmPhraseWheel, gmEditArea, gmGuiHelpers, gmListWidgets 
  21  from Gnumed.wxpython import gmAuthWidgets, gmPatSearchWidgets, gmFormWidgets 
  22  from Gnumed.wxGladeWidgets import wxgMeasurementsPnl, wxgMeasurementsReviewDlg 
  23  from Gnumed.wxGladeWidgets import wxgMeasurementEditAreaPnl 
  24   
  25   
  26  _log = logging.getLogger('gm.ui') 
  27  _log.info(__version__) 
  28   
  29  #================================================================ 
  30  # LOINC related widgets 
  31  #================================================================ 
32 -def update_loinc_reference_data():
33 34 wx.BeginBusyCursor() 35 36 gmDispatcher.send(signal = 'statustext', msg = _('Updating LOINC data can take quite a while...'), beep = True) 37 38 # download 39 downloaded, loinc_dir = gmNetworkTools.download_data_pack(url = 'http://www.gnumed.de/downloads/data/loinc/loinctab.zip') 40 if not downloaded: 41 wx.EndBusyCursor() 42 gmGuiHelpers.gm_show_warning ( 43 aTitle = _('Downloading LOINC'), 44 aMessage = _('Error downloading the latest LOINC data.\n') 45 ) 46 return False 47 48 # split master data file 49 data_fname, license_fname = gmLOINC.split_LOINCDBTXT(input_fname = os.path.join(loinc_dir, 'LOINCDB.TXT')) 50 51 wx.EndBusyCursor() 52 53 conn = gmAuthWidgets.get_dbowner_connection(procedure = _('importing LOINC reference data')) 54 if conn is None: 55 return False 56 57 wx.BeginBusyCursor() 58 59 # import data 60 if gmLOINC.loinc_import(data_fname = data_fname, license_fname = license_fname, conn = conn): 61 gmDispatcher.send(signal = 'statustext', msg = _('Successfully imported LOINC reference data.')) 62 else: 63 gmDispatcher.send(signal = 'statustext', msg = _('Importing LOINC reference data failed.'), beep = True) 64 65 wx.EndBusyCursor() 66 return True
67 #================================================================ 68 # convenience functions 69 #================================================================
70 -def call_browser_on_measurement_type(measurement_type=None):
71 72 dbcfg = gmCfg.cCfgSQL() 73 74 url = dbcfg.get ( 75 option = u'external.urls.measurements_search', 76 workplace = gmSurgery.gmCurrentPractice().active_workplace, 77 bias = 'user', 78 default = u"http://www.google.de/search?as_oq=%(search_term)s&num=10&as_sitesearch=laborlexikon.de" 79 ) 80 81 base_url = dbcfg.get2 ( 82 option = u'external.urls.measurements_encyclopedia', 83 workplace = gmSurgery.gmCurrentPractice().active_workplace, 84 bias = 'user', 85 default = u'http://www.laborlexikon.de' 86 ) 87 88 if measurement_type is None: 89 url = base_url 90 91 measurement_type = measurement_type.strip() 92 93 if measurement_type == u'': 94 url = base_url 95 96 url = url % {'search_term': measurement_type} 97 98 webbrowser.open ( 99 url = url, 100 new = False, 101 autoraise = True 102 )
103 #----------------------------------------------------------------
104 -def edit_measurement(parent=None, measurement=None, single_entry=False):
105 ea = cMeasurementEditAreaPnl(parent = parent, id = -1) 106 ea.data = measurement 107 ea.mode = gmTools.coalesce(measurement, 'new', 'edit') 108 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = single_entry) 109 dlg.SetTitle(gmTools.coalesce(measurement, _('Adding new measurement'), _('Editing measurement'))) 110 if dlg.ShowModal() == wx.ID_OK: 111 dlg.Destroy() 112 return True 113 dlg.Destroy() 114 return False
115 #================================================================
116 -def plot_measurements(parent=None, tests=None):
117 118 template = gmFormWidgets.manage_form_templates ( 119 parent = parent, 120 active_only = True, 121 template_types = [u'gnuplot script'] 122 ) 123 124 if template is None: 125 gmGuiHelpers.gm_show_error ( 126 aMessage = _('Cannot plot without a plot script.'), 127 aTitle = _('Plotting test results') 128 ) 129 return False 130 131 fname_data = gmPathLab.export_results_for_gnuplot(results = tests) 132 133 script = template.instantiate() 134 script.data_filename = fname_data 135 script.generate_output(format = 'wxp') # Gnuplot output terminal
136 137 #================================================================ 138 #from Gnumed.wxGladeWidgets import wxgPrimaryCareVitalsInputPnl 139 140 # Taillenumfang: Mitte zwischen unterster Rippe und 141 # hoechstem Teil des Beckenkamms 142 # Maenner: maessig: 94-102, deutlich: > 102 .. erhoeht 143 # Frauen: maessig: 80-88, deutlich: > 88 .. erhoeht 144 145 #================================================================ 146 # display widgets 147 #================================================================
148 -class cMeasurementsGrid(wx.grid.Grid):
149 """A grid class for displaying measurment results. 150 151 - does NOT listen to the currently active patient 152 - thereby it can display any patient at any time 153 """ 154 # FIXME: sort-by-battery 155 # FIXME: filter-by-battery 156 # FIXME: filter out empty 157 # FIXME: filter by tests of a selected date 158 # FIXME: dates DESC/ASC by cfg 159 # FIXME: mouse over column header: display date info
160 - def __init__(self, *args, **kwargs):
161 162 wx.grid.Grid.__init__(self, *args, **kwargs) 163 164 self.__patient = None 165 self.__cell_data = {} 166 self.__row_label_data = [] 167 168 self.__prev_row = None 169 self.__prev_col = None 170 self.__prev_label_row = None 171 self.__date_format = str((_('lab_grid_date_format::%Y\n%b %d')).lstrip('lab_grid_date_format::')) 172 173 self.__init_ui() 174 self.__register_events()
175 #------------------------------------------------------------ 176 # external API 177 #------------------------------------------------------------
178 - def delete_current_selection(self):
179 if not self.IsSelection(): 180 gmDispatcher.send(signal = u'statustext', msg = _('No results selected for deletion.')) 181 return True 182 183 selected_cells = self.get_selected_cells() 184 if len(selected_cells) > 20: 185 results = None 186 msg = _( 187 'There are %s results marked for deletion.\n' 188 '\n' 189 'Are you sure you want to delete these results ?' 190 ) % len(selected_cells) 191 else: 192 results = self.__cells_to_data(cells = selected_cells, exclude_multi_cells = False) 193 txt = u'\n'.join([ u'%s %s (%s): %s %s%s' % ( 194 r['clin_when'].strftime('%x %H:%M').decode(gmI18N.get_encoding()), 195 r['unified_abbrev'], 196 r['unified_name'], 197 r['unified_val'], 198 r['val_unit'], 199 gmTools.coalesce(r['abnormality_indicator'], u'', u' (%s)') 200 ) for r in results 201 ]) 202 msg = _( 203 'The following results are marked for deletion:\n' 204 '\n' 205 '%s\n' 206 '\n' 207 'Are you sure you want to delete these results ?' 208 ) % txt 209 210 dlg = gmGuiHelpers.c2ButtonQuestionDlg ( 211 self, 212 -1, 213 caption = _('Deleting test results'), 214 question = msg, 215 button_defs = [ 216 {'label': _('Delete'), 'tooltip': _('Yes, delete all the results.'), 'default': False}, 217 {'label': _('Cancel'), 'tooltip': _('No, do NOT delete any results.'), 'default': True} 218 ] 219 ) 220 decision = dlg.ShowModal() 221 222 if decision == wx.ID_YES: 223 if results is None: 224 results = self.__cells_to_data(cells = selected_cells, exclude_multi_cells = False) 225 for result in results: 226 gmPathLab.delete_test_result(result)
227 #------------------------------------------------------------
228 - def sign_current_selection(self):
229 if not self.IsSelection(): 230 gmDispatcher.send(signal = u'statustext', msg = _('Cannot sign results. No results selected.')) 231 return True 232 233 selected_cells = self.get_selected_cells() 234 if len(selected_cells) > 10: 235 test_count = len(selected_cells) 236 tests = None 237 else: 238 test_count = None 239 tests = self.__cells_to_data(cells = selected_cells, exclude_multi_cells = False) 240 241 dlg = cMeasurementsReviewDlg ( 242 self, 243 -1, 244 tests = tests, 245 test_count = test_count 246 ) 247 decision = dlg.ShowModal() 248 249 if decision == wx.ID_APPLY: 250 wx.BeginBusyCursor() 251 252 if dlg._RBTN_confirm_abnormal.GetValue(): 253 abnormal = None 254 elif dlg._RBTN_results_normal.GetValue(): 255 abnormal = False 256 else: 257 abnormal = True 258 259 if dlg._RBTN_confirm_relevance.GetValue(): 260 relevant = None 261 elif dlg._RBTN_results_not_relevant.GetValue(): 262 relevant = False 263 else: 264 relevant = True 265 266 if tests is None: 267 tests = self.__cells_to_data(cells = selected_cells, exclude_multi_cells = False) 268 269 comment = None 270 if len(tests) == 1: 271 comment = dlg._TCTRL_comment.GetValue() 272 273 for test in tests: 274 test.set_review ( 275 technically_abnormal = abnormal, 276 clinically_relevant = relevant, 277 comment = comment, 278 make_me_responsible = dlg._CHBOX_responsible.IsChecked() 279 ) 280 281 wx.EndBusyCursor() 282 283 dlg.Destroy()
284 #------------------------------------------------------------
285 - def plot_current_selection(self):
286 287 if not self.IsSelection(): 288 gmDispatcher.send(signal = u'statustext', msg = _('Cannot plot results. No results selected.')) 289 return True 290 291 tests = self.__cells_to_data ( 292 cells = self.get_selected_cells(), 293 exclude_multi_cells = False, 294 auto_include_multi_cells = True 295 ) 296 297 plot_measurements(parent = self, tests = tests)
298 #------------------------------------------------------------
299 - def get_selected_cells(self):
300 301 sel_block_top_left = self.GetSelectionBlockTopLeft() 302 sel_block_bottom_right = self.GetSelectionBlockBottomRight() 303 sel_cols = self.GetSelectedCols() 304 sel_rows = self.GetSelectedRows() 305 306 selected_cells = [] 307 308 # individually selected cells (ctrl-click) 309 selected_cells += self.GetSelectedCells() 310 311 # selected rows 312 selected_cells += list ( 313 (row, col) 314 for row in sel_rows 315 for col in xrange(self.GetNumberCols()) 316 ) 317 318 # selected columns 319 selected_cells += list ( 320 (row, col) 321 for row in xrange(self.GetNumberRows()) 322 for col in sel_cols 323 ) 324 325 # selection blocks 326 for top_left, bottom_right in zip(self.GetSelectionBlockTopLeft(), self.GetSelectionBlockBottomRight()): 327 selected_cells += [ 328 (row, col) 329 for row in xrange(top_left[0], bottom_right[0] + 1) 330 for col in xrange(top_left[1], bottom_right[1] + 1) 331 ] 332 333 return set(selected_cells)
334 #------------------------------------------------------------
335 - def select_cells(self, unsigned_only=False, accountables_only=False, keep_preselections=False):
336 """Select a range of cells according to criteria. 337 338 unsigned_only: include only those which are not signed at all yet 339 accountable_only: include only those for which the current user is responsible 340 keep_preselections: broaden (rather than replace) the range of selected cells 341 342 Combinations are powerful ! 343 """ 344 wx.BeginBusyCursor() 345 self.BeginBatch() 346 347 if not keep_preselections: 348 self.ClearSelection() 349 350 for col_idx in self.__cell_data.keys(): 351 for row_idx in self.__cell_data[col_idx].keys(): 352 # loop over results in cell and only include 353 # this multi-value cells that are not ambigous 354 do_not_include = False 355 for result in self.__cell_data[col_idx][row_idx]: 356 if unsigned_only: 357 if result['reviewed']: 358 do_not_include = True 359 break 360 if accountables_only: 361 if not result['you_are_responsible']: 362 do_not_include = True 363 break 364 if do_not_include: 365 continue 366 367 self.SelectBlock(row_idx, col_idx, row_idx, col_idx, addToSelected = True) 368 369 self.EndBatch() 370 wx.EndBusyCursor()
371 #------------------------------------------------------------
372 - def repopulate_grid(self):
373 374 self.empty_grid() 375 if self.__patient is None: 376 return 377 378 emr = self.__patient.get_emr() 379 380 self.__row_label_data = emr.get_test_types_for_results() 381 test_type_labels = [ u'%s (%s)' % (test['unified_abbrev'], test['unified_name']) for test in self.__row_label_data ] 382 if len(test_type_labels) == 0: 383 return 384 385 test_date_labels = [ date[0].strftime(self.__date_format) for date in emr.get_dates_for_results() ] 386 results = emr.get_test_results_by_date() 387 388 self.BeginBatch() 389 390 # rows 391 self.AppendRows(numRows = len(test_type_labels)) 392 for row_idx in range(len(test_type_labels)): 393 self.SetRowLabelValue(row_idx, test_type_labels[row_idx]) 394 395 # columns 396 self.AppendCols(numCols = len(test_date_labels)) 397 for date_idx in range(len(test_date_labels)): 398 self.SetColLabelValue(date_idx, test_date_labels[date_idx]) 399 400 # cell values (list of test results) 401 for result in results: 402 row = test_type_labels.index(u'%s (%s)' % (result['unified_abbrev'], result['unified_name'])) 403 col = test_date_labels.index(result['clin_when'].strftime(self.__date_format)) 404 405 try: 406 self.__cell_data[col] 407 except KeyError: 408 self.__cell_data[col] = {} 409 410 # the tooltip always shows the youngest sub result details 411 if self.__cell_data[col].has_key(row): 412 self.__cell_data[col][row].append(result) 413 self.__cell_data[col][row].sort(key = lambda x: x['clin_when'], reverse = True) 414 else: 415 self.__cell_data[col][row] = [result] 416 417 # rebuild cell display string 418 vals2display = [] 419 for sub_result in self.__cell_data[col][row]: 420 421 # is the sub_result technically abnormal ? 422 ind = gmTools.coalesce(sub_result['abnormality_indicator'], u'').strip() 423 if ind != u'': 424 lab_abnormality_indicator = u' (%s)' % ind[:3] 425 else: 426 lab_abnormality_indicator = u'' 427 # - if noone reviewed - use what the lab thinks 428 if sub_result['is_technically_abnormal'] is None: 429 abnormality_indicator = lab_abnormality_indicator 430 # - if someone reviewed and decreed normality - use that 431 elif sub_result['is_technically_abnormal'] is False: 432 abnormality_indicator = u'' 433 # - if someone reviewed and decreed abnormality ... 434 else: 435 # ... invent indicator if the lab did't use one 436 if lab_abnormality_indicator == u'': 437 # FIXME: calculate from min/max/range 438 abnormality_indicator = u' (%s)' % gmTools.u_plus_minus 439 # ... else use indicator the lab used 440 else: 441 abnormality_indicator = lab_abnormality_indicator 442 443 # is the sub_result relevant clinically ? 444 # FIXME: take into account primary_GP once we support that 445 sub_result_relevant = sub_result['is_clinically_relevant'] 446 if sub_result_relevant is None: 447 # FIXME: calculate from clinical range 448 sub_result_relevant = False 449 450 missing_review = False 451 # warn on missing review if 452 # a) no review at all exists or 453 if not sub_result['reviewed']: 454 missing_review = True 455 # b) there is a review but 456 else: 457 # current user is reviewer and hasn't reviewed 458 if sub_result['you_are_responsible'] and not sub_result['review_by_you']: 459 missing_review = True 460 461 # can we display the full sub_result length ? 462 if len(sub_result['unified_val']) > 8: 463 tmp = u'%.7s%s' % (sub_result['unified_val'][:7], gmTools.u_ellipsis) 464 else: 465 tmp = u'%.8s' % sub_result['unified_val'][:8] 466 467 # abnormal ? 468 tmp = u'%s%.6s' % (tmp, abnormality_indicator) 469 470 # is there a comment ? 471 has_sub_result_comment = gmTools.coalesce ( 472 gmTools.coalesce(sub_result['note_test_org'], sub_result['comment']), 473 u'' 474 ).strip() != u'' 475 if has_sub_result_comment: 476 tmp = u'%s %s' % (tmp, gmTools.u_ellipsis) 477 478 # lacking a review ? 479 if missing_review: 480 tmp = u'%s %s' % (tmp, gmTools.u_writing_hand) 481 482 # part of a multi-result cell ? 483 if len(self.__cell_data[col][row]) > 1: 484 tmp = u'%s %s' % (sub_result['clin_when'].strftime('%H:%M'), tmp) 485 486 vals2display.append(tmp) 487 488 self.SetCellValue(row, col, u'\n'.join(vals2display)) 489 self.SetCellAlignment(row, col, horiz = wx.ALIGN_RIGHT, vert = wx.ALIGN_CENTRE) 490 # font = self.GetCellFont(row, col) 491 # if not font.IsFixedWidth(): 492 # font.SetFamily(family = wx.FONTFAMILY_MODERN) 493 # FIXME: what about partial sub results being relevant ?? 494 if sub_result_relevant: 495 font = self.GetCellFont(row, col) 496 self.SetCellTextColour(row, col, 'firebrick') 497 font.SetWeight(wx.FONTWEIGHT_BOLD) 498 self.SetCellFont(row, col, font) 499 # self.SetCellFont(row, col, font) 500 501 self.AutoSize() 502 self.EndBatch() 503 return
504 #------------------------------------------------------------
505 - def empty_grid(self):
506 self.BeginBatch() 507 self.ClearGrid() 508 # Windows cannot do nothing, it rather decides to assert() 509 # on thinking it is supposed to do nothing 510 if self.GetNumberRows() > 0: 511 self.DeleteRows(pos = 0, numRows = self.GetNumberRows()) 512 if self.GetNumberCols() > 0: 513 self.DeleteCols(pos = 0, numCols = self.GetNumberCols()) 514 self.EndBatch() 515 self.__cell_data = {} 516 self.__row_label_data = []
517 #------------------------------------------------------------
518 - def get_row_tooltip(self, row=None):
519 # display test info (unified, which tests are grouped, which panels they belong to 520 # include details about test types included, 521 # most recent value in this row, etc 522 # test_details, td_idx = emr.get_test_types_details() 523 524 # sometimes, for some reason, there is no row and 525 # wxPython still tries to find a tooltip for it 526 try: 527 tt = self.__row_label_data[row] 528 except IndexError: 529 return u' ' 530 531 tip = u'' 532 tip += _('Details about %s (%s)%s\n') % (tt['unified_name'], tt['unified_abbrev'], gmTools.coalesce(tt['unified_loinc'], u'', u' [%s]')) 533 tip += u'\n' 534 tip += _('Meta type:\n') 535 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']) 536 tip += gmTools.coalesce(tt['conversion_unit'], u'', _(' Conversion unit: %s\n')) 537 tip += gmTools.coalesce(tt['comment_meta'], u'', _(' Comment: %s\n')) 538 tip += u'\n' 539 tip += _('Test type:\n') 540 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']) 541 tip += gmTools.coalesce(tt['comment_tt'], u'', _(' Comment: %s\n')) 542 tip += gmTools.coalesce(tt['code_tt'], u'', _(' Code: %s\n')) 543 tip += gmTools.coalesce(tt['coding_system_tt'], u'', _(' Code: %s\n')) 544 result = tt.get_most_recent_result(pk_patient = self.__patient.ID) 545 if result is not None: 546 tip += u'\n' 547 tip += _('Most recent result:\n') 548 tip += _(' %s: %s%s%s') % ( 549 result['clin_when'].strftime('%Y-%m-%d'), 550 result['unified_val'], 551 gmTools.coalesce(result['val_unit'], u'', u' %s'), 552 gmTools.coalesce(result['abnormality_indicator'], u'', u' (%s)') 553 ) 554 555 return tip
556 #------------------------------------------------------------
557 - def get_cell_tooltip(self, col=None, row=None):
558 # FIXME: add panel/battery, request details 559 560 try: 561 d = self.__cell_data[col][row] 562 except KeyError: 563 # FIXME: maybe display the most recent or when the most recent was ? 564 d = None 565 566 if d is None: 567 return u' ' 568 569 is_multi_cell = False 570 if len(d) > 1: 571 is_multi_cell = True 572 573 d = d[0] 574 575 has_normal_min_or_max = (d['val_normal_min'] is not None) or (d['val_normal_max'] is not None) 576 if has_normal_min_or_max: 577 normal_min_max = u'%s - %s' % ( 578 gmTools.coalesce(d['val_normal_min'], u'?'), 579 gmTools.coalesce(d['val_normal_max'], u'?') 580 ) 581 else: 582 normal_min_max = u'' 583 584 has_clinical_min_or_max = (d['val_target_min'] is not None) or (d['val_target_max'] is not None) 585 if has_clinical_min_or_max: 586 clinical_min_max = u'%s - %s' % ( 587 gmTools.coalesce(d['val_target_min'], u'?'), 588 gmTools.coalesce(d['val_target_max'], u'?') 589 ) 590 else: 591 clinical_min_max = u'' 592 593 # header 594 if is_multi_cell: 595 tt = _(u'Measurement details of most recent (topmost) result: \n') 596 else: 597 tt = _(u'Measurement details: \n') 598 599 # basics 600 tt += u' ' + _(u'Date: %s\n') % d['clin_when'].strftime('%c').decode(gmI18N.get_encoding()) 601 tt += u' ' + _(u'Type: "%(name)s" (%(code)s) [#%(pk_type)s]\n') % ({ 602 'name': d['name_tt'], 603 'code': d['code_tt'], 604 'pk_type': d['pk_test_type'] 605 }) 606 tt += u' ' + _(u'Result: %(val)s%(unit)s%(ind)s [#%(pk_result)s]\n') % ({ 607 'val': d['unified_val'], 608 'unit': gmTools.coalesce(d['val_unit'], u'', u' %s'), 609 'ind': gmTools.coalesce(d['abnormality_indicator'], u'', u' (%s)'), 610 'pk_result': d['pk_test_result'] 611 }) 612 tmp = (u'%s%s' % ( 613 gmTools.coalesce(d['name_test_org'], u''), 614 gmTools.coalesce(d['contact_test_org'], u'', u' (%s)'), 615 )).strip() 616 if tmp != u'': 617 tt += u' ' + _(u'Source: %s\n') % tmp 618 tt += u'\n' 619 620 # clinical evaluation 621 norm_eval = None 622 if d['val_num'] is not None: 623 # 1) normal range 624 # lowered ? 625 if (d['val_normal_min'] is not None) and (d['val_num'] < d['val_normal_min']): 626 try: 627 percent = (d['val_num'] * 100) / d['val_normal_min'] 628 except ZeroDivisionError: 629 percent = None 630 if percent is not None: 631 if percent < 6: 632 norm_eval = _(u'%.1f %% of the normal lower limit') % percent 633 else: 634 norm_eval = _(u'%.0f %% of the normal lower limit') % percent 635 # raised ? 636 if (d['val_normal_max'] is not None) and (d['val_num'] > d['val_normal_max']): 637 try: 638 x_times = d['val_num'] / d['val_normal_max'] 639 except ZeroDivisionError: 640 x_times = None 641 if x_times is not None: 642 if x_times < 10: 643 norm_eval = _(u'%.1f times the normal upper limit') % x_times 644 else: 645 norm_eval = _(u'%.0f times the normal upper limit') % x_times 646 if norm_eval is not None: 647 tt += u' (%s)\n' % norm_eval 648 # #------------------------------------- 649 # # this idea was shot down on the list 650 # #------------------------------------- 651 # # bandwidth of deviation 652 # if None not in [d['val_normal_min'], d['val_normal_max']]: 653 # normal_width = d['val_normal_max'] - d['val_normal_min'] 654 # deviation_from_normal_range = None 655 # # below ? 656 # if d['val_num'] < d['val_normal_min']: 657 # deviation_from_normal_range = d['val_normal_min'] - d['val_num'] 658 # # above ? 659 # elif d['val_num'] > d['val_normal_max']: 660 # deviation_from_normal_range = d['val_num'] - d['val_normal_max'] 661 # if deviation_from_normal_range is None: 662 # try: 663 # times_deviation = deviation_from_normal_range / normal_width 664 # except ZeroDivisionError: 665 # times_deviation = None 666 # if times_deviation is not None: 667 # if times_deviation < 10: 668 # tt += u' (%s)\n' % _(u'deviates by %.1f times of the normal range') % times_deviation 669 # else: 670 # tt += u' (%s)\n' % _(u'deviates by %.0f times of the normal range') % times_deviation 671 # #------------------------------------- 672 673 # 2) clinical target range 674 norm_eval = None 675 # lowered ? 676 if (d['val_target_min'] is not None) and (d['val_num'] < d['val_target_min']): 677 try: 678 percent = (d['val_num'] * 100) / d['val_target_min'] 679 except ZeroDivisionError: 680 percent = None 681 if percent is not None: 682 if percent < 6: 683 norm_eval = _(u'%.1f %% of the target lower limit') % percent 684 else: 685 norm_eval = _(u'%.0f %% of the target lower limit') % percent 686 # raised ? 687 if (d['val_target_max'] is not None) and (d['val_num'] > d['val_target_max']): 688 try: 689 x_times = d['val_num'] / d['val_target_max'] 690 except ZeroDivisionError: 691 x_times = None 692 if x_times is not None: 693 if x_times < 10: 694 norm_eval = _(u'%.1f times the target upper limit') % x_times 695 else: 696 norm_eval = _(u'%.0f times the target upper limit') % x_times 697 if norm_eval is not None: 698 tt += u' (%s)\n' % norm_eval 699 # #------------------------------------- 700 # # this idea was shot down on the list 701 # #------------------------------------- 702 # # bandwidth of deviation 703 # if None not in [d['val_target_min'], d['val_target_max']]: 704 # normal_width = d['val_target_max'] - d['val_target_min'] 705 # deviation_from_target_range = None 706 # # below ? 707 # if d['val_num'] < d['val_target_min']: 708 # deviation_from_target_range = d['val_target_min'] - d['val_num'] 709 # # above ? 710 # elif d['val_num'] > d['val_target_max']: 711 # deviation_from_target_range = d['val_num'] - d['val_target_max'] 712 # if deviation_from_target_range is None: 713 # try: 714 # times_deviation = deviation_from_target_range / normal_width 715 # except ZeroDivisionError: 716 # times_deviation = None 717 # if times_deviation is not None: 718 # if times_deviation < 10: 719 # tt += u' (%s)\n' % _(u'deviates by %.1f times of the target range') % times_deviation 720 # else: 721 # tt += u' (%s)\n' % _(u'deviates by %.0f times of the target range') % times_deviation 722 # #------------------------------------- 723 724 # ranges 725 tt += u' ' + _(u'Standard normal range: %(norm_min_max)s%(norm_range)s \n') % ({ 726 'norm_min_max': normal_min_max, 727 'norm_range': gmTools.coalesce ( 728 d['val_normal_range'], 729 u'', 730 gmTools.bool2subst ( 731 has_normal_min_or_max, 732 u' / %s', 733 u'%s' 734 ) 735 ) 736 }) 737 if d['norm_ref_group'] is not None: 738 tt += u' ' + _(u'Reference group: %s\n') % d['norm_ref_group'] 739 tt += u' ' + _(u'Clinical target range: %(clin_min_max)s%(clin_range)s \n') % ({ 740 'clin_min_max': clinical_min_max, 741 'clin_range': gmTools.coalesce ( 742 d['val_target_range'], 743 u'', 744 gmTools.bool2subst ( 745 has_clinical_min_or_max, 746 u' / %s', 747 u'%s' 748 ) 749 ) 750 }) 751 752 # metadata 753 if d['comment'] is not None: 754 tt += u' ' + _(u'Doc: %s\n') % _(u'\n Doc: ').join(d['comment'].split(u'\n')) 755 if d['note_test_org'] is not None: 756 tt += u' ' + _(u'Lab: %s\n') % _(u'\n Lab: ').join(d['note_test_org'].split(u'\n')) 757 tt += u' ' + _(u'Episode: %s\n') % d['episode'] 758 if d['health_issue'] is not None: 759 tt += u' ' + _(u'Issue: %s\n') % d['health_issue'] 760 if d['material'] is not None: 761 tt += u' ' + _(u'Material: %s\n') % d['material'] 762 if d['material_detail'] is not None: 763 tt += u' ' + _(u'Details: %s\n') % d['material_detail'] 764 tt += u'\n' 765 766 # review 767 if d['reviewed']: 768 review = d['last_reviewed'].strftime('%c').decode(gmI18N.get_encoding()) 769 else: 770 review = _('not yet') 771 tt += _(u'Signed (%(sig_hand)s): %(reviewed)s\n') % ({ 772 'sig_hand': gmTools.u_writing_hand, 773 'reviewed': review 774 }) 775 tt += u' ' + _(u'Responsible clinician: %s\n') % gmTools.bool2subst(d['you_are_responsible'], _('you'), d['responsible_reviewer']) 776 if d['reviewed']: 777 tt += u' ' + _(u'Last reviewer: %(reviewer)s\n') % ({'reviewer': gmTools.bool2subst(d['review_by_you'], _('you'), gmTools.coalesce(d['last_reviewer'], u'?'))}) 778 tt += u' ' + _(u' Technically abnormal: %(abnormal)s\n') % ({'abnormal': gmTools.bool2subst(d['is_technically_abnormal'], _('yes'), _('no'), u'?')}) 779 tt += u' ' + _(u' Clinically relevant: %(relevant)s\n') % ({'relevant': gmTools.bool2subst(d['is_clinically_relevant'], _('yes'), _('no'), u'?')}) 780 if d['review_comment'] is not None: 781 tt += u' ' + _(u' Comment: %s\n') % d['review_comment'].strip() 782 tt += u'\n' 783 784 # type 785 tt += _(u'Test type details:\n') 786 tt += u' ' + _(u'Grouped under "%(name_meta)s" (%(abbrev_meta)s) [#%(pk_u_type)s]\n') % ({ 787 'name_meta': gmTools.coalesce(d['name_meta'], u''), 788 'abbrev_meta': gmTools.coalesce(d['abbrev_meta'], u''), 789 'pk_u_type': d['pk_meta_test_type'] 790 }) 791 if d['comment_tt'] is not None: 792 tt += u' ' + _(u'Type comment: %s\n') % _(u'\n Type comment:').join(d['comment_tt'].split(u'\n')) 793 if d['comment_meta'] is not None: 794 tt += u' ' + _(u'Group comment: %s\n') % _(u'\n Group comment: ').join(d['comment_meta'].split(u'\n')) 795 tt += u'\n' 796 797 tt += _(u'Revisions: %(row_ver)s, last %(mod_when)s by %(mod_by)s.') % ({ 798 'row_ver': d['row_version'], 799 'mod_when': d['modified_when'].strftime('%c').decode(gmI18N.get_encoding()), 800 'mod_by': d['modified_by'] 801 }) 802 803 return tt
804 #------------------------------------------------------------ 805 # internal helpers 806 #------------------------------------------------------------
807 - def __init_ui(self):
808 self.CreateGrid(0, 1) 809 self.EnableEditing(0) 810 self.EnableDragGridSize(1) 811 812 # setting this screws up the labels: they are cut off and displaced 813 #self.SetColLabelAlignment(wx.ALIGN_CENTER, wx.ALIGN_BOTTOM) 814 815 #self.SetRowLabelSize(wx.GRID_AUTOSIZE) # starting with 2.8.8 816 self.SetRowLabelSize(150) 817 self.SetRowLabelAlignment(horiz = wx.ALIGN_LEFT, vert = wx.ALIGN_CENTRE) 818 819 # add link to left upper corner 820 dbcfg = gmCfg.cCfgSQL() 821 url = dbcfg.get2 ( 822 option = u'external.urls.measurements_encyclopedia', 823 workplace = gmSurgery.gmCurrentPractice().active_workplace, 824 bias = 'user', 825 default = u'http://www.laborlexikon.de' 826 ) 827 828 self.__WIN_corner = self.GetGridCornerLabelWindow() # a wx.Window instance 829 830 LNK_lab = wx.lib.hyperlink.HyperLinkCtrl ( 831 self.__WIN_corner, 832 -1, 833 label = _('Reference'), 834 style = wx.HL_DEFAULT_STYLE # wx.TE_READONLY|wx.TE_CENTRE| wx.NO_BORDER | 835 ) 836 LNK_lab.SetURL(url) 837 LNK_lab.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_BACKGROUND)) 838 LNK_lab.SetToolTipString(_( 839 'Navigate to an encyclopedia of measurements\n' 840 'and test methods on the web.\n' 841 '\n' 842 ' <%s>' 843 ) % url) 844 845 SZR_inner = wx.BoxSizer(wx.HORIZONTAL) 846 SZR_inner.Add((20, 20), 1, wx.EXPAND, 0) # spacer 847 SZR_inner.Add(LNK_lab, 0, wx.ALIGN_CENTER_VERTICAL, 0) #wx.ALIGN_CENTER wx.EXPAND 848 SZR_inner.Add((20, 20), 1, wx.EXPAND, 0) # spacer 849 850 SZR_corner = wx.BoxSizer(wx.VERTICAL) 851 SZR_corner.Add((20, 20), 1, wx.EXPAND, 0) # spacer 852 SZR_corner.AddWindow(SZR_inner, 0, wx.EXPAND) # inner sizer with centered hyperlink 853 SZR_corner.Add((20, 20), 1, wx.EXPAND, 0) # spacer 854 855 self.__WIN_corner.SetSizer(SZR_corner) 856 SZR_corner.Fit(self.__WIN_corner)
857 #------------------------------------------------------------
858 - def __resize_corner_window(self, evt):
859 self.__WIN_corner.Layout()
860 #------------------------------------------------------------
861 - def __cells_to_data(self, cells=None, exclude_multi_cells=False, auto_include_multi_cells=False):
862 """List of <cells> must be in row / col order.""" 863 data = [] 864 for row, col in cells: 865 try: 866 # cell data is stored col / row 867 data_list = self.__cell_data[col][row] 868 except KeyError: 869 continue 870 871 if len(data_list) == 1: 872 data.append(data_list[0]) 873 continue 874 875 if exclude_multi_cells: 876 gmDispatcher.send(signal = u'statustext', msg = _('Excluding multi-result field from further processing.')) 877 continue 878 879 if auto_include_multi_cells: 880 data.extend(data_list) 881 continue 882 883 data_to_include = self.__get_choices_from_multi_cell(cell_data = data_list) 884 if data_to_include is None: 885 continue 886 data.extend(data_to_include) 887 888 return data
889 #------------------------------------------------------------
890 - def __get_choices_from_multi_cell(self, cell_data=None, single_selection=False):
891 data = gmListWidgets.get_choices_from_list ( 892 parent = self, 893 msg = _( 894 'Your selection includes a field with multiple results.\n' 895 '\n' 896 'Please select the individual results you want to work on:' 897 ), 898 caption = _('Selecting test results'), 899 choices = [ [d['clin_when'], d['unified_abbrev'], d['unified_name'], d['unified_val']] for d in cell_data ], 900 columns = [_('Date / Time'), _('Code'), _('Test'), _('Result')], 901 data = cell_data, 902 single_selection = single_selection 903 ) 904 return data
905 #------------------------------------------------------------ 906 # event handling 907 #------------------------------------------------------------
908 - def __register_events(self):
909 # dynamic tooltips: GridWindow, GridRowLabelWindow, GridColLabelWindow, GridCornerLabelWindow 910 self.GetGridWindow().Bind(wx.EVT_MOTION, self.__on_mouse_over_cells) 911 self.GetGridRowLabelWindow().Bind(wx.EVT_MOTION, self.__on_mouse_over_row_labels) 912 #self.GetGridColLabelWindow().Bind(wx.EVT_MOTION, self.__on_mouse_over_col_labels) 913 914 # sizing left upper corner window 915 self.Bind(wx.EVT_SIZE, self.__resize_corner_window) 916 917 # editing cells 918 self.Bind(wx.grid.EVT_GRID_CELL_LEFT_DCLICK, self.__on_cell_left_dclicked)
919 #------------------------------------------------------------
920 - def __on_cell_left_dclicked(self, evt):
921 col = evt.GetCol() 922 row = evt.GetRow() 923 924 # empty cell, perhaps ? 925 try: 926 self.__cell_data[col][row] 927 except KeyError: 928 # FIXME: invoke editor for adding value for day of that column 929 # FIMXE: and test of that row 930 return 931 932 if len(self.__cell_data[col][row]) > 1: 933 data = self.__get_choices_from_multi_cell(cell_data = self.__cell_data[col][row], single_selection = True) 934 else: 935 data = self.__cell_data[col][row][0] 936 937 if data is None: 938 return 939 940 edit_measurement(parent = self, measurement = data, single_entry = True)
941 #------------------------------------------------------------ 942 # def OnMouseMotionRowLabel(self, evt): 943 # x, y = self.CalcUnscrolledPosition(evt.GetPosition()) 944 # row = self.YToRow(y) 945 # label = self.table().GetRowHelpValue(row) 946 # self.GetGridRowLabelWindow().SetToolTipString(label or "") 947 # evt.Skip()
948 - def __on_mouse_over_row_labels(self, evt):
949 950 # Use CalcUnscrolledPosition() to get the mouse position within the 951 # entire grid including what's offscreen 952 x, y = self.CalcUnscrolledPosition(evt.GetX(), evt.GetY()) 953 954 row = self.YToRow(y) 955 956 if self.__prev_label_row == row: 957 return 958 959 self.__prev_label_row == row 960 961 evt.GetEventObject().SetToolTipString(self.get_row_tooltip(row = row))
962 #------------------------------------------------------------ 963 # def OnMouseMotionColLabel(self, evt): 964 # x, y = self.CalcUnscrolledPosition(evt.GetPosition()) 965 # col = self.XToCol(x) 966 # label = self.table().GetColHelpValue(col) 967 # self.GetGridColLabelWindow().SetToolTipString(label or "") 968 # evt.Skip() 969 #------------------------------------------------------------
970 - def __on_mouse_over_cells(self, evt):
971 """Calculate where the mouse is and set the tooltip dynamically.""" 972 973 # Use CalcUnscrolledPosition() to get the mouse position within the 974 # entire grid including what's offscreen 975 x, y = self.CalcUnscrolledPosition(evt.GetX(), evt.GetY()) 976 977 # use this logic to prevent tooltips outside the actual cells 978 # apply to GetRowSize, too 979 # tot = 0 980 # for col in xrange(self.NumberCols): 981 # tot += self.GetColSize(col) 982 # if xpos <= tot: 983 # self.tool_tip.Tip = 'Tool tip for Column %s' % ( 984 # self.GetColLabelValue(col)) 985 # break 986 # else: # mouse is in label area beyond the right-most column 987 # self.tool_tip.Tip = '' 988 989 row, col = self.XYToCell(x, y) 990 991 if (row == self.__prev_row) and (col == self.__prev_col): 992 return 993 994 self.__prev_row = row 995 self.__prev_col = col 996 997 evt.GetEventObject().SetToolTipString(self.get_cell_tooltip(col=col, row=row))
998 #------------------------------------------------------------ 999 # properties 1000 #------------------------------------------------------------
1001 - def _set_patient(self, patient):
1002 self.__patient = patient 1003 self.repopulate_grid()
1004 1005 patient = property(lambda x:x, _set_patient)
1006 #================================================================
1007 -class cMeasurementsPnl(wxgMeasurementsPnl.wxgMeasurementsPnl, gmRegetMixin.cRegetOnPaintMixin):
1008 1009 """Panel holding a grid with lab data. Used as notebook page.""" 1010
1011 - def __init__(self, *args, **kwargs):
1012 1013 wxgMeasurementsPnl.wxgMeasurementsPnl.__init__(self, *args, **kwargs) 1014 gmRegetMixin.cRegetOnPaintMixin.__init__(self) 1015 self.__init_ui() 1016 self.__register_interests()
1017 #-------------------------------------------------------- 1018 # event handling 1019 #--------------------------------------------------------
1020 - def __register_interests(self):
1021 gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._on_pre_patient_selection) 1022 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection) 1023 gmDispatcher.connect(signal = u'test_result_mod_db', receiver = self._schedule_data_reget) 1024 gmDispatcher.connect(signal = u'reviewed_test_results_mod_db', receiver = self._schedule_data_reget)
1025 #--------------------------------------------------------
1026 - def _on_post_patient_selection(self):
1027 wx.CallAfter(self.__on_post_patient_selection)
1028 #--------------------------------------------------------
1029 - def __on_post_patient_selection(self):
1030 self._schedule_data_reget()
1031 #--------------------------------------------------------
1032 - def _on_pre_patient_selection(self):
1033 wx.CallAfter(self.__on_pre_patient_selection)
1034 #--------------------------------------------------------
1035 - def __on_pre_patient_selection(self):
1036 self.data_grid.patient = None
1037 #--------------------------------------------------------
1038 - def _on_review_button_pressed(self, evt):
1039 self.PopupMenu(self.__action_button_popup)
1040 #--------------------------------------------------------
1041 - def _on_select_button_pressed(self, evt):
1042 if self._RBTN_my_unsigned.GetValue() is True: 1043 self.data_grid.select_cells(unsigned_only = True, accountables_only = True, keep_preselections = False) 1044 elif self._RBTN_all_unsigned.GetValue() is True: 1045 self.data_grid.select_cells(unsigned_only = True, accountables_only = False, keep_preselections = False)
1046 #--------------------------------------------------------
1047 - def __on_sign_current_selection(self, evt):
1048 self.data_grid.sign_current_selection()
1049 #--------------------------------------------------------
1050 - def __on_plot_current_selection(self, evt):
1051 self.data_grid.plot_current_selection()
1052 #--------------------------------------------------------
1053 - def __on_delete_current_selection(self, evt):
1054 self.data_grid.delete_current_selection()
1055 #-------------------------------------------------------- 1056 # internal API 1057 #--------------------------------------------------------
1058 - def __init_ui(self):
1059 self.__action_button_popup = wx.Menu(title = _('Act on selected results')) 1060 1061 menu_id = wx.NewId() 1062 self.__action_button_popup.AppendItem(wx.MenuItem(self.__action_button_popup, menu_id, _('Review and &sign'))) 1063 wx.EVT_MENU(self.__action_button_popup, menu_id, self.__on_sign_current_selection) 1064 1065 menu_id = wx.NewId() 1066 self.__action_button_popup.AppendItem(wx.MenuItem(self.__action_button_popup, menu_id, _('Plot'))) 1067 wx.EVT_MENU(self.__action_button_popup, menu_id, self.__on_plot_current_selection) 1068 1069 menu_id = wx.NewId() 1070 self.__action_button_popup.AppendItem(wx.MenuItem(self.__action_button_popup, menu_id, _('Export to &file'))) 1071 #wx.EVT_MENU(self.__action_button_popup, menu_id, self.data_grid.current_selection_to_file) 1072 self.__action_button_popup.Enable(id = menu_id, enable = False) 1073 1074 menu_id = wx.NewId() 1075 self.__action_button_popup.AppendItem(wx.MenuItem(self.__action_button_popup, menu_id, _('Export to &clipboard'))) 1076 #wx.EVT_MENU(self.__action_button_popup, menu_id, self.data_grid.current_selection_to_clipboard) 1077 self.__action_button_popup.Enable(id = menu_id, enable = False) 1078 1079 menu_id = wx.NewId() 1080 self.__action_button_popup.AppendItem(wx.MenuItem(self.__action_button_popup, menu_id, _('&Delete'))) 1081 wx.EVT_MENU(self.__action_button_popup, menu_id, self.__on_delete_current_selection)
1082 #-------------------------------------------------------- 1083 # reget mixin API 1084 #--------------------------------------------------------
1085 - def _populate_with_data(self):
1086 """Populate fields in pages with data from model.""" 1087 pat = gmPerson.gmCurrentPatient() 1088 if pat.connected: 1089 self.data_grid.patient = pat 1090 else: 1091 self.data_grid.patient = None 1092 return True
1093 #================================================================ 1094 # editing widgets 1095 #================================================================
1096 -class cMeasurementsReviewDlg(wxgMeasurementsReviewDlg.wxgMeasurementsReviewDlg):
1097
1098 - def __init__(self, *args, **kwargs):
1099 1100 try: 1101 tests = kwargs['tests'] 1102 del kwargs['tests'] 1103 test_count = len(tests) 1104 try: del kwargs['test_count'] 1105 except KeyError: pass 1106 except KeyError: 1107 tests = None 1108 test_count = kwargs['test_count'] 1109 del kwargs['test_count'] 1110 1111 wxgMeasurementsReviewDlg.wxgMeasurementsReviewDlg.__init__(self, *args, **kwargs) 1112 1113 if tests is None: 1114 msg = _('%s results selected. Too many to list individually.') % test_count 1115 else: 1116 msg = ' // '.join ( 1117 [ u'%s: %s %s (%s)' % ( 1118 t['unified_abbrev'], 1119 t['unified_val'], 1120 t['val_unit'], 1121 t['clin_when'].strftime('%x').decode(gmI18N.get_encoding()) 1122 ) for t in tests 1123 ] 1124 ) 1125 1126 self._LBL_tests.SetLabel(msg) 1127 1128 if test_count == 1: 1129 self._TCTRL_comment.Enable(True) 1130 self._TCTRL_comment.SetValue(gmTools.coalesce(tests[0]['review_comment'], u'')) 1131 if tests[0]['you_are_responsible']: 1132 self._CHBOX_responsible.Enable(False) 1133 1134 self.Fit()
1135 #-------------------------------------------------------- 1136 # event handling 1137 #--------------------------------------------------------
1138 - def _on_signoff_button_pressed(self, evt):
1139 if self.IsModal(): 1140 self.EndModal(wx.ID_APPLY) 1141 else: 1142 self.Close()
1143 #================================================================
1144 -class cMeasurementEditAreaPnl(wxgMeasurementEditAreaPnl.wxgMeasurementEditAreaPnl, gmEditArea.cGenericEditAreaMixin):
1145 """This edit area saves *new* measurements into the active patient only.""" 1146
1147 - def __init__(self, *args, **kwargs):
1148 1149 try: 1150 self.__default_date = kwargs['date'] 1151 del kwargs['date'] 1152 except KeyError: 1153 self.__default_date = None 1154 1155 wxgMeasurementEditAreaPnl.wxgMeasurementEditAreaPnl.__init__(self, *args, **kwargs) 1156 gmEditArea.cGenericEditAreaMixin.__init__(self) 1157 1158 self.__register_interests() 1159 1160 self.successful_save_msg = _('Successfully saved measurement.')
1161 #-------------------------------------------------------- 1162 # generic edit area mixin API 1163 #--------------------------------------------------------
1164 - def _refresh_as_new(self):
1165 self._PRW_test.SetText(u'', None, True) 1166 self.__refresh_loinc_info() 1167 self.__update_units_context() 1168 self._TCTRL_result.SetValue(u'') 1169 self._PRW_units.SetText(u'', None, True) 1170 self._PRW_abnormality_indicator.SetText(u'', None, True) 1171 if self.__default_date is None: 1172 self._DPRW_evaluated.SetData(data = pyDT.datetime.now(tz = gmDateTime.gmCurrentLocalTimezone)) 1173 else: 1174 self._DPRW_evaluated.SetData(data = None) 1175 self._TCTRL_note_test_org.SetValue(u'') 1176 self._PRW_intended_reviewer.SetData() 1177 self._PRW_problem.SetData() 1178 self._TCTRL_narrative.SetValue(u'') 1179 self._CHBOX_review.SetValue(False) 1180 self._CHBOX_abnormal.SetValue(False) 1181 self._CHBOX_relevant.SetValue(False) 1182 self._CHBOX_abnormal.Enable(False) 1183 self._CHBOX_relevant.Enable(False) 1184 self._TCTRL_review_comment.SetValue(u'') 1185 self._TCTRL_normal_min.SetValue(u'') 1186 self._TCTRL_normal_max.SetValue(u'') 1187 self._TCTRL_normal_range.SetValue(u'') 1188 self._TCTRL_target_min.SetValue(u'') 1189 self._TCTRL_target_max.SetValue(u'') 1190 self._TCTRL_target_range.SetValue(u'') 1191 self._TCTRL_norm_ref_group.SetValue(u'') 1192 1193 self._PRW_test.SetFocus()
1194 #--------------------------------------------------------
1195 - def _refresh_from_existing(self):
1196 self._PRW_test.SetData(data = self.data['pk_test_type']) 1197 self.__refresh_loinc_info() 1198 self.__update_units_context() 1199 self._TCTRL_result.SetValue(self.data['unified_val']) 1200 self._PRW_units.SetText(self.data['val_unit'], self.data['val_unit'], True) 1201 self._PRW_abnormality_indicator.SetText ( 1202 gmTools.coalesce(self.data['abnormality_indicator'], u''), 1203 gmTools.coalesce(self.data['abnormality_indicator'], u''), 1204 True 1205 ) 1206 self._DPRW_evaluated.SetData(data = self.data['clin_when']) 1207 self._TCTRL_note_test_org.SetValue(gmTools.coalesce(self.data['note_test_org'], u'')) 1208 self._PRW_intended_reviewer.SetData(self.data['pk_intended_reviewer']) 1209 self._PRW_problem.SetData(self.data['pk_episode']) 1210 self._TCTRL_narrative.SetValue(gmTools.coalesce(self.data['comment'], u'')) 1211 self._CHBOX_review.SetValue(False) 1212 self._CHBOX_abnormal.SetValue(gmTools.coalesce(self.data['is_technically_abnormal'], False)) 1213 self._CHBOX_relevant.SetValue(gmTools.coalesce(self.data['is_clinically_relevant'], False)) 1214 self._CHBOX_abnormal.Enable(False) 1215 self._CHBOX_relevant.Enable(False) 1216 self._TCTRL_review_comment.SetValue(gmTools.coalesce(self.data['review_comment'], u'')) 1217 self._TCTRL_normal_min.SetValue(unicode(gmTools.coalesce(self.data['val_normal_min'], u''))) 1218 self._TCTRL_normal_max.SetValue(unicode(gmTools.coalesce(self.data['val_normal_max'], u''))) 1219 self._TCTRL_normal_range.SetValue(gmTools.coalesce(self.data['val_normal_range'], u'')) 1220 self._TCTRL_target_min.SetValue(unicode(gmTools.coalesce(self.data['val_target_min'], u''))) 1221 self._TCTRL_target_max.SetValue(unicode(gmTools.coalesce(self.data['val_target_max'], u''))) 1222 self._TCTRL_target_range.SetValue(gmTools.coalesce(self.data['val_target_range'], u'')) 1223 self._TCTRL_norm_ref_group.SetValue(gmTools.coalesce(self.data['norm_ref_group'], u'')) 1224 1225 self._TCTRL_result.SetFocus()
1226 #--------------------------------------------------------
1228 self._refresh_from_existing() 1229 1230 self._PRW_test.SetText(u'', None, True) 1231 self.__refresh_loinc_info() 1232 self.__update_units_context() 1233 self._TCTRL_result.SetValue(u'') 1234 self._PRW_units.SetText(u'', None, True) 1235 self._PRW_abnormality_indicator.SetText(u'', None, True) 1236 # self._DPRW_evaluated 1237 self._TCTRL_note_test_org.SetValue(u'') 1238 self._TCTRL_narrative.SetValue(u'') 1239 self._CHBOX_review.SetValue(False) 1240 self._CHBOX_abnormal.SetValue(False) 1241 self._CHBOX_relevant.SetValue(False) 1242 self._CHBOX_abnormal.Enable(False) 1243 self._CHBOX_relevant.Enable(False) 1244 self._TCTRL_review_comment.SetValue(u'') 1245 self._TCTRL_normal_min.SetValue(u'') 1246 self._TCTRL_normal_max.SetValue(u'') 1247 self._TCTRL_normal_range.SetValue(u'') 1248 self._TCTRL_target_min.SetValue(u'') 1249 self._TCTRL_target_max.SetValue(u'') 1250 self._TCTRL_target_range.SetValue(u'') 1251 self._TCTRL_norm_ref_group.SetValue(u'') 1252 1253 self._PRW_test.SetFocus()
1254 #--------------------------------------------------------
1255 - def _valid_for_save(self):
1256 1257 validity = True 1258 1259 if not self._DPRW_evaluated.is_valid_timestamp(): 1260 self._DPRW_evaluated.display_as_valid(False) 1261 validity = False 1262 else: 1263 self._DPRW_evaluated.display_as_valid(True) 1264 1265 if self._TCTRL_result.GetValue().strip() == u'': 1266 self._TCTRL_result.SetBackgroundColour(gmPhraseWheel.color_prw_invalid) 1267 validity = False 1268 else: 1269 self._TCTRL_result.SetBackgroundColour(gmPhraseWheel.color_prw_valid) 1270 1271 if self._PRW_problem.GetValue().strip() == u'': 1272 self._PRW_problem.display_as_valid(False) 1273 validity = False 1274 else: 1275 self._PRW_problem.display_as_valid(True) 1276 1277 if self._PRW_test.GetValue().strip() == u'': 1278 self._PRW_test.display_as_valid(False) 1279 validity = False 1280 else: 1281 self._PRW_test.display_as_valid(True) 1282 1283 if self._PRW_intended_reviewer.GetData() is None: 1284 self._PRW_intended_reviewer.display_as_valid(False) 1285 validity = False 1286 else: 1287 self._PRW_intended_reviewer.display_as_valid(True) 1288 1289 if self._PRW_units.GetValue().strip() == u'': 1290 self._PRW_units.display_as_valid(False) 1291 validity = False 1292 else: 1293 self._PRW_units.display_as_valid(True) 1294 1295 ctrls = [self._TCTRL_normal_min, self._TCTRL_normal_max, self._TCTRL_target_min, self._TCTRL_target_max] 1296 for widget in ctrls: 1297 val = widget.GetValue().strip() 1298 if val == u'': 1299 continue 1300 try: 1301 decimal.Decimal(val.replace(',', u'.', 1)) 1302 widget.SetBackgroundColour(gmPhraseWheel.color_prw_valid) 1303 except: 1304 widget.SetBackgroundColour(gmPhraseWheel.color_prw_invalid) 1305 validity = False 1306 1307 if validity is False: 1308 gmDispatcher.send(signal = 'statustext', msg = _('Cannot save result. Invalid or missing essential input.')) 1309 1310 return validity
1311 #--------------------------------------------------------
1312 - def _save_as_new(self):
1313 1314 emr = gmPerson.gmCurrentPatient().get_emr() 1315 1316 try: 1317 v_num = decimal.Decimal(self._TCTRL_result.GetValue().strip().replace(',', '.', 1)) 1318 v_al = None 1319 except: 1320 v_num = None 1321 v_al = self._TCTRL_result.GetValue().strip() 1322 1323 pk_type = self._PRW_test.GetData() 1324 if pk_type is None: 1325 tt = gmPathLab.create_measurement_type ( 1326 lab = None, 1327 abbrev = self._PRW_test.GetValue().strip(), 1328 name = self._PRW_test.GetValue().strip(), 1329 unit = gmTools.coalesce(self._PRW_units.GetData(), self._PRW_units.GetValue()).strip() 1330 ) 1331 pk_type = tt['pk_test_type'] 1332 1333 tr = emr.add_test_result ( 1334 episode = self._PRW_problem.GetData(can_create=True, is_open=False), 1335 type = pk_type, 1336 intended_reviewer = self._PRW_intended_reviewer.GetData(), 1337 val_num = v_num, 1338 val_alpha = v_al, 1339 unit = self._PRW_units.GetValue() 1340 ) 1341 1342 tr['clin_when'] = self._DPRW_evaluated.GetData().get_pydt() 1343 1344 ctrls = [ 1345 ('abnormality_indicator', self._PRW_abnormality_indicator), 1346 ('note_test_org', self._TCTRL_note_test_org), 1347 ('comment', self._TCTRL_narrative), 1348 ('val_normal_range', self._TCTRL_normal_range), 1349 ('val_target_range', self._TCTRL_target_range), 1350 ('norm_ref_group', self._TCTRL_norm_ref_group) 1351 ] 1352 for field, widget in ctrls: 1353 tr[field] = widget.GetValue().strip() 1354 1355 ctrls = [ 1356 ('val_normal_min', self._TCTRL_normal_min), 1357 ('val_normal_max', self._TCTRL_normal_max), 1358 ('val_target_min', self._TCTRL_target_min), 1359 ('val_target_max', self._TCTRL_target_max) 1360 ] 1361 for field, widget in ctrls: 1362 val = widget.GetValue().strip() 1363 if val == u'': 1364 tr[field] = None 1365 else: 1366 tr[field] = decimal.Decimal(val.replace(',', u'.', 1)) 1367 1368 tr.save_payload() 1369 1370 if self._CHBOX_review.GetValue() is True: 1371 tr.set_review ( 1372 technically_abnormal = self._CHBOX_abnormal.GetValue(), 1373 clinically_relevant = self._CHBOX_relevant.GetValue(), 1374 comment = gmTools.none_if(self._TCTRL_review_comment.GetValue().strip(), u''), 1375 make_me_responsible = False 1376 ) 1377 1378 self.data = tr 1379 1380 return True
1381 #--------------------------------------------------------
1382 - def _save_as_update(self):
1383 1384 success, result = gmTools.input2decimal(self._TCTRL_result.GetValue()) 1385 if success: 1386 v_num = result 1387 v_al = None 1388 else: 1389 v_num = None 1390 v_al = self._TCTRL_result.GetValue().strip() 1391 1392 pk_type = self._PRW_test.GetData() 1393 if pk_type is None: 1394 tt = gmPathLab.create_measurement_type ( 1395 lab = None, 1396 abbrev = self._PRW_test.GetValue().strip(), 1397 name = self._PRW_test.GetValue().strip(), 1398 unit = gmTools.none_if(self._PRW_units.GetValue().strip(), u'') 1399 ) 1400 pk_type = tt['pk_test_type'] 1401 1402 tr = self.data 1403 1404 tr['pk_episode'] = self._PRW_problem.GetData(can_create=True, is_open=False) 1405 tr['pk_test_type'] = pk_type 1406 tr['pk_intended_reviewer'] = self._PRW_intended_reviewer.GetData() 1407 tr['val_num'] = v_num 1408 tr['val_alpha'] = v_al 1409 tr['val_unit'] = gmTools.coalesce(self._PRW_units.GetData(), self._PRW_units.GetValue()).strip() 1410 tr['clin_when'] = self._DPRW_evaluated.GetData().get_pydt() 1411 1412 ctrls = [ 1413 ('abnormality_indicator', self._PRW_abnormality_indicator), 1414 ('note_test_org', self._TCTRL_note_test_org), 1415 ('comment', self._TCTRL_narrative), 1416 ('val_normal_range', self._TCTRL_normal_range), 1417 ('val_target_range', self._TCTRL_target_range), 1418 ('norm_ref_group', self._TCTRL_norm_ref_group) 1419 ] 1420 for field, widget in ctrls: 1421 tr[field] = widget.GetValue().strip() 1422 1423 ctrls = [ 1424 ('val_normal_min', self._TCTRL_normal_min), 1425 ('val_normal_max', self._TCTRL_normal_max), 1426 ('val_target_min', self._TCTRL_target_min), 1427 ('val_target_max', self._TCTRL_target_max) 1428 ] 1429 for field, widget in ctrls: 1430 val = widget.GetValue().strip() 1431 if val == u'': 1432 tr[field] = None 1433 else: 1434 tr[field] = decimal.Decimal(val.replace(',', u'.', 1)) 1435 1436 tr.save_payload() 1437 1438 if self._CHBOX_review.GetValue() is True: 1439 tr.set_review ( 1440 technically_abnormal = self._CHBOX_abnormal.GetValue(), 1441 clinically_relevant = self._CHBOX_relevant.GetValue(), 1442 comment = gmTools.none_if(self._TCTRL_review_comment.GetValue().strip(), u''), 1443 make_me_responsible = False 1444 ) 1445 1446 return True
1447 #-------------------------------------------------------- 1448 # event handling 1449 #--------------------------------------------------------
1450 - def __register_interests(self):
1451 self._PRW_test.add_callback_on_lose_focus(self._on_leave_test_prw) 1452 self._PRW_abnormality_indicator.add_callback_on_lose_focus(self._on_leave_indicator_prw)
1453 #--------------------------------------------------------
1454 - def _on_leave_test_prw(self):
1455 self.__refresh_loinc_info() 1456 self.__update_units_context()
1457 #--------------------------------------------------------
1458 - def _on_leave_indicator_prw(self):
1459 # if the user hasn't explicitly enabled reviewing 1460 if not self._CHBOX_review.GetValue(): 1461 self._CHBOX_abnormal.SetValue(self._PRW_abnormality_indicator.GetValue().strip() != u'')
1462 #--------------------------------------------------------
1463 - def _on_review_box_checked(self, evt):
1464 self._CHBOX_abnormal.Enable(self._CHBOX_review.GetValue()) 1465 self._CHBOX_relevant.Enable(self._CHBOX_review.GetValue()) 1466 self._TCTRL_review_comment.Enable(self._CHBOX_review.GetValue())
1467 #--------------------------------------------------------
1468 - def _on_test_info_button_pressed(self, event):
1469 1470 pk = self._PRW_test.GetData() 1471 if pk is not None: 1472 tt = gmPathLab.cMeasurementType(aPK_obj = pk) 1473 search_term = u'%s %s %s' % ( 1474 tt['name'], 1475 tt['abbrev'], 1476 gmTools.coalesce(tt['loinc'], u'') 1477 ) 1478 else: 1479 search_term = self._PRW_test.GetValue() 1480 1481 search_term = search_term.replace(' ', u'+') 1482 1483 call_browser_on_measurement_type(measurement_type = search_term)
1484 #-------------------------------------------------------- 1485 # internal helpers 1486 #--------------------------------------------------------
1487 - def __update_units_context(self):
1488 1489 self._PRW_units.unset_context(context = u'loinc') 1490 1491 tt = self._PRW_test.GetData(as_instance = True) 1492 1493 if tt is None: 1494 self._PRW_units.unset_context(context = u'pk_type') 1495 if self._PRW_test.GetValue().strip() == u'': 1496 self._PRW_units.unset_context(context = u'test_name') 1497 else: 1498 self._PRW_units.set_context(context = u'test_name', val = self._PRW_test.GetValue().strip()) 1499 return 1500 1501 self._PRW_units.set_context(context = u'pk_type', val = tt['pk_test_type']) 1502 self._PRW_units.set_context(context = u'test_name', val = tt['name']) 1503 1504 if tt['loinc'] is None: 1505 return 1506 1507 self._PRW_units.set_context(context = u'loinc', val = tt['loinc'])
1508 #--------------------------------------------------------
1509 - def __refresh_loinc_info(self):
1510 1511 self._TCTRL_loinc.SetValue(u'') 1512 1513 if self._PRW_test.GetData() is None: 1514 return 1515 1516 tt = self._PRW_test.GetData(as_instance = True) 1517 1518 if tt['loinc'] is None: 1519 return 1520 1521 info = gmLOINC.loinc2info(loinc = tt['loinc']) 1522 if len(info) == 0: 1523 return 1524 1525 self._TCTRL_loinc.SetValue(u'%s: %s' % (tt['loinc'], info[0]))
1526 #================================================================ 1527 # measurement type handling 1528 #================================================================
1529 -def manage_measurement_types(parent=None):
1530 1531 if parent is None: 1532 parent = wx.GetApp().GetTopWindow() 1533 1534 #------------------------------------------------------------ 1535 def edit(test_type=None): 1536 ea = cMeasurementTypeEAPnl(parent = parent, id = -1, type = test_type) 1537 dlg = gmEditArea.cGenericEditAreaDlg2 ( 1538 parent = parent, 1539 id = -1, 1540 edit_area = ea, 1541 single_entry = gmTools.bool2subst((test_type is None), False, True) 1542 ) 1543 dlg.SetTitle(gmTools.coalesce(test_type, _('Adding measurement type'), _('Editing measurement type'))) 1544 1545 if dlg.ShowModal() == wx.ID_OK: 1546 dlg.Destroy() 1547 return True 1548 1549 dlg.Destroy() 1550 return False
1551 #------------------------------------------------------------ 1552 def refresh(lctrl): 1553 mtypes = gmPathLab.get_measurement_types(order_by = 'name, abbrev') 1554 items = [ [ 1555 m['abbrev'], 1556 m['name'], 1557 gmTools.coalesce(m['loinc'], u''), 1558 gmTools.coalesce(m['conversion_unit'], u''), 1559 gmTools.coalesce(m['comment_type'], u''), 1560 gmTools.coalesce(m['internal_name_org'], _('in-house')), 1561 gmTools.coalesce(m['comment_org'], u''), 1562 m['pk_test_type'] 1563 ] for m in mtypes ] 1564 lctrl.set_string_items(items) 1565 lctrl.set_data(mtypes) 1566 #------------------------------------------------------------ 1567 def delete(measurement_type): 1568 if measurement_type.in_use: 1569 gmDispatcher.send ( 1570 signal = 'statustext', 1571 beep = True, 1572 msg = _('Cannot delete measurement type [%s (%s)] because it is in use.') % (measurement_type['name'], measurement_type['abbrev']) 1573 ) 1574 return False 1575 gmPathLab.delete_measurement_type(measurement_type = measurement_type['pk_test_type']) 1576 return True 1577 #------------------------------------------------------------ 1578 msg = _( 1579 '\n' 1580 'These are the measurement types currently defined in GNUmed.\n' 1581 '\n' 1582 ) 1583 1584 gmListWidgets.get_choices_from_list ( 1585 parent = parent, 1586 msg = msg, 1587 caption = _('Showing measurement types.'), 1588 columns = [_('Abbrev'), _('Name'), _('LOINC'), _('Base unit'), _('Comment'), _('Org'), _('Comment'), u'#'], 1589 single_selection = True, 1590 refresh_callback = refresh, 1591 edit_callback = edit, 1592 new_callback = edit, 1593 delete_callback = delete 1594 ) 1595 #----------------------------------------------------------------
1596 -class cMeasurementTypePhraseWheel(gmPhraseWheel.cPhraseWheel):
1597
1598 - def __init__(self, *args, **kwargs):
1599 1600 query = u""" 1601 ( 1602 select 1603 pk_test_type, 1604 name_tt 1605 || ' (' 1606 || coalesce ( 1607 (select internal_name from clin.test_org cto where cto.pk = vcutt.pk_test_org), 1608 '%(in_house)s' 1609 ) 1610 || ')' 1611 as name 1612 from clin.v_unified_test_types vcutt 1613 where 1614 name_meta %%(fragment_condition)s 1615 1616 ) union ( 1617 1618 select 1619 pk_test_type, 1620 name_tt 1621 || ' (' 1622 || coalesce ( 1623 (select internal_name from clin.test_org cto where cto.pk = vcutt.pk_test_org), 1624 '%(in_house)s' 1625 ) 1626 || ')' 1627 as name 1628 from clin.v_unified_test_types vcutt 1629 where 1630 name_tt %%(fragment_condition)s 1631 1632 ) union ( 1633 1634 select 1635 pk_test_type, 1636 name_tt 1637 || ' (' 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 name 1644 from clin.v_unified_test_types vcutt 1645 where 1646 abbrev_meta %%(fragment_condition)s 1647 1648 ) union ( 1649 1650 select 1651 pk_test_type, 1652 name_tt 1653 || ' (' 1654 || coalesce ( 1655 (select internal_name from clin.test_org cto where cto.pk = vcutt.pk_test_org), 1656 '%(in_house)s' 1657 ) 1658 || ')' 1659 as name 1660 from clin.v_unified_test_types vcutt 1661 where 1662 code_tt %%(fragment_condition)s 1663 ) 1664 1665 order by name 1666 limit 50""" % {'in_house': _('in house lab')} 1667 1668 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 1669 mp.setThresholds(1, 2, 4) 1670 mp.word_separators = '[ \t:@]+' 1671 gmPhraseWheel.cPhraseWheel.__init__ ( 1672 self, 1673 *args, 1674 **kwargs 1675 ) 1676 self.matcher = mp 1677 self.SetToolTipString(_('Select the type of measurement.')) 1678 self.selection_only = False
1679 #------------------------------------------------------------
1680 - def _data2instance(self):
1681 if self.data is None: 1682 return None 1683 1684 return gmPathLab.cMeasurementType(aPK_obj = self.data)
1685 #----------------------------------------------------------------
1686 -class cMeasurementOrgPhraseWheel(gmPhraseWheel.cPhraseWheel):
1687
1688 - def __init__(self, *args, **kwargs):
1689 1690 query = u""" 1691 select distinct on (internal_name) 1692 pk, 1693 internal_name 1694 from clin.test_org 1695 where 1696 internal_name %(fragment_condition)s 1697 order by internal_name 1698 limit 50""" 1699 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 1700 mp.setThresholds(1, 2, 4) 1701 #mp.word_separators = '[ \t:@]+' 1702 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 1703 self.matcher = mp 1704 self.SetToolTipString(_('The name of the path lab/diagnostic organisation.')) 1705 self.selection_only = False
1706 #------------------------------------------------------------
1707 - def _create_data(self):
1708 if self.data is not None: 1709 _log.debug('data already set, not creating') 1710 return 1711 1712 if self.GetValue().strip() == u'': 1713 _log.debug('cannot create new lab, missing name') 1714 return 1715 1716 lab = gmPathLab.create_test_org(name = self.GetValue().strip()) 1717 self.SetText(value = lab['internal_name'], data = lab['pk']) 1718 return
1719 #------------------------------------------------------------
1720 - def _data2instance(self):
1721 return gmPathLab.cTestOrg(aPK_obj = self.data)
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 desired unit for the amount or measurement.')) 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, as_instance = 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 = gmPersonSearch.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 = gmPersonSearch.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