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

Source Code for Module Gnumed.wxpython.gmMeasurementWidgets

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