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.64 2009/12/21 15:12:29 ncq Exp $ 
   6  __version__ = "$Revision: 1.64 $" 
   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 543 # clinical evaluation 544 norm_eval = None 545 if d['val_num'] is not None: 546 # 1) normal range 547 # lowered ? 548 if (d['val_normal_min'] is not None) and (d['val_num'] < d['val_normal_min']): 549 try: 550 percent = (d['val_num'] * 100) / d['val_normal_min'] 551 except ZeroDivisionError: 552 percent = None 553 if percent is not None: 554 if percent < 6: 555 norm_eval = _(u'%.1f %% of the normal lower limit') % percent 556 else: 557 norm_eval = _(u'%.0f %% of the normal lower limit') % percent 558 # raised ? 559 if (d['val_normal_max'] is not None) and (d['val_num'] > d['val_normal_max']): 560 try: 561 x_times = d['val_num'] / d['val_normal_max'] 562 except ZeroDivisionError: 563 x_times = None 564 if x_times is not None: 565 if x_times < 10: 566 norm_eval = _(u'%.1f times the normal upper limit') % x_times 567 else: 568 norm_eval = _(u'%.0f times the normal upper limit') % x_times 569 if norm_eval is not None: 570 tt += u' (%s)\n' % norm_eval 571 # #------------------------------------- 572 # # this idea was shot down on the list 573 # #------------------------------------- 574 # # bandwidth of deviation 575 # if None not in [d['val_normal_min'], d['val_normal_max']]: 576 # normal_width = d['val_normal_max'] - d['val_normal_min'] 577 # deviation_from_normal_range = None 578 # # below ? 579 # if d['val_num'] < d['val_normal_min']: 580 # deviation_from_normal_range = d['val_normal_min'] - d['val_num'] 581 # # above ? 582 # elif d['val_num'] > d['val_normal_max']: 583 # deviation_from_normal_range = d['val_num'] - d['val_normal_max'] 584 # if deviation_from_normal_range is None: 585 # try: 586 # times_deviation = deviation_from_normal_range / normal_width 587 # except ZeroDivisionError: 588 # times_deviation = None 589 # if times_deviation is not None: 590 # if times_deviation < 10: 591 # tt += u' (%s)\n' % _(u'deviates by %.1f times of the normal range') % times_deviation 592 # else: 593 # tt += u' (%s)\n' % _(u'deviates by %.0f times of the normal range') % times_deviation 594 # #------------------------------------- 595 596 # 2) clinical target range 597 norm_eval = None 598 # lowered ? 599 if (d['val_target_min'] is not None) and (d['val_num'] < d['val_target_min']): 600 try: 601 percent = (d['val_num'] * 100) / d['val_target_min'] 602 except ZeroDivisionError: 603 percent = None 604 if percent is not None: 605 if percent < 6: 606 norm_eval = _(u'%.1f %% of the target lower limit') % percent 607 else: 608 norm_eval = _(u'%.0f %% of the target lower limit') % percent 609 # raised ? 610 if (d['val_target_max'] is not None) and (d['val_num'] > d['val_target_max']): 611 try: 612 x_times = d['val_num'] / d['val_target_max'] 613 except ZeroDivisionError: 614 x_times = None 615 if x_times is not None: 616 if x_times < 10: 617 norm_eval = _(u'%.1f times the target upper limit') % x_times 618 else: 619 norm_eval = _(u'%.0f times the target upper limit') % x_times 620 if norm_eval is not None: 621 tt += u' (%s)\n' % norm_eval 622 # #------------------------------------- 623 # # this idea was shot down on the list 624 # #------------------------------------- 625 # # bandwidth of deviation 626 # if None not in [d['val_target_min'], d['val_target_max']]: 627 # normal_width = d['val_target_max'] - d['val_target_min'] 628 # deviation_from_target_range = None 629 # # below ? 630 # if d['val_num'] < d['val_target_min']: 631 # deviation_from_target_range = d['val_target_min'] - d['val_num'] 632 # # above ? 633 # elif d['val_num'] > d['val_target_max']: 634 # deviation_from_target_range = d['val_num'] - d['val_target_max'] 635 # if deviation_from_target_range is None: 636 # try: 637 # times_deviation = deviation_from_target_range / normal_width 638 # except ZeroDivisionError: 639 # times_deviation = None 640 # if times_deviation is not None: 641 # if times_deviation < 10: 642 # tt += u' (%s)\n' % _(u'deviates by %.1f times of the target range') % times_deviation 643 # else: 644 # tt += u' (%s)\n' % _(u'deviates by %.0f times of the target range') % times_deviation 645 # #------------------------------------- 646 647 # ranges 648 tt += u'\n' 649 tt += u' ' + _(u'Standard normal range: %(norm_min_max)s%(norm_range)s \n') % ({ 650 'norm_min_max': normal_min_max, 651 'norm_range': gmTools.coalesce ( 652 d['val_normal_range'], 653 u'', 654 gmTools.bool2subst ( 655 has_normal_min_or_max, 656 u' / %s', 657 u'%s' 658 ) 659 ) 660 }) 661 tt += u' ' + _(u'Reference group: %(ref_group)s\n') % ({'ref_group': gmTools.coalesce(d['norm_ref_group'], u'')}) 662 tt += u' ' + _(u'Clinical target range: %(clin_min_max)s%(clin_range)s \n') % ({ 663 'clin_min_max': clinical_min_max, 664 'clin_range': gmTools.coalesce ( 665 d['val_target_range'], 666 u'', 667 gmTools.bool2subst ( 668 has_clinical_min_or_max, 669 u' / %s', 670 u'%s' 671 ) 672 ) 673 }) 674 675 # metadata 676 tt += u' ' + _(u'Doc: %s\n') % _(u'\n Doc: ').join(gmTools.coalesce(d['comment'], u'').split(u'\n')) 677 tt += u' ' + _(u'Lab: %s\n') % _(u'\n Lab: ').join(gmTools.coalesce(d['note_test_org'], u'').split(u'\n')) 678 tt += u' ' + _(u'Episode: %s\n') % d['episode'] 679 tt += u' ' + _(u'Issue: %s\n') % gmTools.coalesce(d['health_issue'], u'') 680 tt += u' ' + _(u'Material: %s\n') % gmTools.coalesce(d['material'], u'') 681 tt += u' ' + _(u'Details: %s\n') % gmTools.coalesce(d['material_detail'], u'') 682 tt += u'\n' 683 684 # review 685 if d['reviewed']: 686 review = d['last_reviewed'].strftime('%c').decode(gmI18N.get_encoding()) 687 else: 688 review = _('not yet') 689 tt += _(u'Signed (%(sig_hand)s): %(reviewed)s\n') % ({ 690 'sig_hand': gmTools.u_writing_hand, 691 'reviewed': review 692 }) 693 tt += u' ' + _(u'Last reviewer: %(reviewer)s\n') % ({'reviewer': gmTools.bool2subst(d['review_by_you'], _('you'), gmTools.coalesce(d['last_reviewer'], u''))}) 694 tt += u' ' + _(u' Technically abnormal: %(abnormal)s\n') % ({'abnormal': gmTools.bool2subst(d['is_technically_abnormal'], _('yes'), _('no'), u'')}) 695 tt += u' ' + _(u' Clinically relevant: %(relevant)s\n') % ({'relevant': gmTools.bool2subst(d['is_clinically_relevant'], _('yes'), _('no'), u'')}) 696 tt += u' ' + _(u' Comment: %s\n') % gmTools.coalesce(d['review_comment'], u'') 697 tt += u' ' + _(u'Responsible clinician: %s\n') % gmTools.bool2subst(d['you_are_responsible'], _('you'), d['responsible_reviewer']) 698 tt += u'\n' 699 700 # type 701 tt += _(u'Test type details:\n') 702 tt += u' ' + _(u'Grouped under "%(name_meta)s" (%(abbrev_meta)s) [#%(pk_u_type)s]\n') % ({ 703 'name_meta': gmTools.coalesce(d['name_meta'], u''), 704 'abbrev_meta': gmTools.coalesce(d['abbrev_meta'], u''), 705 'pk_u_type': d['pk_meta_test_type'] 706 }) 707 tt += u' ' + _(u'Type comment: %s\n') % _(u'\n Type comment:').join(gmTools.coalesce(d['comment_tt'], u'').split(u'\n')) 708 tt += u' ' + _(u'Group comment: %s\n') % _(u'\n Group comment: ').join(gmTools.coalesce(d['comment_meta'], u'').split(u'\n')) 709 tt += u'\n' 710 711 tt += _(u'Revisions: %(row_ver)s, last %(mod_when)s by %(mod_by)s.') % ({ 712 'row_ver': d['row_version'], 713 'mod_when': d['modified_when'].strftime('%c').decode(gmI18N.get_encoding()), 714 'mod_by': d['modified_by'] 715 }) 716 717 return tt
