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

Source Code for Module Gnumed.wxpython.gmMeasurementWidgets

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