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

Source Code for Module Gnumed.wxpython.gmMeasurementWidgets

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