718 #------------------------------------------------------------ 719 # internal helpers 720 #------------------------------------------------------------
721 - def __init_ui(self):
722 self.CreateGrid(0, 1) 723 self.EnableEditing(0) 724 self.EnableDragGridSize(1) 725 726 # setting this screws up the labels: they are cut off and displaced 727 #self.SetColLabelAlignment(wx.ALIGN_CENTER, wx.ALIGN_BOTTOM) 728 729 #self.SetRowLabelSize(wx.GRID_AUTOSIZE) # starting with 2.8.8 730 self.SetRowLabelSize(150) 731 self.SetRowLabelAlignment(horiz = wx.ALIGN_LEFT, vert = wx.ALIGN_CENTRE) 732 733 # add link to left upper corner 734 dbcfg = gmCfg.cCfgSQL() 735 url = dbcfg.get2 ( 736 option = u'external.urls.measurements_encyclopedia', 737 workplace = gmSurgery.gmCurrentPractice().active_workplace, 738 bias = 'user', 739 default = u'http://www.laborlexikon.de' 740 ) 741 742 self.__WIN_corner = self.GetGridCornerLabelWindow() # a wx.Window instance 743 744 LNK_lab = wx.lib.hyperlink.HyperLinkCtrl ( 745 self.__WIN_corner, 746 -1, 747 label = _('Reference'), 748 style = wx.HL_DEFAULT_STYLE # wx.TE_READONLY|wx.TE_CENTRE| wx.NO_BORDER | 749 ) 750 LNK_lab.SetURL(url) 751 LNK_lab.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_BACKGROUND)) 752 LNK_lab.SetToolTipString(_( 753 'Navigate to an encyclopedia of measurements\n' 754 'and test methods on the web.\n' 755 '\n' 756 ' <%s>' 757 ) % url) 758 759 SZR_inner = wx.BoxSizer(wx.HORIZONTAL) 760 SZR_inner.Add((20, 20), 1, wx.EXPAND, 0) # spacer 761 SZR_inner.Add(LNK_lab, 0, wx.ALIGN_CENTER_VERTICAL, 0) #wx.ALIGN_CENTER wx.EXPAND 762 SZR_inner.Add((20, 20), 1, wx.EXPAND, 0) # spacer 763 764 SZR_corner = wx.BoxSizer(wx.VERTICAL) 765 SZR_corner.Add((20, 20), 1, wx.EXPAND, 0) # spacer 766 SZR_corner.AddWindow(SZR_inner, 0, wx.EXPAND) # inner sizer with centered hyperlink 767 SZR_corner.Add((20, 20), 1, wx.EXPAND, 0) # spacer 768 769 self.__WIN_corner.SetSizer(SZR_corner) 770 SZR_corner.Fit(self.__WIN_corner)
771 #------------------------------------------------------------
772 - def __resize_corner_window(self, evt):
773 self.__WIN_corner.Layout()
774 #------------------------------------------------------------
775 - def __cells_to_data(self, cells=None, exclude_multi_cells=False):
776 """List of <cells> must be in row / col order.""" 777 data = [] 778 for row, col in cells: 779 try: 780 # cell data is stored col / row 781 data_list = self.__cell_data[col][row] 782 except KeyError: 783 continue 784 785 if len(data_list) == 1: 786 data.append(data_list[0]) 787 continue 788 789 if exclude_multi_cells: 790 gmDispatcher.send(signal = u'statustext', msg = _('Excluding multi-result field from further processing.')) 791 continue 792 793 data_to_include = self.__get_choices_from_multi_cell(cell_data = data_list) 794 795 if data_to_include is None: 796 continue 797 798 data.extend(data_to_include) 799 800 return data
801 #------------------------------------------------------------
802 - def __get_choices_from_multi_cell(self, cell_data=None, single_selection=False):
803 data = gmListWidgets.get_choices_from_list ( 804 parent = self, 805 msg = _( 806 'Your selection includes a field with multiple results.\n' 807 '\n' 808 'Please select the individual results you want to work on:' 809 ), 810 caption = _('Selecting test results'), 811 choices = [ [d['clin_when'], d['unified_abbrev'], d['unified_name'], d['unified_val']] for d in cell_data ], 812 columns = [_('Date / Time'), _('Code'), _('Test'), _('Result')], 813 data = cell_data, 814 single_selection = single_selection 815 ) 816 return data
817 #------------------------------------------------------------ 818 # event handling 819 #------------------------------------------------------------
820 - def __register_events(self):
821 # dynamic tooltips: GridWindow, GridRowLabelWindow, GridColLabelWindow, GridCornerLabelWindow 822 self.GetGridWindow().Bind(wx.EVT_MOTION, self.__on_mouse_over_cells) 823 self.GetGridRowLabelWindow().Bind(wx.EVT_MOTION, self.__on_mouse_over_row_labels) 824 #self.GetGridColLabelWindow().Bind(wx.EVT_MOTION, self.__on_mouse_over_col_labels) 825 826 # sizing left upper corner window 827 self.Bind(wx.EVT_SIZE, self.__resize_corner_window) 828 829 # editing cells 830 self.Bind(wx.grid.EVT_GRID_CELL_LEFT_DCLICK, self.__on_cell_left_dclicked)
831 #------------------------------------------------------------
832 - def __on_cell_left_dclicked(self, evt):
833 col = evt.GetCol() 834 row = evt.GetRow() 835 836 # empty cell, perhaps ? 837 try: 838 self.__cell_data[col][row] 839 except KeyError: 840 # FIXME: invoke editor for adding value for day of that column 841 # FIMXE: and test of that row 842 return 843 844 if len(self.__cell_data[col][row]) > 1: 845 data = self.__get_choices_from_multi_cell(cell_data = self.__cell_data[col][row], single_selection = True) 846 else: 847 data = self.__cell_data[col][row][0] 848 849 if data is None: 850 return 851 852 edit_measurement(parent = self, measurement = data)
853 #------------------------------------------------------------ 854 # def OnMouseMotionRowLabel(self, evt): 855 # x, y = self.CalcUnscrolledPosition(evt.GetPosition()) 856 # row = self.YToRow(y) 857 # label = self.table().GetRowHelpValue(row) 858 # self.GetGridRowLabelWindow().SetToolTipString(label or "") 859 # evt.Skip()
860 - def __on_mouse_over_row_labels(self, evt):
861 862 # Use CalcUnscrolledPosition() to get the mouse position within the 863 # entire grid including what's offscreen 864 x, y = self.CalcUnscrolledPosition(evt.GetX(), evt.GetY()) 865 866 row = self.YToRow(y) 867 868 if self.__prev_label_row == row: 869 return 870 871 self.__prev_label_row == row 872 873 evt.GetEventObject().SetToolTipString(self.get_row_tooltip(row = row))
874 #------------------------------------------------------------ 875 # def OnMouseMotionColLabel(self, evt): 876 # x, y = self.CalcUnscrolledPosition(evt.GetPosition()) 877 # col = self.XToCol(x) 878 # label = self.table().GetColHelpValue(col) 879 # self.GetGridColLabelWindow().SetToolTipString(label or "") 880 # evt.Skip() 881 #------------------------------------------------------------
882 - def __on_mouse_over_cells(self, evt):
883 """Calculate where the mouse is and set the tooltip dynamically.""" 884 885 # Use CalcUnscrolledPosition() to get the mouse position within the 886 # entire grid including what's offscreen 887 x, y = self.CalcUnscrolledPosition(evt.GetX(), evt.GetY()) 888 889 # use this logic to prevent tooltips outside the actual cells 890 # apply to GetRowSize, too 891 # tot = 0 892 # for col in xrange(self.NumberCols): 893 # tot += self.GetColSize(col) 894 # if xpos <= tot: 895 # self.tool_tip.Tip = 'Tool tip for Column %s' % ( 896 # self.GetColLabelValue(col)) 897 # break 898 # else: # mouse is in label area beyond the right-most column 899 # self.tool_tip.Tip = '' 900 901 row, col = self.XYToCell(x, y) 902 903 if (row == self.__prev_row) and (col == self.__prev_col): 904 return 905 906 self.__prev_row = row 907 self.__prev_col = col 908 909 evt.GetEventObject().SetToolTipString(self.get_cell_tooltip(col=col, row=row))
910 #------------------------------------------------------------ 911 # properties 912 #------------------------------------------------------------
913 - def _set_patient(self, patient):
914 self.__patient = patient 915 self.repopulate_grid()
916 917 patient = property(lambda x:x, _set_patient)
918 #================================================================
919 -class cMeasurementsPnl(wxgMeasurementsPnl.wxgMeasurementsPnl, gmRegetMixin.cRegetOnPaintMixin):
920 921 """Panel holding a grid with lab data. Used as notebook page.""" 922
923 - def __init__(self, *args, **kwargs):
924 925 wxgMeasurementsPnl.wxgMeasurementsPnl.__init__(self, *args, **kwargs) 926 gmRegetMixin.cRegetOnPaintMixin.__init__(self) 927 self.__init_ui() 928 self.__register_interests()
929 #-------------------------------------------------------- 930 # event handling 931 #--------------------------------------------------------
932 - def __register_interests(self):
933 gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._on_pre_patient_selection) 934 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection) 935 gmDispatcher.connect(signal = u'test_result_mod_db', receiver = self._schedule_data_reget) 936 gmDispatcher.connect(signal = u'reviewed_test_results_mod_db', receiver = self._schedule_data_reget)
937 #--------------------------------------------------------
938 - def _on_post_patient_selection(self):
939 wx.CallAfter(self.__on_post_patient_selection)
940 #--------------------------------------------------------
941 - def __on_post_patient_selection(self):
942 self._schedule_data_reget()
943 #--------------------------------------------------------
944 - def _on_pre_patient_selection(self):
945 wx.CallAfter(self.__on_pre_patient_selection)
946 #--------------------------------------------------------
947 - def __on_pre_patient_selection(self):
948 self.data_grid.patient = None
949 #--------------------------------------------------------
950 - def _on_review_button_pressed(self, evt):
951 self.PopupMenu(self.__action_button_popup)
952 #--------------------------------------------------------
953 - def _on_select_button_pressed(self, evt):
954 if self._RBTN_my_unsigned.GetValue() is True: 955 self.data_grid.select_cells(unsigned_only = True, accountables_only = True, keep_preselections = False) 956 elif self._RBTN_all_unsigned.GetValue() is True: 957 self.data_grid.select_cells(unsigned_only = True, accountables_only = False, keep_preselections = False)
958 #--------------------------------------------------------
959 - def __on_sign_current_selection(self, evt):
960 self.data_grid.sign_current_selection()
961 #--------------------------------------------------------
962 - def __on_delete_current_selection(self, evt):
963 self.data_grid.delete_current_selection()
964 #-------------------------------------------------------- 965 # internal API 966 #--------------------------------------------------------
967 - def __init_ui(self):
968 self.__action_button_popup = wx.Menu(title = _('Act on selected results')) 969 970 menu_id = wx.NewId() 971 self.__action_button_popup.AppendItem(wx.MenuItem(self.__action_button_popup, menu_id, _('Review and &sign'))) 972 wx.EVT_MENU(self.__action_button_popup, menu_id, self.__on_sign_current_selection) 973 974 menu_id = wx.NewId() 975 self.__action_button_popup.AppendItem(wx.MenuItem(self.__action_button_popup, menu_id, _('Export to &file'))) 976 #wx.EVT_MENU(self.__action_button_popup, menu_id, self.data_grid.current_selection_to_file) 977 self.__action_button_popup.Enable(id = menu_id, enable = False) 978 979 menu_id = wx.NewId() 980 self.__action_button_popup.AppendItem(wx.MenuItem(self.__action_button_popup, menu_id, _('Export to &clipboard'))) 981 #wx.EVT_MENU(self.__action_button_popup, menu_id, self.data_grid.current_selection_to_clipboard) 982 self.__action_button_popup.Enable(id = menu_id, enable = False) 983 984 menu_id = wx.NewId() 985 self.__action_button_popup.AppendItem(wx.MenuItem(self.__action_button_popup, menu_id, _('&Delete'))) 986 wx.EVT_MENU(self.__action_button_popup, menu_id, self.__on_delete_current_selection)
987 #-------------------------------------------------------- 988 # reget mixin API 989 #--------------------------------------------------------
990 - def _populate_with_data(self):
991 """Populate fields in pages with data from model.""" 992 pat = gmPerson.gmCurrentPatient() 993 if pat.connected: 994 self.data_grid.patient = pat 995 else: 996 self.data_grid.patient = None 997 return True
998 #================================================================ 999 # editing widgets 1000 #================================================================
1001 -class cMeasurementsReviewDlg(wxgMeasurementsReviewDlg.wxgMeasurementsReviewDlg):
1002
1003 - def __init__(self, *args, **kwargs):
1004 1005 try: 1006 tests = kwargs['tests'] 1007 del kwargs['tests'] 1008 test_count = len(tests) 1009 try: del kwargs['test_count'] 1010 except KeyError: pass 1011 except KeyError: 1012 tests = None 1013 test_count = kwargs['test_count'] 1014 del kwargs['test_count'] 1015 1016 wxgMeasurementsReviewDlg.wxgMeasurementsReviewDlg.__init__(self, *args, **kwargs) 1017 1018 if tests is None: 1019 msg = _('%s results selected. Too many to list individually.') % test_count 1020 else: 1021 msg = ' // '.join ( 1022 [ u'%s: %s %s (%s)' % ( 1023 t['unified_abbrev'], 1024 t['unified_val'], 1025 t['val_unit'], 1026 t['clin_when'].strftime('%x').decode(gmI18N.get_encoding()) 1027 ) for t in tests 1028 ] 1029 ) 1030 1031 self._LBL_tests.SetLabel(msg) 1032 1033 if test_count == 1: 1034 self._TCTRL_comment.Enable(True) 1035 self._TCTRL_comment.SetValue(gmTools.coalesce(tests[0]['review_comment'], u'')) 1036 if tests[0]['you_are_responsible']: 1037 self._CHBOX_responsible.Enable(False) 1038 1039 self.Fit()
1040 #-------------------------------------------------------- 1041 # event handling 1042 #--------------------------------------------------------
1043 - def _on_signoff_button_pressed(self, evt):
1044 if self.IsModal(): 1045 self.EndModal(wx.ID_APPLY) 1046 else: 1047 self.Close()
1048 #================================================================
1049 -class cMeasurementEditAreaPnl(wxgMeasurementEditAreaPnl.wxgMeasurementEditAreaPnl, gmEditArea.cGenericEditAreaMixin):
1050 """This edit area saves *new* measurements into the active patient only.""" 1051
1052 - def __init__(self, *args, **kwargs):
1053 1054 try: 1055 self.__default_date = kwargs['date'] 1056 del kwargs['date'] 1057 except KeyError: 1058 self.__default_date = None 1059 1060 wxgMeasurementEditAreaPnl.wxgMeasurementEditAreaPnl.__init__(self, *args, **kwargs) 1061 gmEditArea.cGenericEditAreaMixin.__init__(self) 1062 1063 self.__register_interests() 1064 1065 self.successful_save_msg = _('Successfully saved measurement.')
1066 #-------------------------------------------------------- 1067 # generic edit area mixin API 1068 #--------------------------------------------------------
1069 - def _refresh_as_new(self):
1070 self._PRW_test.SetText(u'', None, True) 1071 self._TCTRL_result.SetValue(u'') 1072 self._PRW_units.SetText(u'', None, True) 1073 self._PRW_abnormality_indicator.SetText(u'', None, True) 1074 if self.__default_date is None: 1075 self._DPRW_evaluated.SetData(data = pyDT.datetime.now(tz = gmDateTime.gmCurrentLocalTimezone)) 1076 else: 1077 self._DPRW_evaluated.SetData(data = None) 1078 self._TCTRL_note_test_org.SetValue(u'') 1079 self._PRW_intended_reviewer.SetData() 1080 self._PRW_problem.SetData() 1081 self._TCTRL_narrative.SetValue(u'') 1082 self._CHBOX_review.SetValue(False) 1083 self._CHBOX_abnormal.SetValue(False) 1084 self._CHBOX_relevant.SetValue(False) 1085 self._CHBOX_abnormal.Enable(False) 1086 self._CHBOX_relevant.Enable(False) 1087 self._TCTRL_review_comment.SetValue(u'') 1088 self._TCTRL_normal_min.SetValue(u'') 1089 self._TCTRL_normal_max.SetValue(u'') 1090 self._TCTRL_normal_range.SetValue(u'') 1091 self._TCTRL_target_min.SetValue(u'') 1092 self._TCTRL_target_max.SetValue(u'') 1093 self._TCTRL_target_range.SetValue(u'') 1094 self._TCTRL_norm_ref_group.SetValue(u'') 1095 1096 self._PRW_test.SetFocus()
1097 #--------------------------------------------------------
1098 - def _refresh_from_existing(self):
1099 self._PRW_test.SetData(data = self.data['pk_test_type']) 1100 self._TCTRL_result.SetValue(self.data['unified_val']) 1101 self._PRW_units.SetText(self.data['val_unit'], self.data['val_unit'], True) 1102 self._PRW_abnormality_indicator.SetText ( 1103 gmTools.coalesce(self.data['abnormality_indicator'], u''), 1104 gmTools.coalesce(self.data['abnormality_indicator'], u''), 1105 True 1106 ) 1107 self._DPRW_evaluated.SetData(data = self.data['clin_when']) 1108 self._TCTRL_note_test_org.SetValue(gmTools.coalesce(self.data['note_test_org'], u'')) 1109 self._PRW_intended_reviewer.SetData(self.data['pk_intended_reviewer']) 1110 self._PRW_problem.SetData(self.data['pk_episode']) 1111 self._TCTRL_narrative.SetValue(gmTools.coalesce(self.data['comment'], u'')) 1112 self._CHBOX_review.SetValue(False) 1113 self._CHBOX_abnormal.SetValue(gmTools.coalesce(self.data['is_technically_abnormal'], False)) 1114 self._CHBOX_relevant.SetValue(gmTools.coalesce(self.data['is_clinically_relevant'], False)) 1115 self._CHBOX_abnormal.Enable(False) 1116 self._CHBOX_relevant.Enable(False) 1117 self._TCTRL_review_comment.SetValue(gmTools.coalesce(self.data['review_comment'], u'')) 1118 self._TCTRL_normal_min.SetValue(unicode(gmTools.coalesce(self.data['val_normal_min'], u''))) 1119 self._TCTRL_normal_max.SetValue(unicode(gmTools.coalesce(self.data['val_normal_max'], u''))) 1120 self._TCTRL_normal_range.SetValue(gmTools.coalesce(self.data['val_normal_range'], u'')) 1121 self._TCTRL_target_min.SetValue(unicode(gmTools.coalesce(self.data['val_target_min'], u''))) 1122 self._TCTRL_target_max.SetValue(unicode(gmTools.coalesce(self.data['val_target_max'], u''))) 1123 self._TCTRL_target_range.SetValue(gmTools.coalesce(self.data['val_target_range'], u'')) 1124 self._TCTRL_norm_ref_group.SetValue(gmTools.coalesce(self.data['norm_ref_group'], u'')) 1125 1126 self._TCTRL_result.SetFocus()
1127 #--------------------------------------------------------
1129 self._refresh_from_existing() 1130 1131 self._PRW_test.SetText(u'', None, True) 1132 self._TCTRL_result.SetValue(u'') 1133 self._PRW_units.SetText(u'', None, True) 1134 self._PRW_abnormality_indicator.SetText(u'', None, True) 1135 # self._DPRW_evaluated 1136 self._TCTRL_note_test_org.SetValue(u'') 1137 self._TCTRL_narrative.SetValue(u'') 1138 self._CHBOX_review.SetValue(False) 1139 self._CHBOX_abnormal.SetValue(False) 1140 self._CHBOX_relevant.SetValue(False) 1141 self._CHBOX_abnormal.Enable(False) 1142 self._CHBOX_relevant.Enable(False) 1143 self._TCTRL_review_comment.SetValue(u'') 1144 self._TCTRL_normal_min.SetValue(u'') 1145 self._TCTRL_normal_max.SetValue(u'') 1146 self._TCTRL_normal_range.SetValue(u'') 1147 self._TCTRL_target_min.SetValue(u'') 1148 self._TCTRL_target_max.SetValue(u'') 1149 self._TCTRL_target_range.SetValue(u'') 1150 self._TCTRL_norm_ref_group.SetValue(u'') 1151 1152 self._PRW_test.SetFocus()
1153 #--------------------------------------------------------
1154 - def _valid_for_save(self):
1155 1156 validity = True 1157 1158 if not self._DPRW_evaluated.is_valid_timestamp(): 1159 self._DPRW_evaluated.display_as_valid(False) 1160 validity = False 1161 else: 1162 self._DPRW_evaluated.display_as_valid(True) 1163 1164 if self._TCTRL_result.GetValue().strip() == u'': 1165 self._TCTRL_result.SetBackgroundColour(gmPhraseWheel.color_prw_invalid) 1166 validity = False 1167 else: 1168 self._TCTRL_result.SetBackgroundColour(gmPhraseWheel.color_prw_valid) 1169 1170 if self._PRW_problem.GetValue().strip() == u'': 1171 self._PRW_problem.display_as_valid(False) 1172 validity = False 1173 else: 1174 self._PRW_problem.display_as_valid(True) 1175 1176 if self._PRW_test.GetValue().strip() == u'': 1177 self._PRW_test.display_as_valid(False) 1178 validity = False 1179 else: 1180 self._PRW_test.display_as_valid(True) 1181 1182 if self._PRW_intended_reviewer.GetData() is None: 1183 self._PRW_intended_reviewer.display_as_valid(False) 1184 validity = False 1185 else: 1186 self._PRW_intended_reviewer.display_as_valid(True) 1187 1188 if self._PRW_units.GetValue().strip() == u'': 1189 self._PRW_units.display_as_valid(False) 1190 validity = False 1191 else: 1192 self._PRW_units.display_as_valid(True) 1193 1194 ctrls = [self._TCTRL_normal_min, self._TCTRL_normal_max, self._TCTRL_target_min, self._TCTRL_target_max] 1195 for widget in ctrls: 1196 val = widget.GetValue().strip() 1197 if val == u'': 1198 continue 1199 try: 1200 decimal.Decimal(val.replace(',', u'.', 1)) 1201 widget.SetBackgroundColour(gmPhraseWheel.color_prw_valid) 1202 except: 1203 widget.SetBackgroundColour(gmPhraseWheel.color_prw_invalid) 1204 validity = False 1205 1206 if validity is False: 1207 gmDispatcher.send(signal = 'statustext', msg = _('Cannot save result. Invalid or missing essential input.')) 1208 1209 return validity
1210 #--------------------------------------------------------
1211 - def _save_as_new(self):
1212 1213 emr = gmPerson.gmCurrentPatient().get_emr() 1214 1215 try: 1216 v_num = decimal.Decimal(self._TCTRL_result.GetValue().strip().replace(',', '.', 1)) 1217 v_al = None 1218 except: 1219 v_num = None 1220 v_al = self._TCTRL_result.GetValue().strip() 1221 1222 pk_type = self._PRW_test.GetData() 1223 if pk_type is None: 1224 tt = gmPathLab.create_measurement_type ( 1225 lab = None, 1226 abbrev = self._PRW_test.GetValue().strip(), 1227 name = self._PRW_test.GetValue().strip(), 1228 unit = gmTools.none_if(self._PRW_units.GetValue().strip(), u'') 1229 ) 1230 pk_type = tt['pk_test_type'] 1231 1232 tr = emr.add_test_result ( 1233 episode = self._PRW_problem.GetData(can_create=True, is_open=False), 1234 type = pk_type, 1235 intended_reviewer = self._PRW_intended_reviewer.GetData(), 1236 val_num = v_num, 1237 val_alpha = v_al, 1238 unit = self._PRW_units.GetValue() 1239 ) 1240 1241 tr['clin_when'] = self._DPRW_evaluated.GetData().get_pydt() 1242 1243 ctrls = [ 1244 ('abnormality_indicator', self._PRW_abnormality_indicator), 1245 ('note_test_org', self._TCTRL_note_test_org), 1246 ('comment', self._TCTRL_narrative), 1247 ('val_normal_min', self._TCTRL_normal_min), 1248 ('val_normal_max', self._TCTRL_normal_max), 1249 ('val_normal_range', self._TCTRL_normal_range), 1250 ('val_target_min', self._TCTRL_target_min), 1251 ('val_target_max', self._TCTRL_target_max), 1252 ('val_target_range', self._TCTRL_target_range), 1253 ('norm_ref_group', self._TCTRL_norm_ref_group) 1254 ] 1255 for field, widget in ctrls: 1256 val = widget.GetValue().strip() 1257 if val != u'': 1258 tr[field] = val 1259 1260 tr.save_payload() 1261 1262 if self._CHBOX_review.GetValue() is True: 1263 tr.set_review ( 1264 technically_abnormal = self._CHBOX_abnormal.GetValue(), 1265 clinically_relevant = self._CHBOX_relevant.GetValue(), 1266 comment = gmTools.none_if(self._TCTRL_review_comment.GetValue().strip(), u''), 1267 make_me_responsible = False 1268 ) 1269 1270 self.data = tr 1271 1272 return True
1273 #--------------------------------------------------------
1274 - def _save_as_update(self):
1275 1276 success, result = gmTools.input2decimal(self._TCTRL_result.GetValue()) 1277 if success: 1278 v_num = result 1279 v_al = None 1280 else: 1281 v_num = None 1282 v_al = self._TCTRL_result.GetValue().strip() 1283 1284 pk_type = self._PRW_test.GetData() 1285 if pk_type is None: 1286 tt = gmPathLab.create_measurement_type ( 1287 lab = None, 1288 abbrev = self._PRW_test.GetValue().strip(), 1289 name = self._PRW_test.GetValue().strip(), 1290 unit = gmTools.none_if(self._PRW_units.GetValue().strip(), u'') 1291 ) 1292 pk_type = tt['pk_test_type'] 1293 1294 tr = self.data 1295 1296 tr['pk_episode'] = self._PRW_problem.GetData(can_create=True, is_open=False) 1297 tr['pk_test_type'] = pk_type 1298 tr['pk_intended_reviewer'] = self._PRW_intended_reviewer.GetData() 1299 tr['val_num'] = v_num 1300 tr['val_alpha'] = v_al 1301 tr['val_unit'] = self._PRW_units.GetValue().strip() 1302 tr['clin_when'] = self._DPRW_evaluated.GetData().get_pydt() 1303 tr['abnormality_indicator'] = self._PRW_abnormality_indicator.strip() 1304 1305 ctrls = [ 1306 ('note_test_org', self._TCTRL_note_test_org), 1307 ('comment', self._TCTRL_narrative), 1308 ('val_normal_min', self._TCTRL_normal_min), 1309 ('val_normal_max', self._TCTRL_normal_max), 1310 ('val_normal_range', self._TCTRL_normal_range), 1311 ('val_target_min', self._TCTRL_target_min), 1312 ('val_target_max', self._TCTRL_target_max), 1313 ('val_target_range', self._TCTRL_target_range), 1314 ('norm_ref_group', self._TCTRL_norm_ref_group) 1315 ] 1316 for field, widget in ctrls: 1317 val = widget.GetValue().strip() 1318 if val != u'': 1319 tr[field] = val 1320 1321 tr.save_payload() 1322 1323 if self._CHBOX_review.GetValue() is True: 1324 tr.set_review ( 1325 technically_abnormal = self._CHBOX_abnormal.GetValue(), 1326 clinically_relevant = self._CHBOX_relevant.GetValue(), 1327 comment = gmTools.none_if(self._TCTRL_review_comment.GetValue().strip(), u''), 1328 make_me_responsible = False 1329 ) 1330 1331 return True
1332 #-------------------------------------------------------- 1333 # event handling 1334 #--------------------------------------------------------
1335 - def __register_interests(self):
1336 self._PRW_test.add_callback_on_lose_focus(self._on_leave_test_prw) 1337 self._PRW_abnormality_indicator.add_callback_on_lose_focus(self._on_leave_indicator_prw)
1338 #--------------------------------------------------------
1339 - def _on_leave_test_prw(self):
1340 pk_type = self._PRW_test.GetData() 1341 # units context 1342 if pk_type is None: 1343 self._PRW_units.unset_context(context = u'pk_type') 1344 else: 1345 self._PRW_units.set_context(context = u'pk_type', val = pk_type)
1346 #--------------------------------------------------------
1347 - def _on_leave_indicator_prw(self):
1348 # if the user hasn't explicitly enabled reviewing 1349 if not self._CHBOX_review.GetValue(): 1350 self._CHBOX_abnormal.SetValue(self._PRW_abnormality_indicator.GetValue().strip() != u'')
1351 #--------------------------------------------------------
1352 - def _on_review_box_checked(self, evt):
1353 self._CHBOX_abnormal.Enable(self._CHBOX_review.GetValue()) 1354 self._CHBOX_relevant.Enable(self._CHBOX_review.GetValue()) 1355 self._TCTRL_review_comment.Enable(self._CHBOX_review.GetValue())
1356 #================================================================ 1357 # measurement type handling 1358 #================================================================
1359 -def manage_measurement_types(parent=None):
1360 1361 if parent is None: 1362 parent = wx.GetApp().GetTopWindow() 1363 1364 #------------------------------------------------------------ 1365 def edit(test_type=None): 1366 ea = cMeasurementTypeEAPnl(parent = parent, id = -1, type = test_type) 1367 dlg = gmEditArea.cGenericEditAreaDlg2 ( 1368 parent = parent, 1369 id = -1, 1370 edit_area = ea, 1371 single_entry = gmTools.bool2subst((test_type is None), False, True) 1372 ) 1373 dlg.SetTitle(gmTools.coalesce(test_type, _('Adding measurement type'), _('Editing measurement type'))) 1374 1375 if dlg.ShowModal() == wx.ID_OK: 1376 dlg.Destroy() 1377 return True 1378 1379 dlg.Destroy() 1380 return False
1381 #------------------------------------------------------------ 1382 def refresh(lctrl): 1383 mtypes = gmPathLab.get_measurement_types(order_by = 'name, abbrev') 1384 items = [ [ 1385 m['abbrev'], 1386 m['name'], 1387 gmTools.coalesce(m['loinc'], u''), 1388 gmTools.coalesce(m['conversion_unit'], u''), 1389 gmTools.coalesce(m['comment_type'], u''), 1390 gmTools.coalesce(m['internal_name_org'], _('in-house')), 1391 gmTools.coalesce(m['comment_org'], u''), 1392 m['pk_test_type'] 1393 ] for m in mtypes ] 1394 lctrl.set_string_items(items) 1395 lctrl.set_data(mtypes) 1396 #------------------------------------------------------------ 1397 def delete(measurement_type): 1398 if measurement_type.in_use: 1399 gmDispatcher.send ( 1400 signal = 'statustext', 1401 beep = True, 1402 msg = _('Cannot delete measurement type [%s (%s)] because it is in use.') % (measurement_type['name'], measurement_type['abbrev']) 1403 ) 1404 return False 1405 gmPathLab.delete_measurement_type(measurement_type = measurement_type['pk_test_type']) 1406 return True 1407 #------------------------------------------------------------ 1408 msg = _( 1409 '\n' 1410 'These are the measurement types currently defined in GNUmed.\n' 1411 '\n' 1412 ) 1413 1414 gmListWidgets.get_choices_from_list ( 1415 parent = parent, 1416 msg = msg, 1417 caption = _('Showing measurement types.'), 1418 columns = [_('Abbrev'), _('Name'), _('LOINC'), _('Base unit'), _('Comment'), _('Org'), _('Comment'), u'#'], 1419 single_selection = True, 1420 refresh_callback = refresh, 1421 edit_callback = edit, 1422 new_callback = edit, 1423 delete_callback = delete 1424 ) 1425 #----------------------------------------------------------------
1426 -class cMeasurementTypePhraseWheel(gmPhraseWheel.cPhraseWheel):
1427
1428 - def __init__(self, *args, **kwargs):
1429 1430 query = u""" 1431 ( 1432 select 1433 pk_test_type, 1434 name_tt 1435 || ' (' 1436 || coalesce ( 1437 (select internal_name from clin.test_org cto where cto.pk = vcutt.pk_test_org), 1438 '%(in_house)s' 1439 ) 1440 || ')' 1441 as name 1442 from clin.v_unified_test_types vcutt 1443 where 1444 name_meta %%(fragment_condition)s 1445 1446 ) union ( 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_tt %%(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 abbrev_meta %%(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 code_tt %%(fragment_condition)s 1493 ) 1494 1495 order by name 1496 limit 50""" % {'in_house': _('in house lab')} 1497 1498 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 1499 mp.setThresholds(1, 2, 4) 1500 mp.word_separators = '[ \t:@]+' 1501 gmPhraseWheel.cPhraseWheel.__init__ ( 1502 self, 1503 *args, 1504 **kwargs 1505 ) 1506 self.matcher = mp 1507 self.SetToolTipString(_('Select the type of measurement.')) 1508 self.selection_only = False
1509 #---------------------------------------------------------------- 1510 from Gnumed.wxGladeWidgets import wxgMeasurementTypeEAPnl 1511
1512 -class cMeasurementTypeEAPnl(wxgMeasurementTypeEAPnl.wxgMeasurementTypeEAPnl, gmEditArea.cGenericEditAreaMixin):
1513
1514 - def __init__(self, *args, **kwargs):
1515 1516 try: 1517 data = kwargs['type'] 1518 del kwargs['type'] 1519 except KeyError: 1520 data = None 1521 1522 wxgMeasurementTypeEAPnl.wxgMeasurementTypeEAPnl.__init__(self, *args, **kwargs) 1523 gmEditArea.cGenericEditAreaMixin.__init__(self) 1524 self.mode = 'new' 1525 self.data = data 1526 if data is not None: 1527 self.mode = 'edit' 1528 1529 self.__init_ui()
1530 1531 #----------------------------------------------------------------
1532 - def __init_ui(self):
1533 1534 # name phraseweel 1535 query = u""" 1536 select distinct on (name) 1537 pk, 1538 name 1539 from clin.test_type 1540 where 1541 name %(fragment_condition)s 1542 order by name 1543 limit 50""" 1544 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 1545 mp.setThresholds(1, 2, 4) 1546 self._PRW_name.matcher = mp 1547 self._PRW_name.selection_only = False 1548 1549 # abbreviation 1550 query = u""" 1551 select distinct on (abbrev) 1552 pk, 1553 abbrev 1554 from clin.test_type 1555 where 1556 abbrev %(fragment_condition)s 1557 order by abbrev 1558 limit 50""" 1559 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 1560 mp.setThresholds(1, 2, 3) 1561 self._PRW_abbrev.matcher = mp 1562 self._PRW_abbrev.selection_only = False 1563 1564 # unit 1565 # FIXME: use units from test_result 1566 query = u""" 1567 select distinct on (conversion_unit) 1568 conversion_unit, 1569 conversion_unit 1570 from clin.test_type 1571 where 1572 conversion_unit %(fragment_condition)s 1573 order by conversion_unit 1574 limit 50""" 1575 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 1576 mp.setThresholds(1, 2, 3) 1577 self._PRW_conversion_unit.matcher = mp 1578 self._PRW_conversion_unit.selection_only = False 1579 1580 # loinc 1581 query = u""" 1582 select distinct on (term) 1583 loinc, 1584 term 1585 from (( 1586 select 1587 loinc, 1588 (loinc || ': ' || abbrev || ' (' || name || ')') as term 1589 from clin.test_type 1590 where loinc %(fragment_condition)s 1591 limit 50 1592 ) union all ( 1593 select 1594 code as loinc, 1595 (code || ': ' || term) as term 1596 from ref.v_coded_terms 1597 where 1598 coding_system = 'LOINC' 1599 and 1600 lang = i18n.get_curr_lang() 1601 and 1602 (code %(fragment_condition)s 1603 or 1604 term %(fragment_condition)s) 1605 limit 50 1606 ) union all ( 1607 select 1608 code as loinc, 1609 (code || ': ' || term) as term 1610 from ref.v_coded_terms 1611 where 1612 coding_system = 'LOINC' 1613 and 1614 lang = 'en_EN' 1615 and 1616 (code %(fragment_condition)s 1617 or 1618 term %(fragment_condition)s) 1619 limit 50 1620 ) union all ( 1621 select 1622 code as loinc, 1623 (code || ': ' || term) as term 1624 from ref.v_coded_terms 1625 where 1626 coding_system = 'LOINC' 1627 and 1628 (code %(fragment_condition)s 1629 or 1630 term %(fragment_condition)s) 1631 limit 50 1632 ) 1633 ) as all_known_loinc 1634 order by term 1635 limit 50""" 1636 mp = gmMatchProvider.cMatchProvider_SQL2(queries = query) 1637 mp.setThresholds(1, 2, 4) 1638 self._PRW_loinc.matcher = mp 1639 self._PRW_loinc.selection_only = False 1640 self._PRW_loinc.add_callback_on_lose_focus(callback = self._on_loinc_lost_focus) 1641 1642 # test org 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 self._PRW_test_org.matcher = mp 1655 self._PRW_test_org.selection_only = False
1656 #----------------------------------------------------------------
1657 - def _on_loinc_lost_focus(self):
1658 loinc = self._PRW_loinc.GetData() 1659 1660 if loinc is None: 1661 self._TCTRL_loinc_info.SetValue(u'') 1662 return 1663 1664 info = gmLOINC.loinc2info(loinc = loinc) 1665 if len(info) == 0: 1666 self._TCTRL_loinc_info.SetValue(u'') 1667 return 1668 1669 self._TCTRL_loinc_info.SetValue(info[0])
1670 #---------------------------------------------------------------- 1671 # generic Edit Area mixin API 1672 #----------------------------------------------------------------
1673 - def _valid_for_save(self):
1674 1675 has_errors = False 1676 for field in [self._PRW_name, self._PRW_abbrev, self._PRW_conversion_unit]: 1677 if field.GetValue().strip() in [u'', None]: 1678 has_errors = True 1679 field.display_as_valid(valid = False) 1680 else: 1681 field.display_as_valid(valid = True) 1682 field.Refresh() 1683 1684 return (not has_errors)
1685 #----------------------------------------------------------------
1686 - def _save_as_new(self):
1687 1688 pk_org = self._PRW_test_org.GetData() 1689 if pk_org is None: 1690 pk_org = gmPathLab.create_measurement_org ( 1691 name = gmTools.none_if(self._PRW_test_org.GetValue().strip(), u''), 1692 comment = gmTools.none_if(self._TCTRL_comment_org.GetValue().strip(), u'') 1693 ) 1694 1695 tt = gmPathLab.create_measurement_type ( 1696 lab = pk_org, 1697 abbrev = self._PRW_abbrev.GetValue().strip(), 1698 name = self._PRW_name.GetValue().strip(), 1699 unit = gmTools.none_if(self._PRW_conversion_unit.GetValue().strip(), u'') 1700 ) 1701 tt['loinc'] = gmTools.none_if(self._PRW_loinc.GetValue().strip(), u'') 1702 tt['comment_type'] = gmTools.none_if(self._TCTRL_comment_type.GetValue().strip(), u'') 1703 tt.save() 1704 1705 self.data = tt 1706 1707 return True
1708 #----------------------------------------------------------------
1709 - def _save_as_update(self):
1710 1711 pk_org = self._PRW_test_org.GetData() 1712 if pk_org is None: 1713 pk_org = gmPathLab.create_measurement_org ( 1714 name = gmTools.none_if(self._PRW_test_org.GetValue().strip(), u''), 1715 comment = gmTools.none_if(self._TCTRL_comment_org.GetValue().strip(), u'') 1716 ) 1717 1718 self.data['pk_test_org'] = pk_org 1719 self.data['abbrev'] = self._PRW_abbrev.GetValue().strip() 1720 self.data['name'] = self._PRW_name.GetValue().strip() 1721 self.data['conversion_unit'] = gmTools.none_if(self._PRW_conversion_unit.GetValue().strip(), u'') 1722 self.data['loinc'] = gmTools.none_if(self._PRW_loinc.GetValue().strip(), u'') 1723 self.data['comment_type'] = gmTools.none_if(self._TCTRL_comment_type.GetValue().strip(), u'') 1724 self.data.save() 1725 1726 return True
1727 #----------------------------------------------------------------
1728 - def _refresh_as_new(self):
1729 self._PRW_name.SetText(u'', None, True) 1730 self._PRW_abbrev.SetText(u'', None, True) 1731 self._PRW_conversion_unit.SetText(u'', None, True) 1732 self._PRW_loinc.SetText(u'', None, True) 1733 self._TCTRL_loinc_info.SetValue(u'') 1734 self._TCTRL_comment_type.SetValue(u'') 1735 self._PRW_test_org.SetText(u'', None, True) 1736 self._TCTRL_comment_org.SetValue(u'')
1737 #----------------------------------------------------------------
1738 - def _refresh_from_existing(self):
1739 self._PRW_name.SetText(self.data['name'], self.data['name'], True) 1740 self._PRW_abbrev.SetText(self.data['abbrev'], self.data['abbrev'], True) 1741 self._PRW_conversion_unit.SetText ( 1742 gmTools.coalesce(self.data['conversion_unit'], u''), 1743 self.data['conversion_unit'], 1744 True 1745 ) 1746 self._PRW_loinc.SetText ( 1747 gmTools.coalesce(self.data['loinc'], u''), 1748 self.data['loinc'], 1749 True 1750 ) 1751 self._TCTRL_loinc_info.SetValue(u'') # FIXME: properly set 1752 self._TCTRL_comment_type.SetValue(gmTools.coalesce(self.data['comment_type'], u'')) 1753 self._PRW_test_org.SetText ( 1754 gmTools.coalesce(self.data['pk_test_org'], u'', self.data['internal_name_org']), 1755 self.data['pk_test_org'], 1756 True 1757 ) 1758 self._TCTRL_comment_org.SetValue(gmTools.coalesce(self.data['comment_org'], u''))
1759 #----------------------------------------------------------------
1761 self._refresh_as_new() 1762 self._PRW_test_org.SetText ( 1763 gmTools.coalesce(self.data['pk_test_org'], u'', self.data['internal_name_org']), 1764 self.data['pk_test_org'], 1765 True 1766 ) 1767 self._TCTRL_comment_org.SetValue(gmTools.coalesce(self.data['comment_org'], u''))
1768 #================================================================
1769 -class cUnitPhraseWheel(gmPhraseWheel.cPhraseWheel):
1770
1771 - def __init__(self, *args, **kwargs):
1772 1773 query = u""" 1774 select distinct val_unit, 1775 val_unit, val_unit 1776 from clin.v_test_results 1777 where 1778 ( 1779 val_unit %(fragment_condition)s 1780 or 1781 conversion_unit %(fragment_condition)s 1782 ) 1783 %(ctxt_test_name)s 1784 %(ctxt_test_pk)s 1785 order by val_unit 1786 limit 25""" 1787 1788 ctxt = { 1789 'ctxt_test_name': { 1790 'where_part': u'and %(test)s in (name_tt, name_meta, code_tt, abbrev_meta)', 1791 'placeholder': u'test' 1792 }, 1793 'ctxt_test_pk': { 1794 'where_part': u'and pk_test_type = %(pk_type)s', 1795 'placeholder': u'pk_type' 1796 } 1797 } 1798 1799 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query, context=ctxt) 1800 mp.setThresholds(1, 2, 4) 1801 gmPhraseWheel.cPhraseWheel.__init__ ( 1802 self, 1803 *args, 1804 **kwargs 1805 ) 1806 self.matcher = mp 1807 self.SetToolTipString(_('Select the unit of the test result.')) 1808 self.selection_only = False
1809 1810 #================================================================ 1811 1812 #================================================================
1813 -class cTestResultIndicatorPhraseWheel(gmPhraseWheel.cPhraseWheel):
1814
1815 - def __init__(self, *args, **kwargs):
1816 1817 query = u""" 1818 select distinct abnormality_indicator, 1819 abnormality_indicator, abnormality_indicator 1820 from clin.v_test_results 1821 where 1822 abnormality_indicator %(fragment_condition)s 1823 order by abnormality_indicator 1824 limit 25""" 1825 1826 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 1827 mp.setThresholds(1, 1, 2) 1828 mp.ignored_chars = "[.'\\\[\]#$%_]+" + '"' 1829 mp.word_separators = '[ \t&:]+' 1830 gmPhraseWheel.cPhraseWheel.__init__ ( 1831 self, 1832 *args, 1833 **kwargs 1834 ) 1835 self.matcher = mp 1836 self.SetToolTipString(_('Select an indicator for the level of abnormality.')) 1837 self.selection_only = False
1838 #================================================================
1839 -def manage_meta_test_types(parent=None):
1840 1841 if parent is None: 1842 parent = wx.GetApp().GetTopWindow() 1843 1844 msg = _( 1845 '\n' 1846 'These are the meta test types currently defined in GNUmed.\n' 1847 '\n' 1848 'Meta test types allow you to aggregate several actual test types used\n' 1849 'by pathology labs into one logical type.\n' 1850 '\n' 1851 'This is useful for grouping together results of tests which come under\n' 1852 'different names but really are the same thing. This often happens when\n' 1853 'you switch labs or the lab starts using another test method.\n' 1854 ) 1855 1856 mtts = gmPathLab.get_meta_test_types() 1857 1858 gmListWidgets.get_choices_from_list ( 1859 parent = parent, 1860 msg = msg, 1861 caption = _('Showing meta test types.'), 1862 columns = [_('Abbrev'), _('Name'), _('LOINC'), _('Comment'), u'#'], 1863 choices = [ [ 1864 m['abbrev'], 1865 m['name'], 1866 gmTools.coalesce(m['loinc'], u''), 1867 gmTools.coalesce(m['comment'], u''), 1868 m['pk'] 1869 ] for m in mtts ], 1870 data = mtts, 1871 single_selection = True, 1872 #edit_callback = edit, 1873 #new_callback = edit, 1874 #delete_callback = delete, 1875 #refresh_callback = refresh 1876 )
1877 #================================================================ 1878 # main 1879 #---------------------------------------------------------------- 1880 if __name__ == '__main__': 1881 1882 from Gnumed.pycommon import gmLog2 1883 1884 gmI18N.activate_locale() 1885 gmI18N.install_domain() 1886 gmDateTime.init() 1887 1888 #------------------------------------------------------------
1889 - def test_grid():
1890 pat = gmPerson.ask_for_patient() 1891 app = wx.PyWidgetTester(size = (500, 300)) 1892 lab_grid = cMeasurementsGrid(parent = app.frame, id = -1) 1893 lab_grid.patient = pat 1894 app.frame.Show() 1895 app.MainLoop()
1896 #------------------------------------------------------------
1897 - def test_test_ea_pnl():
1898 pat = gmPerson.ask_for_patient() 1899 gmPatSearchWidgets.set_active_patient(patient=pat) 1900 app = wx.PyWidgetTester(size = (500, 300)) 1901 ea = cMeasurementEditAreaPnl(parent = app.frame, id = -1) 1902 app.frame.Show() 1903 app.MainLoop()
1904 #------------------------------------------------------------ 1905 # def test_primary_care_vitals_pnl(): 1906 # app = wx.PyWidgetTester(size = (500, 300)) 1907 # pnl = wxgPrimaryCareVitalsInputPnl.wxgPrimaryCareVitalsInputPnl(parent = app.frame, id = -1) 1908 # app.frame.Show() 1909 # app.MainLoop() 1910 #------------------------------------------------------------ 1911 if (len(sys.argv) > 1) and (sys.argv[1] == 'test'): 1912 #test_grid() 1913 test_test_ea_pnl() 1914 #test_primary_care_vitals_pnl() 1915 1916 #================================================================ 1917 # $Log: gmMeasurementWidgets.py,v $ 1918 # Revision 1.64 2009/12/21 15:12:29 ncq 1919 # - cleanup 1920 # - fix typo 1921 # - missing return 1922 # 1923 # Revision 1.63 2009/12/03 17:50:20 ncq 1924 # - row label tooltips 1925 # 1926 # Revision 1.62 2009/12/01 21:54:04 ncq 1927 # - cleanup 1928 # 1929 # Revision 1.61 2009/10/28 16:42:53 ncq 1930 # - make grid draggable 1931 # 1932 # Revision 1.60 2009/09/17 21:54:55 ncq 1933 # - properly access test type pk 1934 # - check for use before deleting test type 1935 # 1936 # Revision 1.59 2009/09/01 22:33:25 ncq 1937 # - order test types in list 1938 # 1939 # Revision 1.58 2009/08/24 20:11:27 ncq 1940 # - bump db version 1941 # - fix tag creation 1942 # - provider inbox: 1943 # enable filter-to-active-patient, 1944 # listen to new signal, 1945 # use cInboxMessage class 1946 # - properly constrain LOINC phrasewheel SQL 1947 # - include v12 scripts in release 1948 # - install arriba jar to /usr/local/bin/ 1949 # - check for table existence in audit schema generator 1950 # - include dem.message inbox with additional generic signals 1951 # 1952 # Revision 1.57 2009/08/11 10:49:23 ncq 1953 # - cleanup 1954 # - remove LOINC files after import 1955 # - row labels now "abbrev (desc)", again 1956 # - Encyclopedia -> Reference 1957 # - improved LOINC matcher and use loinc to set loinc info 1958 # 1959 # Revision 1.56 2009/08/08 12:18:12 ncq 1960 # - setup phrasewheels in measurement type EA 1961 # 1962 # Revision 1.55 2009/08/03 20:50:48 ncq 1963 # - properly support adding/editing measurement type 1964 # 1965 # Revision 1.54 2009/07/20 20:33:35 ncq 1966 # - start implementing test type management 1967 # 1968 # Revision 1.53 2009/07/15 12:22:46 ncq 1969 # - fix incomplete validity check for new-result problem 1970 # 1971 # Revision 1.52 2009/07/06 17:15:45 ncq 1972 # - row labels only test name until proper support for abbrev is there 1973 # - improved formatting of test result for display in cell 1974 # - only remind of display being most-recent only if cell actually is multi-result in cell tooltip 1975 # - use successful-save message on EA 1976 # - safer refresh after save-and-next-value 1977 # 1978 # Revision 1.51 2009/07/02 20:54:05 ncq 1979 # - fix bug where second patient didn't show measurements on patient change 1980 # 1981 # Revision 1.50 2009/06/22 09:26:49 ncq 1982 # - people didn't like the bandwidth calculation 1983 # 1984 # Revision 1.49 2009/06/20 22:38:05 ncq 1985 # - factor out cell tooltip creation and only do it on mouse over 1986 # 1987 # Revision 1.48 2009/06/11 12:37:25 ncq 1988 # - much simplified initial setup of list ctrls 1989 # 1990 # Revision 1.47 2009/06/04 16:19:00 ncq 1991 # - re-adjust to test table changes 1992 # - update loinc 1993 # - adjust to list widget changes (refresh) 1994 # 1995 # Revision 1.47 2009/05/28 10:53:40 ncq 1996 # - adjust to test tables changes 1997 # 1998 # Revision 1.46 2009/05/24 16:29:14 ncq 1999 # - support (meta) test types 2000 # 2001 # Revision 1.45 2009/04/24 12:05:20 ncq 2002 # - properly display lab link in grid corner 2003 # 2004 # Revision 1.44 2009/04/21 17:01:12 ncq 2005 # - try various other things to try to center the lab link 2006 # 2007 # Revision 1.43 2009/04/19 22:28:23 ncq 2008 # - put hyperlink in upper left corner of lab grid 2009 # 2010 # Revision 1.42 2009/04/14 18:35:27 ncq 2011 # - HCI screening revealed test types scroll off when 2012 # moving horizontall so fix that 2013 # 2014 # Revision 1.41 2009/04/03 09:50:21 ncq 2015 # - comment 2016 # 2017 # Revision 1.40 2009/03/18 14:30:47 ncq 2018 # - improved result tooltip 2019 # 2020 # Revision 1.39 2009/03/01 18:15:55 ncq 2021 # - lots of missing u'', decode strftime results 2022 # - adjust word separators in test type match provider 2023 # 2024 # Revision 1.38 2009/02/20 15:43:21 ncq 2025 # - u''ify 2026 # 2027 # Revision 1.37 2009/02/17 17:47:31 ncq 2028 # - comment out primary care vitals 2029 # 2030 # Revision 1.36 2009/02/12 16:23:39 ncq 2031 # - start work on primary care vitals input 2032 # 2033 # Revision 1.35 2009/01/28 11:27:56 ncq 2034 # - slightly better naming and comments 2035 # 2036 # Revision 1.34 2009/01/02 11:40:27 ncq 2037 # - properly check for numericity of value/range input 2038 # 2039 # Revision 1.33 2008/10/22 12:21:57 ncq 2040 # - use %x in strftime where appropriate 2041 # 2042 # Revision 1.32 2008/08/31 18:21:54 ncq 2043 # - work around Windows' inability to do nothing when 2044 # there's nothing to do 2045 # 2046 # Revision 1.31 2008/08/31 18:04:30 ncq 2047 # - properly handle cell data now being list in select_cells() 2048 # 2049 # Revision 1.30 2008/08/31 17:13:50 ncq 2050 # - don't crash on double-clicking empty test results cell 2051 # 2052 # Revision 1.29 2008/08/31 17:04:17 ncq 2053 # - need to cast val_normal/target_min/max to unicode before display 2054 # 2055 # Revision 1.28 2008/08/15 15:57:10 ncq 2056 # - indicate data revisions in tooltip 2057 # 2058 # Revision 1.27 2008/08/08 13:31:58 ncq 2059 # - better results layout 2060 # 2061 # Revision 1.26 2008/08/05 16:21:30 ncq 2062 # - support multiple values per cell 2063 # 2064 # Revision 1.25 2008/07/17 21:41:36 ncq 2065 # - cleanup 2066 # 2067 # Revision 1.24 2008/07/14 13:47:36 ncq 2068 # - explicitely set focus after refresh per user request 2069 # 2070 # Revision 1.23 2008/07/13 16:13:33 ncq 2071 # - add_new_measurement -> edit_measurement 2072 # - use cGenericEditAreaMixin on results edit area 2073 # - invoked results edit area via double-click on result in grid 2074 # 2075 # Revision 1.22 2008/07/07 13:43:17 ncq 2076 # - current patient .connected 2077 # 2078 # Revision 1.21 2008/06/24 14:00:09 ncq 2079 # - action button popup menu 2080 # - handle result deletion 2081 # 2082 # Revision 1.20 2008/06/23 21:50:26 ncq 2083 # - create test types on the fly 2084 # 2085 # Revision 1.19 2008/06/22 17:32:39 ncq 2086 # - implement refresh on measurement ea so "Next" will work in dialog 2087 # 2088 # Revision 1.18 2008/06/19 15:26:09 ncq 2089 # - finish saving test result from edit area 2090 # - fix a few oversights in the result tooltip 2091 # 2092 # Revision 1.17 2008/06/18 15:49:22 ncq 2093 # - improve save validity check on edit area 2094 # 2095 # Revision 1.16 2008/06/16 15:03:20 ncq 2096 # - first cut at saving test results 2097 # 2098 # Revision 1.15 2008/06/15 20:43:31 ncq 2099 # - add test result indicator phrasewheel 2100 # 2101 # Revision 1.14 2008/06/09 15:36:04 ncq 2102 # - reordered for clarity 2103 # - add_new_measurement 2104 # - edit area start 2105 # - phrasewheels 2106 # 2107 # Revision 1.13 2008/05/14 15:01:43 ncq 2108 # - remove spurious evt argument in _on_pre_patient_selection() 2109 # 2110 # Revision 1.12 2008/04/26 21:40:58 ncq 2111 # - eventually support selecting certain ranges of cells 2112 # 2113 # Revision 1.11 2008/04/26 10:05:32 ncq 2114 # - in review dialog when user is already responsible 2115 # disable make_me_responsible checkbox 2116 # 2117 # Revision 1.10 2008/04/22 21:18:49 ncq 2118 # - implement signing 2119 # - improved tooltip 2120 # - properly clear grid when active patient changes 2121 # 2122 # Revision 1.9 2008/04/16 20:39:39 ncq 2123 # - working versions of the wxGlade code and use it, too 2124 # - show client version in login dialog 2125 # 2126 # Revision 1.8 2008/04/11 23:12:23 ncq 2127 # - improve docs 2128 # 2129 # Revision 1.7 2008/04/04 13:09:45 ncq 2130 # - use +/- as abnormality indicator where not available 2131 # - more complete calculation of "more result data available" 2132 # 2133 # Revision 1.6 2008/04/02 10:48:33 ncq 2134 # - cleanup, review -> sign 2135 # - support test_count in review widget 2136 # - better results formatting as per list discussion 2137 # 2138 # Revision 1.5 2008/03/29 16:19:57 ncq 2139 # - review_current_selection() 2140 # - get_selected_cells() 2141 # - bold test names 2142 # - display abnormality indicator and clinical relevance 2143 # - improve tooltip 2144 # - cMeasurementsReviewDialog() 2145 # - listen to test result database changes 2146 # 2147 # Revision 1.4 2008/03/25 19:36:30 ncq 2148 # - fix imports 2149 # - better docs 2150 # - str() wants non-u'' 2151 # - cMeasurementsPnl() 2152 # 2153 # Revision 1.3 2008/03/20 15:31:40 ncq 2154 # - improve cell tooltips with review status and issue/episode information 2155 # - start row labels tooltips 2156 # 2157 # Revision 1.2 2008/03/17 14:55:41 ncq 2158 # - add lots of TODOs 2159 # - better layout 2160 # - set grid cell tooltips 2161 # 2162 # Revision 1.1 2008/03/16 11:57:47 ncq 2163 # - first iteration 2164 # 2165 # 2166