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