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

Source Code for Module Gnumed.wxpython.gmMeasurementWidgets

   1  """GNUmed measurement widgets.""" 
   2  #================================================================ 
   3  __author__ = "Karsten Hilbert <Karsten.Hilbert@gmx.net>" 
   4  __license__ = "GPL" 
   5   
   6   
   7  import sys 
   8  import logging 
   9  import datetime as pyDT 
  10  import decimal 
  11  import os 
  12  import subprocess 
  13  import codecs 
  14  import os.path 
  15   
  16   
  17  import wx 
  18  import wx.grid 
  19  import wx.lib.hyperlink 
  20   
  21   
  22  if __name__ == '__main__': 
  23          sys.path.insert(0, '../../') 
  24  from Gnumed.pycommon import gmTools 
  25  from Gnumed.pycommon import gmNetworkTools 
  26  from Gnumed.pycommon import gmI18N 
  27  from Gnumed.pycommon import gmShellAPI 
  28  from Gnumed.pycommon import gmCfg 
  29  from Gnumed.pycommon import gmDateTime 
  30  from Gnumed.pycommon import gmMatchProvider 
  31  from Gnumed.pycommon import gmDispatcher 
  32  from Gnumed.pycommon import gmMimeLib 
  33   
  34  from Gnumed.business import gmPerson 
  35  from Gnumed.business import gmStaff 
  36  from Gnumed.business import gmPathLab 
  37  from Gnumed.business import gmPraxis 
  38  from Gnumed.business import gmLOINC 
  39  from Gnumed.business import gmForms 
  40  from Gnumed.business import gmPersonSearch 
  41  from Gnumed.business import gmOrganization 
  42   
  43  from Gnumed.wxpython import gmRegetMixin 
  44  from Gnumed.wxpython import gmEditArea 
  45  from Gnumed.wxpython import gmPhraseWheel 
  46  from Gnumed.wxpython import gmListWidgets 
  47  from Gnumed.wxpython import gmGuiHelpers 
  48  from Gnumed.wxpython import gmAuthWidgets 
  49  from Gnumed.wxpython import gmOrganizationWidgets 
  50   
  51   
  52  _log = logging.getLogger('gm.ui') 
  53   
  54  #================================================================ 
  55  # HL7 related widgets 
  56  #================================================================ 
57 -def import_Excelleris_HL7(parent=None):
58 59 if parent is None: 60 parent = wx.GetApp().GetTopWindow() 61 62 # select file 63 dlg = wx.FileDialog ( 64 parent = parent, 65 message = 'Import Excelleris HL7 from XML file:', 66 # defaultDir = aDefDir, 67 # defaultFile = fname, 68 wildcard = "xml files|*.xml|XML files|*.XML|all files|*", 69 style = wx.OPEN | wx.FILE_MUST_EXIST 70 ) 71 choice = dlg.ShowModal() 72 xml_name = dlg.GetPath() 73 dlg.Destroy() 74 if choice != wx.ID_OK: 75 return False 76 77 # for now, localize gmHL7 import 78 from Gnumed.business import gmHL7 79 80 hl7 = gmHL7.extract_HL7_from_CDATA(xml_name, u'.//Message') 81 if hl7 is None: 82 gmGuiHelpers.gm_show_info ( 83 u'File [%s]\ndoes not seem to contain HL7 wrapped in XML.' % xml_name, 84 u'Extracting HL7 from XML' 85 ) 86 return False 87 fixed_hl7 = gmHL7.fix_HL7_stupidities(hl7) 88 PID_names = gmHL7.split_HL7_by_PID(fixed_hl7) 89 for name in PID_names: 90 gmHL7.stage_MSH_as_incoming_data(name, source = u'Excelleris')
91 92 #================================================================
93 -def import_HL7(parent=None):
94 95 if parent is None: 96 parent = wx.GetApp().GetTopWindow() 97 98 # select file 99 dlg = wx.FileDialog ( 100 parent = parent, 101 message = 'Import HL7 from file:', 102 # defaultDir = aDefDir, 103 # defaultFile = fname, 104 wildcard = "*.hl7|*.hl7|*.HL7|*.HL7|all files|*", 105 style = wx.OPEN | wx.FILE_MUST_EXIST 106 ) 107 choice = dlg.ShowModal() 108 hl7_name = dlg.GetPath() 109 dlg.Destroy() 110 if choice != wx.ID_OK: 111 return False 112 113 # for now, localize gmHL7 import 114 from Gnumed.business import gmHL7 115 116 fixed_hl7 = gmHL7.fix_HL7_stupidities(hl7_name) 117 PID_names = gmHL7.split_HL7_by_PID(fixed_hl7) 118 for name in PID_names: 119 gmHL7.stage_MSH_as_incoming_data(name, source = u'generic')
120 121 #================================================================
122 -def browse_incoming_unmatched(parent=None):
123 124 # for now, localize gmHL7 import 125 from Gnumed.business import gmHL7 126 127 if parent is None: 128 parent = wx.GetApp().GetTopWindow() 129 #------------------------------------------------------------ 130 def show_hl7(data=None): 131 if data is None: 132 return False 133 filename = data.export_to_file() 134 if filename is None: 135 return False 136 formatted_hl7 = gmHL7.format_hl7_file(filename, return_filename = True) 137 gmMimeLib.call_viewer_on_file(aFile = formatted_hl7, block = False) 138 139 return False
140 #------------------------------------------------------------ 141 def refresh(lctrl): 142 incoming = gmHL7.get_incoming_data() 143 items = [ [ 144 i['data_type'], 145 u'%s, %s (%s) %s' % ( 146 i['lastnames'], 147 i['firstnames'], 148 i['dob'], 149 i['gender'] 150 ), 151 i['external_data_id'], 152 i['pk_incoming_data_unmatched'] 153 ] for i in incoming ] 154 lctrl.set_string_items(items) 155 lctrl.set_data(incoming) 156 #------------------------------------------------------------ 157 gmListWidgets.get_choices_from_list ( 158 parent = parent, 159 msg = None, 160 caption = _('Showing unmatched incoming data'), 161 columns = [ _('Type'), _('Patient'), _('Data ID'), '#' ], 162 single_selection = True, 163 can_return_empty = False, 164 ignore_OK_button = True, 165 refresh_callback = refresh, 166 # edit_callback=None, 167 # new_callback=None, 168 # delete_callback=None, 169 left_extra_button = [_('Show'), _('Show formatted HL7'), show_hl7] 170 # middle_extra_button=None, 171 # right_extra_button=None 172 ) 173 174 #================================================================ 175 # LOINC related widgets 176 #================================================================
177 -def update_loinc_reference_data():
178 179 wx.BeginBusyCursor() 180 181 gmDispatcher.send(signal = 'statustext', msg = _('Updating LOINC data can take quite a while...'), beep = True) 182 183 # download 184 loinc_zip = gmNetworkTools.download_file(url = 'http://www.gnumed.de/downloads/data/loinc/loinctab.zip', suffix = '.zip') 185 if loinc_zip is None: 186 wx.EndBusyCursor() 187 gmGuiHelpers.gm_show_warning ( 188 aTitle = _('Downloading LOINC'), 189 aMessage = _('Error downloading the latest LOINC data.\n') 190 ) 191 return False 192 193 _log.debug('downloaded zipped LOINC data into [%s]', loinc_zip) 194 195 loinc_dir = gmNetworkTools.unzip_data_pack(filename = loinc_zip) 196 197 # split master data file 198 data_fname, license_fname = gmLOINC.split_LOINCDBTXT(input_fname = os.path.join(loinc_dir, 'LOINCDB.TXT')) 199 200 wx.EndBusyCursor() 201 202 conn = gmAuthWidgets.get_dbowner_connection(procedure = _('importing LOINC reference data')) 203 if conn is None: 204 return False 205 206 wx.BeginBusyCursor() 207 208 # import data 209 if gmLOINC.loinc_import(data_fname = data_fname, license_fname = license_fname, conn = conn): 210 gmDispatcher.send(signal = 'statustext', msg = _('Successfully imported LOINC reference data.')) 211 else: 212 gmDispatcher.send(signal = 'statustext', msg = _('Importing LOINC reference data failed.'), beep = True) 213 214 wx.EndBusyCursor() 215 return True
216 217 #================================================================ 218 # convenience functions 219 #================================================================
220 -def call_browser_on_measurement_type(measurement_type=None):
221 222 dbcfg = gmCfg.cCfgSQL() 223 224 url = dbcfg.get ( 225 option = u'external.urls.measurements_search', 226 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace, 227 bias = 'user', 228 default = u"http://www.google.de/search?as_oq=%(search_term)s&num=10&as_sitesearch=laborlexikon.de" 229 ) 230 231 base_url = dbcfg.get2 ( 232 option = u'external.urls.measurements_encyclopedia', 233 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace, 234 bias = 'user', 235 default = u'http://www.laborlexikon.de' 236 ) 237 238 if measurement_type is None: 239 url = base_url 240 241 measurement_type = measurement_type.strip() 242 243 if measurement_type == u'': 244 url = base_url 245 246 url = url % {'search_term': measurement_type} 247 248 gmNetworkTools.open_url_in_browser(url = url)
249 250 #----------------------------------------------------------------
251 -def edit_measurement(parent=None, measurement=None, single_entry=False):
252 ea = cMeasurementEditAreaPnl(parent = parent, id = -1) 253 ea.data = measurement 254 ea.mode = gmTools.coalesce(measurement, 'new', 'edit') 255 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = single_entry) 256 dlg.SetTitle(gmTools.coalesce(measurement, _('Adding new measurement'), _('Editing measurement'))) 257 if dlg.ShowModal() == wx.ID_OK: 258 dlg.Destroy() 259 return True 260 dlg.Destroy() 261 return False
262 263 #----------------------------------------------------------------
264 -def manage_measurements(parent=None, single_selection=False, emr=None):
265 266 if parent is None: 267 parent = wx.GetApp().GetTopWindow() 268 269 if emr is None: 270 emr = gmPerson.gmCurrentPatient().emr 271 272 #------------------------------------------------------------ 273 def edit(measurement=None): 274 return edit_measurement(parent = parent, measurement = measurement, single_entry = True)
275 #------------------------------------------------------------ 276 def delete(measurement): 277 gmPathLab.delete_test_result(result = measurement) 278 return True 279 #------------------------------------------------------------ 280 def do_review(lctrl): 281 data = lctrl.get_selected_item_data() 282 if len(data) == 0: 283 return 284 return review_tests(parent = parent, tests = data) 285 #------------------------------------------------------------ 286 def do_plot(lctrl): 287 data = lctrl.get_selected_item_data() 288 if len(data) == 0: 289 return 290 return plot_measurements(parent = parent, tests = data) 291 #------------------------------------------------------------ 292 def get_tooltip(measurement): 293 return measurement.format(with_review=True, with_evaluation=True, with_ranges=True) 294 #------------------------------------------------------------ 295 def refresh(lctrl): 296 results = emr.get_test_results(order_by = 'clin_when DESC, unified_abbrev, unified_name') 297 items = [ [ 298 gmDateTime.pydt_strftime ( 299 r['clin_when'], 300 '%Y %b %d %H:%M', 301 accuracy = gmDateTime.acc_minutes 302 ), 303 r['unified_abbrev'], 304 u'%s%s%s' % ( 305 r['unified_val'], 306 gmTools.coalesce(r['val_unit'], u'', u' %s'), 307 gmTools.coalesce(r['abnormality_indicator'], u'', u' %s') 308 ), 309 r['unified_name'], 310 gmTools.coalesce(r['comment'], u''), 311 r['pk_test_result'] 312 ] for r in results ] 313 lctrl.set_string_items(items) 314 lctrl.set_data(results) 315 #------------------------------------------------------------ 316 msg = _('Test results (ordered reverse-chronologically)') 317 318 return gmListWidgets.get_choices_from_list ( 319 parent = parent, 320 msg = msg, 321 caption = _('Showing test results.'), 322 columns = [ _('When'), _('Abbrev'), _('Value'), _('Name'), _('Comment'), u'#' ], 323 single_selection = single_selection, 324 can_return_empty = False, 325 refresh_callback = refresh, 326 edit_callback = edit, 327 new_callback = edit, 328 delete_callback = delete, 329 list_tooltip_callback = get_tooltip, 330 left_extra_button = (_('Review'), _('Review current selection'), do_review, True), 331 middle_extra_button = (_('Plot'), _('Plot current selection'), do_plot, True) 332 ) 333 334 #================================================================
335 -def configure_default_gnuplot_template(parent=None):
336 337 from Gnumed.wxpython import gmFormWidgets 338 339 if parent is None: 340 parent = wx.GetApp().GetTopWindow() 341 342 template = gmFormWidgets.manage_form_templates ( 343 parent = parent, 344 active_only = True, 345 template_types = [u'gnuplot script'] 346 ) 347 348 option = u'form_templates.default_gnuplot_template' 349 350 if template is None: 351 gmDispatcher.send(signal = 'statustext', msg = _('No default Gnuplot script template selected.'), beep = True) 352 return None 353 354 if template['engine'] != u'G': 355 gmDispatcher.send(signal = 'statustext', msg = _('No default Gnuplot script template selected.'), beep = True) 356 return None 357 358 dbcfg = gmCfg.cCfgSQL() 359 dbcfg.set ( 360 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace, 361 option = option, 362 value = u'%s - %s' % (template['name_long'], template['external_version']) 363 ) 364 return template
365 366 #============================================================
367 -def get_default_gnuplot_template(parent = None):
368 369 option = u'form_templates.default_gnuplot_template' 370 371 dbcfg = gmCfg.cCfgSQL() 372 373 # load from option 374 default_template_name = dbcfg.get2 ( 375 option = option, 376 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace, 377 bias = 'user' 378 ) 379 380 # not configured -> try to configure 381 if default_template_name is None: 382 gmDispatcher.send('statustext', msg = _('No default Gnuplot template configured.'), beep = False) 383 default_template = configure_default_gnuplot_template(parent = parent) 384 # still not configured -> return 385 if default_template is None: 386 gmGuiHelpers.gm_show_error ( 387 aMessage = _('There is no default Gnuplot one-type script template configured.'), 388 aTitle = _('Plotting test results') 389 ) 390 return None 391 return default_template 392 393 # now it MUST be configured (either newly or previously) 394 # but also *validly* ? 395 try: 396 name, ver = default_template_name.split(u' - ') 397 except: 398 # not valid 399 _log.exception('problem splitting Gnuplot script template name [%s]', default_template_name) 400 gmDispatcher.send(signal = 'statustext', msg = _('Problem loading Gnuplot script template.'), beep = True) 401 return None 402 403 default_template = gmForms.get_form_template(name_long = name, external_version = ver) 404 if default_template is None: 405 default_template = configure_default_gnuplot_template(parent = parent) 406 # still not configured -> return 407 if default_template is None: 408 gmGuiHelpers.gm_show_error ( 409 aMessage = _('Cannot load default Gnuplot script template [%s - %s]') % (name, ver), 410 aTitle = _('Plotting test results') 411 ) 412 return None 413 414 return default_template
415 416 #----------------------------------------------------------------
417 -def plot_measurements(parent=None, tests=None, format=None, show_year = True, use_default_template=False):
418 419 from Gnumed.wxpython import gmFormWidgets 420 421 # only valid for one-type plotting 422 if use_default_template: 423 template = get_default_gnuplot_template() 424 else: 425 template = gmFormWidgets.manage_form_templates ( 426 parent = parent, 427 active_only = True, 428 template_types = [u'gnuplot script'] 429 ) 430 431 if template is None: 432 gmGuiHelpers.gm_show_error ( 433 aMessage = _('Cannot plot without a plot script.'), 434 aTitle = _('Plotting test results') 435 ) 436 return False 437 438 fname_data = gmPathLab.export_results_for_gnuplot(results = tests, show_year = show_year) 439 440 script = template.instantiate() 441 script.data_filename = fname_data 442 script.generate_output(format = format) # Gnuplot output terminal, wxt = wxWidgets window
443 444 #----------------------------------------------------------------
445 -def plot_adjacent_measurements(parent=None, test=None, format=None, show_year=True, plot_singular_result=True, use_default_template=False):
446 447 earlier, later = test.get_adjacent_results(desired_earlier_results = 2, desired_later_results = 2) 448 results2plot = [] 449 if earlier is not None: 450 results2plot.extend(earlier) 451 results2plot.append(test) 452 if later is not None: 453 results2plot.extend(later) 454 if len(results2plot) == 1: 455 if not plot_singular_result: 456 return 457 plot_measurements ( 458 parent = parent, 459 tests = results2plot, 460 format = format, 461 show_year = show_year, 462 use_default_template = use_default_template 463 )
464 #================================================================ 465 #from Gnumed.wxGladeWidgets import wxgPrimaryCareVitalsInputPnl 466 467 # Taillenumfang: Mitte zwischen unterster Rippe und 468 # hoechstem Teil des Beckenkamms 469 # Maenner: maessig: 94-102, deutlich: > 102 .. erhoeht 470 # Frauen: maessig: 80-88, deutlich: > 88 .. erhoeht 471 472 #================================================================ 473 # display widgets 474 #================================================================
475 -class cMeasurementsGrid(wx.grid.Grid):
476 """A grid class for displaying measurment results. 477 478 - does NOT listen to the currently active patient 479 - thereby it can display any patient at any time 480 """ 481 # FIXME: sort-by-battery 482 # FIXME: filter-by-battery 483 # FIXME: filter out empty 484 # FIXME: filter by tests of a selected date 485 # FIXME: dates DESC/ASC by cfg 486 # FIXME: mouse over column header: display date info
487 - def __init__(self, *args, **kwargs):
488 489 wx.grid.Grid.__init__(self, *args, **kwargs) 490 491 self.__patient = None 492 self.__panel_to_show = None 493 self.__show_by_panel = False 494 self.__cell_data = {} 495 self.__row_label_data = [] 496 497 self.__prev_row = None 498 self.__prev_col = None 499 self.__prev_label_row = None 500 self.__date_format = str((_('lab_grid_date_format::%Y\n%b %d')).lstrip('lab_grid_date_format::')) 501 502 self.__init_ui() 503 self.__register_events()
504 #------------------------------------------------------------ 505 # external API 506 #------------------------------------------------------------
507 - def delete_current_selection(self):
508 if not self.IsSelection(): 509 gmDispatcher.send(signal = u'statustext', msg = _('No results selected for deletion.')) 510 return True 511 512 selected_cells = self.get_selected_cells() 513 if len(selected_cells) > 20: 514 results = None 515 msg = _( 516 'There are %s results marked for deletion.\n' 517 '\n' 518 'Are you sure you want to delete these results ?' 519 ) % len(selected_cells) 520 else: 521 results = self.__cells_to_data(cells = selected_cells, exclude_multi_cells = False) 522 txt = u'\n'.join([ u'%s %s (%s): %s %s%s' % ( 523 r['clin_when'].strftime('%x %H:%M').decode(gmI18N.get_encoding()), 524 r['unified_abbrev'], 525 r['unified_name'], 526 r['unified_val'], 527 r['val_unit'], 528 gmTools.coalesce(r['abnormality_indicator'], u'', u' (%s)') 529 ) for r in results 530 ]) 531 msg = _( 532 'The following results are marked for deletion:\n' 533 '\n' 534 '%s\n' 535 '\n' 536 'Are you sure you want to delete these results ?' 537 ) % txt 538 539 dlg = gmGuiHelpers.c2ButtonQuestionDlg ( 540 self, 541 -1, 542 caption = _('Deleting test results'), 543 question = msg, 544 button_defs = [ 545 {'label': _('Delete'), 'tooltip': _('Yes, delete all the results.'), 'default': False}, 546 {'label': _('Cancel'), 'tooltip': _('No, do NOT delete any results.'), 'default': True} 547 ] 548 ) 549 decision = dlg.ShowModal() 550 551 if decision == wx.ID_YES: 552 if results is None: 553 results = self.__cells_to_data(cells = selected_cells, exclude_multi_cells = False) 554 for result in results: 555 gmPathLab.delete_test_result(result)
556 #------------------------------------------------------------
557 - def sign_current_selection(self):
558 if not self.IsSelection(): 559 gmDispatcher.send(signal = u'statustext', msg = _('Cannot sign results. No results selected.')) 560 return True 561 562 selected_cells = self.get_selected_cells() 563 tests = self.__cells_to_data(cells = selected_cells, exclude_multi_cells = False) 564 565 return review_tests(parent = self, tests = tests)
566 #------------------------------------------------------------
567 - def plot_current_selection(self):
568 569 if not self.IsSelection(): 570 gmDispatcher.send(signal = u'statustext', msg = _('Cannot plot results. No results selected.')) 571 return True 572 573 tests = self.__cells_to_data ( 574 cells = self.get_selected_cells(), 575 exclude_multi_cells = False, 576 auto_include_multi_cells = True 577 ) 578 579 plot_measurements(parent = self, tests = tests)
580 #------------------------------------------------------------
581 - def get_selected_cells(self):
582 583 sel_block_top_left = self.GetSelectionBlockTopLeft() 584 sel_block_bottom_right = self.GetSelectionBlockBottomRight() 585 sel_cols = self.GetSelectedCols() 586 sel_rows = self.GetSelectedRows() 587 588 selected_cells = [] 589 590 # individually selected cells (ctrl-click) 591 selected_cells += self.GetSelectedCells() 592 593 # selected rows 594 selected_cells += list ( 595 (row, col) 596 for row in sel_rows 597 for col in xrange(self.GetNumberCols()) 598 ) 599 600 # selected columns 601 selected_cells += list ( 602 (row, col) 603 for row in xrange(self.GetNumberRows()) 604 for col in sel_cols 605 ) 606 607 # selection blocks 608 for top_left, bottom_right in zip(self.GetSelectionBlockTopLeft(), self.GetSelectionBlockBottomRight()): 609 selected_cells += [ 610 (row, col) 611 for row in xrange(top_left[0], bottom_right[0] + 1) 612 for col in xrange(top_left[1], bottom_right[1] + 1) 613 ] 614 615 return set(selected_cells)
616 #------------------------------------------------------------
617 - def select_cells(self, unsigned_only=False, accountables_only=False, keep_preselections=False):
618 """Select a range of cells according to criteria. 619 620 unsigned_only: include only those which are not signed at all yet 621 accountable_only: include only those for which the current user is responsible 622 keep_preselections: broaden (rather than replace) the range of selected cells 623 624 Combinations are powerful ! 625 """ 626 wx.BeginBusyCursor() 627 self.BeginBatch() 628 629 if not keep_preselections: 630 self.ClearSelection() 631 632 for col_idx in self.__cell_data.keys(): 633 for row_idx in self.__cell_data[col_idx].keys(): 634 # loop over results in cell and only include 635 # those multi-value cells that are not ambiguous 636 do_not_include = False 637 for result in self.__cell_data[col_idx][row_idx]: 638 if unsigned_only: 639 if result['reviewed']: 640 do_not_include = True 641 break 642 if accountables_only: 643 if not result['you_are_responsible']: 644 do_not_include = True 645 break 646 if do_not_include: 647 continue 648 649 self.SelectBlock(row_idx, col_idx, row_idx, col_idx, addToSelected = True) 650 651 self.EndBatch() 652 wx.EndBusyCursor()
653 #------------------------------------------------------------
654 - def repopulate_grid(self):
655 self.empty_grid() 656 if self.__patient is None: 657 return 658 659 if self.__show_by_panel: 660 self.__repopulate_grid_by_panel() 661 return 662 663 self.__repopulate_grid_all_results()
664 #------------------------------------------------------------
665 - def __repopulate_grid_by_panel(self):
666 667 if self.__panel_to_show is None: 668 return 669 670 emr = self.__patient.get_emr() 671 672 # rows 673 self.__row_label_data = self.__panel_to_show.test_types 674 row_labels = [ u'%s%s' % ( 675 gmTools.bool2subst(test['is_fake_meta_type'], u'', gmTools.u_sum, u''), 676 test['unified_abbrev'] 677 ) for test in self.__row_label_data 678 ] 679 if len(row_labels) == 0: 680 return 681 682 # columns 683 column_labels = [ 684 date[0].strftime(self.__date_format) for date in emr.get_dates_for_results ( 685 tests = self.__panel_to_show['pk_test_types'], 686 # FIXME: make configurable 687 reverse_chronological = True 688 ) 689 ] 690 results = emr.get_test_results_by_date ( 691 tests = self.__panel_to_show['pk_test_types'], 692 # FIXME: make configurable 693 reverse_chronological = True 694 ) 695 696 self.BeginBatch() 697 698 # rows 699 self.AppendRows(numRows = len(row_labels)) 700 for row_idx in range(len(row_labels)): 701 self.SetRowLabelValue(row_idx, row_labels[row_idx]) 702 703 # columns 704 self.AppendCols(numCols = len(column_labels)) 705 for date_idx in range(len(column_labels)): 706 self.SetColLabelValue(date_idx, column_labels[date_idx]) 707 708 # cell values (list of test results) 709 for result in results: 710 row = row_labels.index(u'%s%s' % ( 711 gmTools.bool2subst(result['is_fake_meta_type'], u'', gmTools.u_sum, u''), 712 result['unified_abbrev'] 713 )) 714 col = column_labels.index(result['clin_when'].strftime(self.__date_format)) 715 716 try: 717 self.__cell_data[col] 718 except KeyError: 719 self.__cell_data[col] = {} 720 721 # the tooltip always shows the youngest sub result details 722 if self.__cell_data[col].has_key(row): 723 self.__cell_data[col][row].append(result) 724 self.__cell_data[col][row].sort(key = lambda x: x['clin_when'], reverse = True) 725 else: 726 self.__cell_data[col][row] = [result] 727 728 # rebuild cell display string 729 vals2display = [] 730 cell_has_out_of_bounds_value = False 731 for sub_result in self.__cell_data[col][row]: 732 733 if sub_result.is_considered_abnormal: 734 cell_has_out_of_bounds_value = True 735 736 abnormality_indicator = sub_result.formatted_abnormality_indicator 737 if abnormality_indicator is None: 738 abnormality_indicator = u'' 739 if abnormality_indicator != u'': 740 abnormality_indicator = u' (%s)' % abnormality_indicator[:3] 741 742 missing_review = False 743 # warn on missing review if 744 # a) no review at all exists or 745 if not sub_result['reviewed']: 746 missing_review = True 747 # b) there is a review but 748 else: 749 # current user is reviewer and hasn't reviewed 750 if sub_result['you_are_responsible'] and not sub_result['review_by_you']: 751 missing_review = True 752 753 # can we display the full sub_result length ? 754 if len(sub_result['unified_val']) > 8: 755 tmp = u'%.7s%s' % (sub_result['unified_val'][:7], gmTools.u_ellipsis) 756 else: 757 tmp = u'%.8s' % sub_result['unified_val'][:8] 758 759 # abnormal ? 760 tmp = u'%s%.6s' % (tmp, abnormality_indicator) 761 762 # is there a comment ? 763 has_sub_result_comment = gmTools.coalesce ( 764 gmTools.coalesce(sub_result['note_test_org'], sub_result['comment']), 765 u'' 766 ).strip() != u'' 767 if has_sub_result_comment: 768 tmp = u'%s %s' % (tmp, gmTools.u_ellipsis) 769 770 # lacking a review ? 771 if missing_review: 772 tmp = u'%s %s' % (tmp, gmTools.u_writing_hand) 773 else: 774 if sub_result['is_clinically_relevant']: 775 tmp += u' !' 776 777 # part of a multi-result cell ? 778 if len(self.__cell_data[col][row]) > 1: 779 tmp = u'%s %s' % (sub_result['clin_when'].strftime('%H:%M'), tmp) 780 781 vals2display.append(tmp) 782 783 self.SetCellValue(row, col, u'\n'.join(vals2display)) 784 self.SetCellAlignment(row, col, horiz = wx.ALIGN_RIGHT, vert = wx.ALIGN_CENTRE) 785 # We used to color text in cells holding abnormals 786 # in firebrick red but that would color ALL text (including 787 # normals) and not only the abnormals within that 788 # cell. Shading, however, only says that *something* 789 # inside that cell is worthy of attention. 790 #if sub_result_relevant: 791 # font = self.GetCellFont(row, col) 792 # self.SetCellTextColour(row, col, 'firebrick') 793 # font.SetWeight(wx.FONTWEIGHT_BOLD) 794 # self.SetCellFont(row, col, font) 795 if cell_has_out_of_bounds_value: 796 self.SetCellBackgroundColour(row, col, 'cornflower blue') 797 798 self.AutoSize() 799 self.EndBatch() 800 return
801 #------------------------------------------------------------
803 emr = self.__patient.get_emr() 804 805 self.__row_label_data = emr.get_test_types_for_results() 806 test_type_labels = [ u'%s%s' % ( 807 gmTools.bool2subst(test['is_fake_meta_type'], u'', gmTools.u_sum, u''), 808 test['unified_abbrev'] 809 ) for test in self.__row_label_data 810 ] 811 if len(test_type_labels) == 0: 812 return 813 814 test_date_labels = [ date[0].strftime(self.__date_format) for date in emr.get_dates_for_results() ] 815 results = emr.get_test_results_by_date() 816 817 self.BeginBatch() 818 819 # rows 820 self.AppendRows(numRows = len(test_type_labels)) 821 for row_idx in range(len(test_type_labels)): 822 self.SetRowLabelValue(row_idx, test_type_labels[row_idx]) 823 824 # columns 825 self.AppendCols(numCols = len(test_date_labels)) 826 for date_idx in range(len(test_date_labels)): 827 self.SetColLabelValue(date_idx, test_date_labels[date_idx]) 828 829 # cell values (list of test results) 830 for result in results: 831 row = test_type_labels.index(u'%s%s' % ( 832 gmTools.bool2subst(result['is_fake_meta_type'], u'', gmTools.u_sum, u''), 833 result['unified_abbrev'] 834 )) 835 col = test_date_labels.index(result['clin_when'].strftime(self.__date_format)) 836 837 try: 838 self.__cell_data[col] 839 except KeyError: 840 self.__cell_data[col] = {} 841 842 # the tooltip always shows the youngest sub result details 843 if self.__cell_data[col].has_key(row): 844 self.__cell_data[col][row].append(result) 845 self.__cell_data[col][row].sort(key = lambda x: x['clin_when'], reverse = True) 846 else: 847 self.__cell_data[col][row] = [result] 848 849 # rebuild cell display string 850 vals2display = [] 851 cell_has_out_of_bounds_value = False 852 for sub_result in self.__cell_data[col][row]: 853 854 #if (sub_result.is_considered_lowered is True) or (sub_result.is_considered_elevated is True): 855 if sub_result.is_considered_abnormal: 856 cell_has_out_of_bounds_value = True 857 858 abnormality_indicator = sub_result.formatted_abnormality_indicator 859 if abnormality_indicator is None: 860 abnormality_indicator = u'' 861 if abnormality_indicator != u'': 862 abnormality_indicator = u' (%s)' % abnormality_indicator[:3] 863 864 # is the sub_result relevant clinically ? 865 # FIXME: take into account primary_GP once we support that 866 sub_result_relevant = sub_result['is_clinically_relevant'] 867 if sub_result_relevant is None: 868 # FIXME: calculate from clinical range 869 sub_result_relevant = False 870 871 missing_review = False 872 # warn on missing review if 873 # a) no review at all exists or 874 if not sub_result['reviewed']: 875 missing_review = True 876 # b) there is a review but 877 else: 878 # current user is reviewer and hasn't reviewed 879 if sub_result['you_are_responsible'] and not sub_result['review_by_you']: 880 missing_review = True 881 882 # can we display the full sub_result length ? 883 if len(sub_result['unified_val']) > 8: 884 tmp = u'%.7s%s' % (sub_result['unified_val'][:7], gmTools.u_ellipsis) 885 else: 886 tmp = u'%.8s' % sub_result['unified_val'][:8] 887 888 # abnormal ? 889 tmp = u'%s%.6s' % (tmp, abnormality_indicator) 890 891 # is there a comment ? 892 has_sub_result_comment = gmTools.coalesce ( 893 gmTools.coalesce(sub_result['note_test_org'], sub_result['comment']), 894 u'' 895 ).strip() != u'' 896 if has_sub_result_comment: 897 tmp = u'%s %s' % (tmp, gmTools.u_ellipsis) 898 899 # lacking a review ? 900 if missing_review: 901 tmp = u'%s %s' % (tmp, gmTools.u_writing_hand) 902 903 # part of a multi-result cell ? 904 if len(self.__cell_data[col][row]) > 1: 905 tmp = u'%s %s' % (sub_result['clin_when'].strftime('%H:%M'), tmp) 906 907 vals2display.append(tmp) 908 909 self.SetCellValue(row, col, u'\n'.join(vals2display)) 910 self.SetCellAlignment(row, col, horiz = wx.ALIGN_RIGHT, vert = wx.ALIGN_CENTRE) 911 # FIXME: what about partial sub results being relevant ?? 912 if sub_result_relevant: 913 font = self.GetCellFont(row, col) 914 self.SetCellTextColour(row, col, 'firebrick') 915 font.SetWeight(wx.FONTWEIGHT_BOLD) 916 self.SetCellFont(row, col, font) 917 if cell_has_out_of_bounds_value: 918 self.SetCellBackgroundColour(row, col, 'cornflower blue') 919 920 self.AutoSize() 921 self.EndBatch() 922 return
923 #------------------------------------------------------------
924 - def empty_grid(self):
925 self.BeginBatch() 926 self.ClearGrid() 927 # Windows cannot do nothing, it rather decides to assert() 928 # on thinking it is supposed to do nothing 929 if self.GetNumberRows() > 0: 930 self.DeleteRows(pos = 0, numRows = self.GetNumberRows()) 931 if self.GetNumberCols() > 0: 932 self.DeleteCols(pos = 0, numCols = self.GetNumberCols()) 933 self.EndBatch() 934 self.__cell_data = {} 935 self.__row_label_data = []
936 #------------------------------------------------------------
937 - def get_row_tooltip(self, row=None):
938 # include details about test types included ? 939 940 # sometimes, for some reason, there is no row and 941 # wxPython still tries to find a tooltip for it 942 try: 943 tt = self.__row_label_data[row] 944 except IndexError: 945 return u' ' 946 947 if tt['is_fake_meta_type']: 948 return tt.format(patient = self.__patient.ID) 949 950 meta_tt = tt.meta_test_type 951 txt = meta_tt.format(with_tests = True, patient = self.__patient.ID) 952 953 return txt
954 #------------------------------------------------------------
955 - def get_cell_tooltip(self, col=None, row=None):
956 try: 957 d = self.__cell_data[col][row] 958 except KeyError: 959 # FIXME: maybe display the most recent or when the most recent was ? 960 d = None 961 962 if d is None: 963 return u' ' 964 965 is_multi_cell = False 966 if len(d) > 1: 967 is_multi_cell = True 968 d = d[0] 969 970 tt = u'' 971 # header 972 if is_multi_cell: 973 tt += _(u'Details of most recent (topmost) result ! \n') 974 tt += d.format(with_review = True, with_evaluation = True, with_ranges = True) 975 return tt
976 #------------------------------------------------------------ 977 # internal helpers 978 #------------------------------------------------------------
979 - def __init_ui(self):
980 self.CreateGrid(0, 1) 981 self.EnableEditing(0) 982 self.EnableDragGridSize(1) 983 self.SetMinSize(wx.DefaultSize) 984 985 # column labels 986 # setting this screws up the labels: they are cut off and displaced 987 #self.SetColLabelAlignment(wx.ALIGN_CENTER, wx.ALIGN_BOTTOM) 988 989 # row labels 990 self.SetRowLabelSize(wx.grid.GRID_AUTOSIZE) # starting with 2.8.8 991 #self.SetRowLabelSize(150) 992 self.SetRowLabelAlignment(horiz = wx.ALIGN_LEFT, vert = wx.ALIGN_CENTRE) 993 font = self.GetLabelFont() 994 font.SetWeight(wx.FONTWEIGHT_LIGHT) 995 self.SetLabelFont(font) 996 997 # add link to left upper corner 998 dbcfg = gmCfg.cCfgSQL() 999 url = dbcfg.get2 ( 1000 option = u'external.urls.measurements_encyclopedia', 1001 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace, 1002 bias = 'user', 1003 default = u'http://www.laborlexikon.de' 1004 ) 1005 1006 self.__WIN_corner = self.GetGridCornerLabelWindow() # a wx.Window instance 1007 1008 LNK_lab = wx.lib.hyperlink.HyperLinkCtrl ( 1009 self.__WIN_corner, 1010 -1, 1011 label = _('Tests'), 1012 style = wx.HL_DEFAULT_STYLE # wx.TE_READONLY|wx.TE_CENTRE| wx.NO_BORDER | 1013 ) 1014 LNK_lab.SetURL(url) 1015 LNK_lab.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_BACKGROUND)) 1016 LNK_lab.SetToolTipString(_( 1017 'Navigate to an encyclopedia of measurements\n' 1018 'and test methods on the web.\n' 1019 '\n' 1020 ' <%s>' 1021 ) % url) 1022 1023 SZR_inner = wx.BoxSizer(wx.HORIZONTAL) 1024 SZR_inner.Add((20, 20), 1, wx.EXPAND, 0) # spacer 1025 SZR_inner.Add(LNK_lab, 0, wx.ALIGN_CENTER_VERTICAL, 0) #wx.ALIGN_CENTER wx.EXPAND 1026 SZR_inner.Add((20, 20), 1, wx.EXPAND, 0) # spacer 1027 1028 SZR_corner = wx.BoxSizer(wx.VERTICAL) 1029 SZR_corner.Add((20, 20), 1, wx.EXPAND, 0) # spacer 1030 SZR_corner.AddWindow(SZR_inner, 0, wx.EXPAND) # inner sizer with centered hyperlink 1031 SZR_corner.Add((20, 20), 1, wx.EXPAND, 0) # spacer 1032 1033 self.__WIN_corner.SetSizer(SZR_corner) 1034 SZR_corner.Fit(self.__WIN_corner)
1035 #------------------------------------------------------------
1036 - def __resize_corner_window(self, evt):
1037 self.__WIN_corner.Layout()
1038 #------------------------------------------------------------
1039 - def __cells_to_data(self, cells=None, exclude_multi_cells=False, auto_include_multi_cells=False):
1040 """List of <cells> must be in row / col order.""" 1041 data = [] 1042 for row, col in cells: 1043 try: 1044 # cell data is stored col / row 1045 data_list = self.__cell_data[col][row] 1046 except KeyError: 1047 continue 1048 1049 if len(data_list) == 1: 1050 data.append(data_list[0]) 1051 continue 1052 1053 if exclude_multi_cells: 1054 gmDispatcher.send(signal = u'statustext', msg = _('Excluding multi-result field from further processing.')) 1055 continue 1056 1057 if auto_include_multi_cells: 1058 data.extend(data_list) 1059 continue 1060 1061 data_to_include = self.__get_choices_from_multi_cell(cell_data = data_list) 1062 if data_to_include is None: 1063 continue 1064 data.extend(data_to_include) 1065 1066 return data
1067 #------------------------------------------------------------
1068 - def __get_choices_from_multi_cell(self, cell_data=None, single_selection=False):
1069 data = gmListWidgets.get_choices_from_list ( 1070 parent = self, 1071 msg = _( 1072 'Your selection includes a field with multiple results.\n' 1073 '\n' 1074 'Please select the individual results you want to work on:' 1075 ), 1076 caption = _('Selecting test results'), 1077 choices = [ [d['clin_when'], u'%s: %s' % (d['abbrev_tt'], d['name_tt']), d['unified_val']] for d in cell_data ], 1078 columns = [ _('Date / Time'), _('Test'), _('Result') ], 1079 data = cell_data, 1080 single_selection = single_selection 1081 ) 1082 return data
1083 #------------------------------------------------------------ 1084 # event handling 1085 #------------------------------------------------------------
1086 - def __register_events(self):
1087 # dynamic tooltips: GridWindow, GridRowLabelWindow, GridColLabelWindow, GridCornerLabelWindow 1088 self.GetGridWindow().Bind(wx.EVT_MOTION, self.__on_mouse_over_cells) 1089 self.GetGridRowLabelWindow().Bind(wx.EVT_MOTION, self.__on_mouse_over_row_labels) 1090 #self.GetGridColLabelWindow().Bind(wx.EVT_MOTION, self.__on_mouse_over_col_labels) 1091 1092 # sizing left upper corner window 1093 self.Bind(wx.EVT_SIZE, self.__resize_corner_window) 1094 1095 # editing cells 1096 self.Bind(wx.grid.EVT_GRID_CELL_LEFT_DCLICK, self.__on_cell_left_dclicked)
1097 #------------------------------------------------------------
1098 - def __on_cell_left_dclicked(self, evt):
1099 col = evt.GetCol() 1100 row = evt.GetRow() 1101 1102 # empty cell, perhaps ? 1103 try: 1104 self.__cell_data[col][row] 1105 except KeyError: 1106 # FIXME: invoke editor for adding value for day of that column 1107 # FIMXE: and test of that row 1108 return 1109 1110 if len(self.__cell_data[col][row]) > 1: 1111 data = self.__get_choices_from_multi_cell(cell_data = self.__cell_data[col][row], single_selection = True) 1112 else: 1113 data = self.__cell_data[col][row][0] 1114 1115 if data is None: 1116 return 1117 1118 edit_measurement(parent = self, measurement = data, single_entry = True)
1119 #------------------------------------------------------------ 1120 # def OnMouseMotionRowLabel(self, evt): 1121 # x, y = self.CalcUnscrolledPosition(evt.GetPosition()) 1122 # row = self.YToRow(y) 1123 # label = self.table().GetRowHelpValue(row) 1124 # self.GetGridRowLabelWindow().SetToolTipString(label or "") 1125 # evt.Skip()
1126 - def __on_mouse_over_row_labels(self, evt):
1127 1128 # Use CalcUnscrolledPosition() to get the mouse position within the 1129 # entire grid including what's offscreen 1130 x, y = self.CalcUnscrolledPosition(evt.GetX(), evt.GetY()) 1131 1132 row = self.YToRow(y) 1133 1134 if self.__prev_label_row == row: 1135 return 1136 1137 self.__prev_label_row == row 1138 1139 evt.GetEventObject().SetToolTipString(self.get_row_tooltip(row = row))
1140 #------------------------------------------------------------ 1141 # def OnMouseMotionColLabel(self, evt): 1142 # x, y = self.CalcUnscrolledPosition(evt.GetPosition()) 1143 # col = self.XToCol(x) 1144 # label = self.table().GetColHelpValue(col) 1145 # self.GetGridColLabelWindow().SetToolTipString(label or "") 1146 # evt.Skip() 1147 #------------------------------------------------------------
1148 - def __on_mouse_over_cells(self, evt):
1149 """Calculate where the mouse is and set the tooltip dynamically.""" 1150 1151 # Use CalcUnscrolledPosition() to get the mouse position within the 1152 # entire grid including what's offscreen 1153 x, y = self.CalcUnscrolledPosition(evt.GetX(), evt.GetY()) 1154 1155 # use this logic to prevent tooltips outside the actual cells 1156 # apply to GetRowSize, too 1157 # tot = 0 1158 # for col in xrange(self.NumberCols): 1159 # tot += self.GetColSize(col) 1160 # if xpos <= tot: 1161 # self.tool_tip.Tip = 'Tool tip for Column %s' % ( 1162 # self.GetColLabelValue(col)) 1163 # break 1164 # else: # mouse is in label area beyond the right-most column 1165 # self.tool_tip.Tip = '' 1166 1167 row, col = self.XYToCell(x, y) 1168 1169 if (row == self.__prev_row) and (col == self.__prev_col): 1170 return 1171 1172 self.__prev_row = row 1173 self.__prev_col = col 1174 1175 evt.GetEventObject().SetToolTipString(self.get_cell_tooltip(col=col, row=row))
1176 #------------------------------------------------------------ 1177 # properties 1178 #------------------------------------------------------------
1179 - def _set_patient(self, patient):
1180 self.__patient = patient 1181 self.repopulate_grid()
1182 1183 patient = property(lambda x:x, _set_patient) 1184 #------------------------------------------------------------
1185 - def _set_panel_to_show(self, panel):
1186 self.__panel_to_show = panel 1187 self.repopulate_grid()
1188 1189 panel_to_show = property(lambda x:x, _set_panel_to_show) 1190 #------------------------------------------------------------
1191 - def _set_show_by_panel(self, show_by_panel):
1192 self.__show_by_panel = show_by_panel 1193 self.repopulate_grid()
1194 1195 show_by_panel = property(lambda x:x, _set_show_by_panel)
1196 #================================================================ 1197 from Gnumed.wxGladeWidgets import wxgMeasurementsPnl 1198
1199 -class cMeasurementsPnl(wxgMeasurementsPnl.wxgMeasurementsPnl, gmRegetMixin.cRegetOnPaintMixin):
1200 """Panel holding a grid with lab data. Used as notebook page.""" 1201
1202 - def __init__(self, *args, **kwargs):
1203 1204 wxgMeasurementsPnl.wxgMeasurementsPnl.__init__(self, *args, **kwargs) 1205 gmRegetMixin.cRegetOnPaintMixin.__init__(self) 1206 self.__init_ui() 1207 self.__register_interests()
1208 #-------------------------------------------------------- 1209 # event handling 1210 #--------------------------------------------------------
1211 - def __register_interests(self):
1212 gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._on_pre_patient_selection) 1213 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection) 1214 gmDispatcher.connect(signal = u'clin.test_result_mod_db', receiver = self._schedule_data_reget) 1215 gmDispatcher.connect(signal = u'clin.reviewed_test_results_mod_db', receiver = self._schedule_data_reget)
1216 #--------------------------------------------------------
1217 - def _on_post_patient_selection(self):
1218 wx.CallAfter(self.__on_post_patient_selection)
1219 #--------------------------------------------------------
1220 - def __on_post_patient_selection(self):
1221 self._schedule_data_reget()
1222 #--------------------------------------------------------
1223 - def _on_pre_patient_selection(self):
1224 wx.CallAfter(self.__on_pre_patient_selection)
1225 #--------------------------------------------------------
1226 - def __on_pre_patient_selection(self):
1227 self.data_grid.patient = None 1228 self.panel_data_grid.patient = None
1229 #--------------------------------------------------------
1230 - def _on_add_button_pressed(self, event):
1231 edit_measurement(parent = self, measurement = None)
1232 #--------------------------------------------------------
1233 - def _on_list_button_pressed(self, event):
1234 event.Skip() 1235 manage_measurements(parent = self, single_selection = True)#, emr = pat.emr)
1236 #--------------------------------------------------------
1237 - def _on_review_button_pressed(self, evt):
1238 self.PopupMenu(self.__action_button_popup)
1239 #--------------------------------------------------------
1240 - def _on_select_button_pressed(self, evt):
1241 if self._RBTN_my_unsigned.GetValue() is True: 1242 self.data_grid.select_cells(unsigned_only = True, accountables_only = True, keep_preselections = False) 1243 elif self._RBTN_all_unsigned.GetValue() is True: 1244 self.data_grid.select_cells(unsigned_only = True, accountables_only = False, keep_preselections = False)
1245 #--------------------------------------------------------
1246 - def _on_manage_panels_button_pressed(self, event):
1247 manage_test_panels(parent = self)
1248 #--------------------------------------------------------
1249 - def __on_sign_current_selection(self, evt):
1250 self.data_grid.sign_current_selection()
1251 #--------------------------------------------------------
1252 - def __on_plot_current_selection(self, evt):
1253 self.data_grid.plot_current_selection()
1254 #--------------------------------------------------------
1255 - def __on_delete_current_selection(self, evt):
1256 self.data_grid.delete_current_selection()
1257 #--------------------------------------------------------
1258 - def _on_panel_selected(self, panel):
1259 wx.CallAfter(self.__on_panel_selected, panel=panel)
1260 #--------------------------------------------------------
1261 - def __on_panel_selected(self, panel):
1262 if panel is None: 1263 self._TCTRL_panel_comment.SetValue(u'') 1264 self.panel_data_grid.panel_to_show = None 1265 self.panel_data_grid.Hide() 1266 else: 1267 pnl = self._PRW_panel.GetData(as_instance = True) 1268 self._TCTRL_panel_comment.SetValue(gmTools.coalesce ( 1269 pnl['comment'], 1270 u'' 1271 )) 1272 self.panel_data_grid.panel_to_show = pnl 1273 self.panel_data_grid.Show() 1274 self.Layout()
1275 #self.Refresh() 1276 #--------------------------------------------------------
1278 wx.CallAfter(self.__on_panel_selection_modified)
1279 #--------------------------------------------------------
1281 self._TCTRL_panel_comment.SetValue(u'') 1282 if self._PRW_panel.GetValue().strip() == u'': 1283 self.panel_data_grid.panel_to_show = None 1284 self.panel_data_grid.Hide() 1285 self.Layout()
1286 #-------------------------------------------------------- 1287 # internal API 1288 #--------------------------------------------------------
1289 - def __init_ui(self):
1290 self.__action_button_popup = wx.Menu(title = _('Perform on selected results:')) 1291 1292 menu_id = wx.NewId() 1293 self.__action_button_popup.AppendItem(wx.MenuItem(self.__action_button_popup, menu_id, _('Review and &sign'))) 1294 wx.EVT_MENU(self.__action_button_popup, menu_id, self.__on_sign_current_selection) 1295 1296 menu_id = wx.NewId() 1297 self.__action_button_popup.AppendItem(wx.MenuItem(self.__action_button_popup, menu_id, _('Plot'))) 1298 wx.EVT_MENU(self.__action_button_popup, menu_id, self.__on_plot_current_selection) 1299 1300 menu_id = wx.NewId() 1301 self.__action_button_popup.AppendItem(wx.MenuItem(self.__action_button_popup, menu_id, _('Export to &file'))) 1302 #wx.EVT_MENU(self.__action_button_popup, menu_id, self.data_grid.current_selection_to_file) 1303 self.__action_button_popup.Enable(id = menu_id, enable = False) 1304 1305 menu_id = wx.NewId() 1306 self.__action_button_popup.AppendItem(wx.MenuItem(self.__action_button_popup, menu_id, _('Export to &clipboard'))) 1307 #wx.EVT_MENU(self.__action_button_popup, menu_id, self.data_grid.current_selection_to_clipboard) 1308 self.__action_button_popup.Enable(id = menu_id, enable = False) 1309 1310 menu_id = wx.NewId() 1311 self.__action_button_popup.AppendItem(wx.MenuItem(self.__action_button_popup, menu_id, _('&Delete'))) 1312 wx.EVT_MENU(self.__action_button_popup, menu_id, self.__on_delete_current_selection) 1313 1314 # FIXME: create inbox message to staff to phone patient to come in 1315 # FIXME: generate and let edit a SOAP narrative and include the values 1316 1317 self._PRW_panel.add_callback_on_selection(callback = self._on_panel_selected) 1318 self._PRW_panel.add_callback_on_modified(callback = self._on_panel_selection_modified) 1319 1320 self.panel_data_grid.show_by_panel = True 1321 self.panel_data_grid.panel_to_show = None 1322 self.panel_data_grid.Hide() 1323 self.Layout() 1324 1325 self._PRW_panel.SetFocus()
1326 #-------------------------------------------------------- 1327 # reget mixin API 1328 #--------------------------------------------------------
1329 - def _populate_with_data(self):
1330 """Populate fields in pages with data from model.""" 1331 pat = gmPerson.gmCurrentPatient() 1332 if pat.connected: 1333 self.data_grid.patient = pat 1334 self.panel_data_grid.patient = pat 1335 else: 1336 self.data_grid.patient = None 1337 self.panel_data_grid.patient = None 1338 return True
1339 1340 #================================================================ 1341 # editing widgets 1342 #================================================================
1343 -def review_tests(parent=None, tests=None):
1344 1345 if tests is None: 1346 return True 1347 1348 if len(tests) == 0: 1349 return True 1350 1351 if parent is None: 1352 parent = wx.GetApp().GetTopWindow() 1353 1354 if len(tests) > 10: 1355 test_count = len(tests) 1356 tests2show = None 1357 else: 1358 test_count = None 1359 tests2show = tests 1360 if len(tests) == 0: 1361 return True 1362 1363 dlg = cMeasurementsReviewDlg(parent, -1, tests = tests, test_count = test_count) 1364 decision = dlg.ShowModal() 1365 if decision != wx.ID_APPLY: 1366 return True 1367 1368 wx.BeginBusyCursor() 1369 if dlg._RBTN_confirm_abnormal.GetValue(): 1370 abnormal = None 1371 elif dlg._RBTN_results_normal.GetValue(): 1372 abnormal = False 1373 else: 1374 abnormal = True 1375 1376 if dlg._RBTN_confirm_relevance.GetValue(): 1377 relevant = None 1378 elif dlg._RBTN_results_not_relevant.GetValue(): 1379 relevant = False 1380 else: 1381 relevant = True 1382 1383 comment = None 1384 if len(tests) == 1: 1385 comment = dlg._TCTRL_comment.GetValue() 1386 1387 make_responsible = dlg._CHBOX_responsible.IsChecked() 1388 dlg.Destroy() 1389 1390 for test in tests: 1391 test.set_review ( 1392 technically_abnormal = abnormal, 1393 clinically_relevant = relevant, 1394 comment = comment, 1395 make_me_responsible = make_responsible 1396 ) 1397 wx.EndBusyCursor() 1398 1399 return True
1400 #---------------------------------------------------------------- 1401 from Gnumed.wxGladeWidgets import wxgMeasurementsReviewDlg 1402
1403 -class cMeasurementsReviewDlg(wxgMeasurementsReviewDlg.wxgMeasurementsReviewDlg):
1404
1405 - def __init__(self, *args, **kwargs):
1406 1407 try: 1408 tests = kwargs['tests'] 1409 del kwargs['tests'] 1410 test_count = len(tests) 1411 try: del kwargs['test_count'] 1412 except KeyError: pass 1413 except KeyError: 1414 tests = None 1415 test_count = kwargs['test_count'] 1416 del kwargs['test_count'] 1417 1418 wxgMeasurementsReviewDlg.wxgMeasurementsReviewDlg.__init__(self, *args, **kwargs) 1419 1420 if tests is None: 1421 msg = _('%s results selected. Too many to list individually.') % test_count 1422 else: 1423 msg = '\n'.join ( 1424 [ u'%s: %s %s (%s)' % ( 1425 t['unified_abbrev'], 1426 t['unified_val'], 1427 t['val_unit'], 1428 gmDateTime.pydt_strftime(t['clin_when'], '%Y %b %d') 1429 ) for t in tests 1430 ] 1431 ) 1432 1433 self._LBL_tests.SetLabel(msg) 1434 1435 if test_count == 1: 1436 self._TCTRL_comment.Enable(True) 1437 self._TCTRL_comment.SetValue(gmTools.coalesce(tests[0]['review_comment'], u'')) 1438 if tests[0]['you_are_responsible']: 1439 self._CHBOX_responsible.Enable(False) 1440 1441 self.Fit()
1442 #-------------------------------------------------------- 1443 # event handling 1444 #--------------------------------------------------------
1445 - def _on_signoff_button_pressed(self, evt):
1446 if self.IsModal(): 1447 self.EndModal(wx.ID_APPLY) 1448 else: 1449 self.Close()
1450 #================================================================ 1451 from Gnumed.wxGladeWidgets import wxgMeasurementEditAreaPnl 1452
1453 -class cMeasurementEditAreaPnl(wxgMeasurementEditAreaPnl.wxgMeasurementEditAreaPnl, gmEditArea.cGenericEditAreaMixin):
1454 """This edit area saves *new* measurements into the active patient only.""" 1455
1456 - def __init__(self, *args, **kwargs):
1457 1458 try: 1459 self.__default_date = kwargs['date'] 1460 del kwargs['date'] 1461 except KeyError: 1462 self.__default_date = None 1463 1464 wxgMeasurementEditAreaPnl.wxgMeasurementEditAreaPnl.__init__(self, *args, **kwargs) 1465 gmEditArea.cGenericEditAreaMixin.__init__(self) 1466 1467 self.__register_interests() 1468 1469 self.successful_save_msg = _('Successfully saved measurement.') 1470 1471 self._DPRW_evaluated.display_accuracy = gmDateTime.acc_minutes
1472 #-------------------------------------------------------- 1473 # generic edit area mixin API 1474 #--------------------------------------------------------
1475 - def _refresh_as_new(self):
1476 self._PRW_test.SetText(u'', None, True) 1477 self.__refresh_loinc_info() 1478 self.__refresh_previous_value() 1479 self.__update_units_context() 1480 self._TCTRL_result.SetValue(u'') 1481 self._PRW_units.SetText(u'', None, True) 1482 self._PRW_abnormality_indicator.SetText(u'', None, True) 1483 if self.__default_date is None: 1484 self._DPRW_evaluated.SetData(data = pyDT.datetime.now(tz = gmDateTime.gmCurrentLocalTimezone)) 1485 else: 1486 self._DPRW_evaluated.SetData(data = None) 1487 self._TCTRL_note_test_org.SetValue(u'') 1488 self._PRW_intended_reviewer.SetData(gmStaff.gmCurrentProvider()['pk_staff']) 1489 self._PRW_problem.SetData() 1490 self._TCTRL_narrative.SetValue(u'') 1491 self._CHBOX_review.SetValue(False) 1492 self._CHBOX_abnormal.SetValue(False) 1493 self._CHBOX_relevant.SetValue(False) 1494 self._CHBOX_abnormal.Enable(False) 1495 self._CHBOX_relevant.Enable(False) 1496 self._TCTRL_review_comment.SetValue(u'') 1497 self._TCTRL_normal_min.SetValue(u'') 1498 self._TCTRL_normal_max.SetValue(u'') 1499 self._TCTRL_normal_range.SetValue(u'') 1500 self._TCTRL_target_min.SetValue(u'') 1501 self._TCTRL_target_max.SetValue(u'') 1502 self._TCTRL_target_range.SetValue(u'') 1503 self._TCTRL_norm_ref_group.SetValue(u'') 1504 1505 self._PRW_test.SetFocus()
1506 #--------------------------------------------------------
1507 - def _refresh_from_existing(self):
1508 self._PRW_test.SetData(data = self.data['pk_test_type']) 1509 self.__refresh_loinc_info() 1510 self.__refresh_previous_value() 1511 self.__update_units_context() 1512 self._TCTRL_result.SetValue(self.data['unified_val']) 1513 self._PRW_units.SetText(self.data['val_unit'], self.data['val_unit'], True) 1514 self._PRW_abnormality_indicator.SetText ( 1515 gmTools.coalesce(self.data['abnormality_indicator'], u''), 1516 gmTools.coalesce(self.data['abnormality_indicator'], u''), 1517 True 1518 ) 1519 self._DPRW_evaluated.SetData(data = self.data['clin_when']) 1520 self._TCTRL_note_test_org.SetValue(gmTools.coalesce(self.data['note_test_org'], u'')) 1521 self._PRW_intended_reviewer.SetData(self.data['pk_intended_reviewer']) 1522 self._PRW_problem.SetData(self.data['pk_episode']) 1523 self._TCTRL_narrative.SetValue(gmTools.coalesce(self.data['comment'], u'')) 1524 self._CHBOX_review.SetValue(False) 1525 self._CHBOX_abnormal.SetValue(gmTools.coalesce(self.data['is_technically_abnormal'], False)) 1526 self._CHBOX_relevant.SetValue(gmTools.coalesce(self.data['is_clinically_relevant'], False)) 1527 self._CHBOX_abnormal.Enable(False) 1528 self._CHBOX_relevant.Enable(False) 1529 self._TCTRL_review_comment.SetValue(gmTools.coalesce(self.data['review_comment'], u'')) 1530 self._TCTRL_normal_min.SetValue(unicode(gmTools.coalesce(self.data['val_normal_min'], u''))) 1531 self._TCTRL_normal_max.SetValue(unicode(gmTools.coalesce(self.data['val_normal_max'], u''))) 1532 self._TCTRL_normal_range.SetValue(gmTools.coalesce(self.data['val_normal_range'], u'')) 1533 self._TCTRL_target_min.SetValue(unicode(gmTools.coalesce(self.data['val_target_min'], u''))) 1534 self._TCTRL_target_max.SetValue(unicode(gmTools.coalesce(self.data['val_target_max'], u''))) 1535 self._TCTRL_target_range.SetValue(gmTools.coalesce(self.data['val_target_range'], u'')) 1536 self._TCTRL_norm_ref_group.SetValue(gmTools.coalesce(self.data['norm_ref_group'], u'')) 1537 1538 self._TCTRL_result.SetFocus()
1539 #--------------------------------------------------------
1541 self._PRW_test.SetText(u'', None, True) 1542 self.__refresh_loinc_info() 1543 self.__refresh_previous_value() 1544 self.__update_units_context() 1545 self._TCTRL_result.SetValue(u'') 1546 self._PRW_units.SetText(u'', None, True) 1547 self._PRW_abnormality_indicator.SetText(u'', None, True) 1548 self._DPRW_evaluated.SetData(data = self.data['clin_when']) 1549 self._TCTRL_note_test_org.SetValue(u'') 1550 self._PRW_intended_reviewer.SetData(self.data['pk_intended_reviewer']) 1551 self._PRW_problem.SetData(self.data['pk_episode']) 1552 self._TCTRL_narrative.SetValue(u'') 1553 self._CHBOX_review.SetValue(False) 1554 self._CHBOX_abnormal.SetValue(False) 1555 self._CHBOX_relevant.SetValue(False) 1556 self._CHBOX_abnormal.Enable(False) 1557 self._CHBOX_relevant.Enable(False) 1558 self._TCTRL_review_comment.SetValue(u'') 1559 self._TCTRL_normal_min.SetValue(u'') 1560 self._TCTRL_normal_max.SetValue(u'') 1561 self._TCTRL_normal_range.SetValue(u'') 1562 self._TCTRL_target_min.SetValue(u'') 1563 self._TCTRL_target_max.SetValue(u'') 1564 self._TCTRL_target_range.SetValue(u'') 1565 self._TCTRL_norm_ref_group.SetValue(u'') 1566 1567 self._PRW_test.SetFocus()
1568 #--------------------------------------------------------
1569 - def _valid_for_save(self):
1570 1571 validity = True 1572 1573 if not self._DPRW_evaluated.is_valid_timestamp(): 1574 self._DPRW_evaluated.display_as_valid(False) 1575 validity = False 1576 else: 1577 self._DPRW_evaluated.display_as_valid(True) 1578 1579 val = self._TCTRL_result.GetValue().strip() 1580 if val == u'': 1581 validity = False 1582 self.display_ctrl_as_valid(self._TCTRL_result, False) 1583 else: 1584 self.display_ctrl_as_valid(self._TCTRL_result, True) 1585 numeric, val = gmTools.input2decimal(val) 1586 if numeric: 1587 if self._PRW_units.GetValue().strip() == u'': 1588 self._PRW_units.display_as_valid(False) 1589 validity = False 1590 else: 1591 self._PRW_units.display_as_valid(True) 1592 else: 1593 self._PRW_units.display_as_valid(True) 1594 1595 if self._PRW_problem.GetValue().strip() == u'': 1596 self._PRW_problem.display_as_valid(False) 1597 validity = False 1598 else: 1599 self._PRW_problem.display_as_valid(True) 1600 1601 if self._PRW_test.GetValue().strip() == u'': 1602 self._PRW_test.display_as_valid(False) 1603 validity = False 1604 else: 1605 self._PRW_test.display_as_valid(True) 1606 1607 if self._PRW_intended_reviewer.GetData() is None: 1608 self._PRW_intended_reviewer.display_as_valid(False) 1609 validity = False 1610 else: 1611 self._PRW_intended_reviewer.display_as_valid(True) 1612 1613 ctrls = [self._TCTRL_normal_min, self._TCTRL_normal_max, self._TCTRL_target_min, self._TCTRL_target_max] 1614 for widget in ctrls: 1615 val = widget.GetValue().strip() 1616 if val == u'': 1617 continue 1618 try: 1619 decimal.Decimal(val.replace(',', u'.', 1)) 1620 self.display_ctrl_as_valid(widget, True) 1621 except: 1622 validity = False 1623 self.display_ctrl_as_valid(widget, False) 1624 1625 if validity is False: 1626 gmDispatcher.send(signal = 'statustext', msg = _('Cannot save result. Invalid or missing essential input.')) 1627 1628 return validity
1629 #--------------------------------------------------------
1630 - def _save_as_new(self):
1631 1632 emr = gmPerson.gmCurrentPatient().get_emr() 1633 1634 success, result = gmTools.input2decimal(self._TCTRL_result.GetValue()) 1635 if success: 1636 v_num = result 1637 v_al = None 1638 else: 1639 v_al = self._TCTRL_result.GetValue().strip() 1640 v_num = None 1641 1642 pk_type = self._PRW_test.GetData() 1643 if pk_type is None: 1644 tt = gmPathLab.create_measurement_type ( 1645 lab = None, 1646 abbrev = self._PRW_test.GetValue().strip(), 1647 name = self._PRW_test.GetValue().strip(), 1648 unit = gmTools.coalesce(self._PRW_units.GetData(), self._PRW_units.GetValue()).strip() 1649 ) 1650 pk_type = tt['pk_test_type'] 1651 1652 tr = emr.add_test_result ( 1653 episode = self._PRW_problem.GetData(can_create=True, is_open=False), 1654 type = pk_type, 1655 intended_reviewer = self._PRW_intended_reviewer.GetData(), 1656 val_num = v_num, 1657 val_alpha = v_al, 1658 unit = self._PRW_units.GetValue() 1659 ) 1660 1661 tr['clin_when'] = self._DPRW_evaluated.GetData().get_pydt() 1662 1663 ctrls = [ 1664 ('abnormality_indicator', self._PRW_abnormality_indicator), 1665 ('note_test_org', self._TCTRL_note_test_org), 1666 ('comment', self._TCTRL_narrative), 1667 ('val_normal_range', self._TCTRL_normal_range), 1668 ('val_target_range', self._TCTRL_target_range), 1669 ('norm_ref_group', self._TCTRL_norm_ref_group) 1670 ] 1671 for field, widget in ctrls: 1672 tr[field] = widget.GetValue().strip() 1673 1674 ctrls = [ 1675 ('val_normal_min', self._TCTRL_normal_min), 1676 ('val_normal_max', self._TCTRL_normal_max), 1677 ('val_target_min', self._TCTRL_target_min), 1678 ('val_target_max', self._TCTRL_target_max) 1679 ] 1680 for field, widget in ctrls: 1681 val = widget.GetValue().strip() 1682 if val == u'': 1683 tr[field] = None 1684 else: 1685 tr[field] = decimal.Decimal(val.replace(',', u'.', 1)) 1686 1687 tr.save_payload() 1688 1689 if self._CHBOX_review.GetValue() is True: 1690 tr.set_review ( 1691 technically_abnormal = self._CHBOX_abnormal.GetValue(), 1692 clinically_relevant = self._CHBOX_relevant.GetValue(), 1693 comment = gmTools.none_if(self._TCTRL_review_comment.GetValue().strip(), u''), 1694 make_me_responsible = False 1695 ) 1696 1697 self.data = tr 1698 1699 wx.CallAfter ( 1700 plot_adjacent_measurements, 1701 test = self.data, 1702 plot_singular_result = False, 1703 use_default_template = True 1704 ) 1705 1706 return True
1707 #--------------------------------------------------------
1708 - def _save_as_update(self):
1709 1710 success, result = gmTools.input2decimal(self._TCTRL_result.GetValue()) 1711 if success: 1712 v_num = result 1713 v_al = None 1714 else: 1715 v_num = None 1716 v_al = self._TCTRL_result.GetValue().strip() 1717 1718 pk_type = self._PRW_test.GetData() 1719 if pk_type is None: 1720 tt = gmPathLab.create_measurement_type ( 1721 lab = None, 1722 abbrev = self._PRW_test.GetValue().strip(), 1723 name = self._PRW_test.GetValue().strip(), 1724 unit = gmTools.none_if(self._PRW_units.GetValue().strip(), u'') 1725 ) 1726 pk_type = tt['pk_test_type'] 1727 1728 tr = self.data 1729 1730 tr['pk_episode'] = self._PRW_problem.GetData(can_create=True, is_open=False) 1731 tr['pk_test_type'] = pk_type 1732 tr['pk_intended_reviewer'] = self._PRW_intended_reviewer.GetData() 1733 tr['val_num'] = v_num 1734 tr['val_alpha'] = v_al 1735 tr['val_unit'] = gmTools.coalesce(self._PRW_units.GetData(), self._PRW_units.GetValue()).strip() 1736 tr['clin_when'] = self._DPRW_evaluated.GetData().get_pydt() 1737 1738 ctrls = [ 1739 ('abnormality_indicator', self._PRW_abnormality_indicator), 1740 ('note_test_org', self._TCTRL_note_test_org), 1741 ('comment', self._TCTRL_narrative), 1742 ('val_normal_range', self._TCTRL_normal_range), 1743 ('val_target_range', self._TCTRL_target_range), 1744 ('norm_ref_group', self._TCTRL_norm_ref_group) 1745 ] 1746 for field, widget in ctrls: 1747 tr[field] = widget.GetValue().strip() 1748 1749 ctrls = [ 1750 ('val_normal_min', self._TCTRL_normal_min), 1751 ('val_normal_max', self._TCTRL_normal_max), 1752 ('val_target_min', self._TCTRL_target_min), 1753 ('val_target_max', self._TCTRL_target_max) 1754 ] 1755 for field, widget in ctrls: 1756 val = widget.GetValue().strip() 1757 if val == u'': 1758 tr[field] = None 1759 else: 1760 tr[field] = decimal.Decimal(val.replace(',', u'.', 1)) 1761 1762 tr.save_payload() 1763 1764 if self._CHBOX_review.GetValue() is True: 1765 tr.set_review ( 1766 technically_abnormal = self._CHBOX_abnormal.GetValue(), 1767 clinically_relevant = self._CHBOX_relevant.GetValue(), 1768 comment = gmTools.none_if(self._TCTRL_review_comment.GetValue().strip(), u''), 1769 make_me_responsible = False 1770 ) 1771 1772 wx.CallAfter ( 1773 plot_adjacent_measurements, 1774 test = self.data, 1775 plot_singular_result = False, 1776 use_default_template = True 1777 ) 1778 1779 return True
1780 #-------------------------------------------------------- 1781 # event handling 1782 #--------------------------------------------------------
1783 - def __register_interests(self):
1784 self._PRW_test.add_callback_on_lose_focus(self._on_leave_test_prw) 1785 self._PRW_abnormality_indicator.add_callback_on_lose_focus(self._on_leave_indicator_prw) 1786 self._PRW_units.add_callback_on_lose_focus(self._on_leave_unit_prw)
1787 #--------------------------------------------------------
1788 - def _on_leave_test_prw(self):
1789 self.__refresh_loinc_info() 1790 self.__refresh_previous_value() 1791 self.__update_units_context() 1792 # only works if we've got a unit set 1793 self.__update_normal_range() 1794 self.__update_clinical_range()
1795 #--------------------------------------------------------
1796 - def _on_leave_unit_prw(self):
1797 # maybe we've got a unit now ? 1798 self.__update_normal_range() 1799 self.__update_clinical_range()
1800 #--------------------------------------------------------
1801 - def _on_leave_indicator_prw(self):
1802 # if the user hasn't explicitly enabled reviewing 1803 if not self._CHBOX_review.GetValue(): 1804 self._CHBOX_abnormal.SetValue(self._PRW_abnormality_indicator.GetValue().strip() != u'')
1805 #--------------------------------------------------------
1806 - def _on_review_box_checked(self, evt):
1807 self._CHBOX_abnormal.Enable(self._CHBOX_review.GetValue()) 1808 self._CHBOX_relevant.Enable(self._CHBOX_review.GetValue()) 1809 self._TCTRL_review_comment.Enable(self._CHBOX_review.GetValue())
1810 #--------------------------------------------------------
1811 - def _on_test_info_button_pressed(self, event):
1812 1813 pk = self._PRW_test.GetData() 1814 if pk is not None: 1815 tt = gmPathLab.cMeasurementType(aPK_obj = pk) 1816 search_term = u'%s %s %s' % ( 1817 tt['name'], 1818 tt['abbrev'], 1819 gmTools.coalesce(tt['loinc'], u'') 1820 ) 1821 else: 1822 search_term = self._PRW_test.GetValue() 1823 1824 search_term = search_term.replace(' ', u'+') 1825 1826 call_browser_on_measurement_type(measurement_type = search_term)
1827 #-------------------------------------------------------- 1828 # internal helpers 1829 #--------------------------------------------------------
1830 - def __update_units_context(self):
1831 1832 if self._PRW_test.GetData() is None: 1833 self._PRW_units.unset_context(context = u'pk_type') 1834 self._PRW_units.unset_context(context = u'loinc') 1835 if self._PRW_test.GetValue().strip() == u'': 1836 self._PRW_units.unset_context(context = u'test_name') 1837 else: 1838 self._PRW_units.set_context(context = u'test_name', val = self._PRW_test.GetValue().strip()) 1839 return 1840 1841 tt = self._PRW_test.GetData(as_instance = True) 1842 1843 self._PRW_units.set_context(context = u'pk_type', val = tt['pk_test_type']) 1844 self._PRW_units.set_context(context = u'test_name', val = tt['name']) 1845 1846 if tt['loinc'] is not None: 1847 self._PRW_units.set_context(context = u'loinc', val = tt['loinc']) 1848 1849 # closest unit 1850 if self._PRW_units.GetValue().strip() == u'': 1851 clin_when = self._DPRW_evaluated.GetData().get_pydt() 1852 if clin_when is None: 1853 unit = tt.temporally_closest_unit 1854 else: 1855 unit = tt.get_temporally_closest_unit(timestamp = clin_when) 1856 if unit is None: 1857 self._PRW_units.SetText(u'', unit, True) 1858 else: 1859 self._PRW_units.SetText(unit, unit, True)
1860 1861 #--------------------------------------------------------
1862 - def __update_normal_range(self):
1863 unit = self._PRW_units.GetValue().strip() 1864 if unit == u'': 1865 return 1866 if self._PRW_test.GetData() is None: 1867 return 1868 for ctrl in [self._TCTRL_normal_min, self._TCTRL_normal_max, self._TCTRL_normal_range, self._TCTRL_norm_ref_group]: 1869 if ctrl.GetValue().strip() != u'': 1870 return 1871 tt = self._PRW_test.GetData(as_instance = True) 1872 test_w_range = tt.get_temporally_closest_normal_range ( 1873 unit, 1874 timestamp = self._DPRW_evaluated.GetData().get_pydt() 1875 ) 1876 if test_w_range is None: 1877 return 1878 self._TCTRL_normal_min.SetValue(unicode(gmTools.coalesce(test_w_range['val_normal_min'], u''))) 1879 self._TCTRL_normal_max.SetValue(unicode(gmTools.coalesce(test_w_range['val_normal_max'], u''))) 1880 self._TCTRL_normal_range.SetValue(gmTools.coalesce(test_w_range['val_normal_range'], u'')) 1881 self._TCTRL_norm_ref_group.SetValue(gmTools.coalesce(test_w_range['norm_ref_group'], u''))
1882 1883 #--------------------------------------------------------
1884 - def __update_clinical_range(self):
1885 unit = self._PRW_units.GetValue().strip() 1886 if unit == u'': 1887 return 1888 if self._PRW_test.GetData() is None: 1889 return 1890 for ctrl in [self._TCTRL_target_min, self._TCTRL_target_max, self._TCTRL_target_range]: 1891 if ctrl.GetValue().strip() != u'': 1892 return 1893 tt = self._PRW_test.GetData(as_instance = True) 1894 test_w_range = tt.get_temporally_closest_target_range ( 1895 unit, 1896 gmPerson.gmCurrentPatient().ID, 1897 timestamp = self._DPRW_evaluated.GetData().get_pydt() 1898 ) 1899 if test_w_range is None: 1900 return 1901 self._TCTRL_target_min.SetValue(unicode(gmTools.coalesce(test_w_range['val_target_min'], u''))) 1902 self._TCTRL_target_max.SetValue(unicode(gmTools.coalesce(test_w_range['val_target_max'], u''))) 1903 self._TCTRL_target_range.SetValue(gmTools.coalesce(test_w_range['val_target_range'], u''))
1904 1905 #--------------------------------------------------------
1906 - def __refresh_loinc_info(self):
1907 1908 self._TCTRL_loinc.SetValue(u'') 1909 1910 if self._PRW_test.GetData() is None: 1911 return 1912 1913 tt = self._PRW_test.GetData(as_instance = True) 1914 1915 if tt['loinc'] is None: 1916 return 1917 1918 info = gmLOINC.loinc2term(loinc = tt['loinc']) 1919 if len(info) == 0: 1920 self._TCTRL_loinc.SetValue(u'') 1921 return 1922 1923 self._TCTRL_loinc.SetValue(u'%s: %s' % (tt['loinc'], info[0]))
1924 #--------------------------------------------------------
1925 - def __refresh_previous_value(self):
1926 self._TCTRL_previous_value.SetValue(u'') 1927 # it doesn't make much sense to show the most 1928 # recent value when editing an existing one 1929 if self.data is not None: 1930 return 1931 if self._PRW_test.GetData() is None: 1932 return 1933 tt = self._PRW_test.GetData(as_instance = True) 1934 most_recent = tt.get_most_recent_results ( 1935 no_of_results = 1, 1936 patient = gmPerson.gmCurrentPatient().ID 1937 ) 1938 if most_recent is None: 1939 return 1940 self._TCTRL_previous_value.SetValue(_('%s ago: %s%s%s - %s%s') % ( 1941 gmDateTime.format_interval_medically(gmDateTime.pydt_now_here() - most_recent['clin_when']), 1942 most_recent['unified_val'], 1943 most_recent['val_unit'], 1944 gmTools.coalesce(most_recent['abnormality_indicator'], u'', u' (%s)'), 1945 most_recent['abbrev_tt'], 1946 gmTools.coalesce(most_recent.formatted_range, u'', u' [%s]') 1947 )) 1948 self._TCTRL_previous_value.SetToolTipString(most_recent.format ( 1949 with_review = True, 1950 with_evaluation = False, 1951 with_ranges = True, 1952 with_episode = True, 1953 with_type_details=True 1954 ))
1955 1956 #================================================================ 1957 # measurement type handling 1958 #================================================================
1959 -def pick_measurement_types(parent=None, msg=None, right_column=None, picks=None):
1960 1961 if parent is None: 1962 parent = wx.GetApp().GetTopWindow() 1963 1964 if msg is None: 1965 msg = _('Pick the relevant measurement types.') 1966 1967 if right_column is None: 1968 right_columns = [_('Picked')] 1969 else: 1970 right_columns = [right_column] 1971 1972 picker = gmListWidgets.cItemPickerDlg(parent, -1, msg = msg) 1973 picker.set_columns(columns = [_('Known measurement types')], columns_right = right_columns) 1974 types = gmPathLab.get_measurement_types(order_by = 'unified_abbrev') 1975 picker.set_choices ( 1976 choices = [ 1977 u'%s: %s%s' % ( 1978 t['unified_abbrev'], 1979 t['unified_name'], 1980 gmTools.coalesce(t['name_org'], u'', u' (%s)') 1981 ) 1982 for t in types 1983 ], 1984 data = types 1985 ) 1986 if picks is not None: 1987 picker.set_picks ( 1988 picks = [ 1989 u'%s: %s%s' % ( 1990 p['unified_abbrev'], 1991 p['unified_name'], 1992 gmTools.coalesce(p['name_org'], u'', u' (%s)') 1993 ) 1994 for p in picks 1995 ], 1996 data = picks 1997 ) 1998 result = picker.ShowModal() 1999 2000 if result == wx.ID_CANCEL: 2001 picker.Destroy() 2002 return None 2003 2004 picks = picker.picks 2005 picker.Destroy() 2006 return picks
2007 2008 #----------------------------------------------------------------
2009 -def manage_measurement_types(parent=None):
2010 2011 if parent is None: 2012 parent = wx.GetApp().GetTopWindow() 2013 2014 #------------------------------------------------------------ 2015 def edit(test_type=None): 2016 ea = cMeasurementTypeEAPnl(parent = parent, id = -1, type = test_type) 2017 dlg = gmEditArea.cGenericEditAreaDlg2 ( 2018 parent = parent, 2019 id = -1, 2020 edit_area = ea, 2021 single_entry = gmTools.bool2subst((test_type is None), False, True) 2022 ) 2023 dlg.SetTitle(gmTools.coalesce(test_type, _('Adding measurement type'), _('Editing measurement type'))) 2024 2025 if dlg.ShowModal() == wx.ID_OK: 2026 dlg.Destroy() 2027 return True 2028 2029 dlg.Destroy() 2030 return False
2031 #------------------------------------------------------------ 2032 def delete(measurement_type): 2033 if measurement_type.in_use: 2034 gmDispatcher.send ( 2035 signal = 'statustext', 2036 beep = True, 2037 msg = _('Cannot delete measurement type [%s (%s)] because it is in use.') % (measurement_type['name'], measurement_type['abbrev']) 2038 ) 2039 return False 2040 gmPathLab.delete_measurement_type(measurement_type = measurement_type['pk_test_type']) 2041 return True 2042 #------------------------------------------------------------ 2043 def get_tooltip(test_type): 2044 return test_type.format() 2045 #------------------------------------------------------------ 2046 def refresh(lctrl): 2047 mtypes = gmPathLab.get_measurement_types(order_by = 'name, abbrev') 2048 items = [ [ 2049 m['abbrev'], 2050 m['name'], 2051 gmTools.coalesce(m['conversion_unit'], u''), 2052 gmTools.coalesce(m['loinc'], u''), 2053 gmTools.coalesce(m['comment_type'], u''), 2054 gmTools.coalesce(m['name_org'], u'?'), 2055 gmTools.coalesce(m['comment_org'], u''), 2056 m['pk_test_type'] 2057 ] for m in mtypes ] 2058 lctrl.set_string_items(items) 2059 lctrl.set_data(mtypes) 2060 #------------------------------------------------------------ 2061 msg = _( 2062 '\n' 2063 'These are the measurement types currently defined in GNUmed.\n' 2064 '\n' 2065 ) 2066 2067 gmListWidgets.get_choices_from_list ( 2068 parent = parent, 2069 msg = msg, 2070 caption = _('Showing measurement types.'), 2071 columns = [ _('Abbrev'), _('Name'), _('Unit'), _('LOINC'), _('Comment'), _('Org'), _('Comment'), u'#' ], 2072 single_selection = True, 2073 refresh_callback = refresh, 2074 edit_callback = edit, 2075 new_callback = edit, 2076 delete_callback = delete, 2077 list_tooltip_callback = get_tooltip 2078 ) 2079 2080 #----------------------------------------------------------------
2081 -class cMeasurementTypePhraseWheel(gmPhraseWheel.cPhraseWheel):
2082
2083 - def __init__(self, *args, **kwargs):
2084 2085 query = u""" 2086 SELECT DISTINCT ON (field_label) 2087 pk_test_type AS data, 2088 name 2089 || ' (' 2090 || coalesce ( 2091 (SELECT unit || ' @ ' || organization FROM clin.v_test_orgs c_vto WHERE c_vto.pk_test_org = c_vtt.pk_test_org), 2092 '%(in_house)s' 2093 ) 2094 || ')' 2095 AS field_label, 2096 name 2097 || ' (' 2098 || abbrev || ', ' 2099 || coalesce(abbrev_meta || ': ' || name_meta || ', ', '') 2100 || coalesce ( 2101 (SELECT unit || ' @ ' || organization FROM clin.v_test_orgs c_vto WHERE c_vto.pk_test_org = c_vtt.pk_test_org), 2102 '%(in_house)s' 2103 ) 2104 || ')' 2105 AS list_label 2106 FROM 2107 clin.v_test_types c_vtt 2108 WHERE 2109 abbrev_meta %%(fragment_condition)s 2110 OR 2111 name_meta %%(fragment_condition)s 2112 OR 2113 abbrev %%(fragment_condition)s 2114 OR 2115 name %%(fragment_condition)s 2116 ORDER BY field_label 2117 LIMIT 50""" % {'in_house': _('generic / in house lab')} 2118 2119 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 2120 mp.setThresholds(1, 2, 4) 2121 mp.word_separators = '[ \t:@]+' 2122 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 2123 self.matcher = mp 2124 self.SetToolTipString(_('Select the type of measurement.')) 2125 self.selection_only = False
2126 #------------------------------------------------------------
2127 - def _data2instance(self):
2128 if self.GetData() is None: 2129 return None 2130 2131 return gmPathLab.cMeasurementType(aPK_obj = self.GetData())
2132 #---------------------------------------------------------------- 2133 from Gnumed.wxGladeWidgets import wxgMeasurementTypeEAPnl 2134
2135 -class cMeasurementTypeEAPnl(wxgMeasurementTypeEAPnl.wxgMeasurementTypeEAPnl, gmEditArea.cGenericEditAreaMixin):
2136
2137 - def __init__(self, *args, **kwargs):
2138 2139 try: 2140 data = kwargs['type'] 2141 del kwargs['type'] 2142 except KeyError: 2143 data = None 2144 2145 wxgMeasurementTypeEAPnl.wxgMeasurementTypeEAPnl.__init__(self, *args, **kwargs) 2146 gmEditArea.cGenericEditAreaMixin.__init__(self) 2147 self.mode = 'new' 2148 self.data = data 2149 if data is not None: 2150 self.mode = 'edit' 2151 2152 self.__init_ui()
2153 2154 #----------------------------------------------------------------
2155 - def __init_ui(self):
2156 2157 # name phraseweel 2158 query = u""" 2159 select distinct on (name) 2160 pk, 2161 name 2162 from clin.test_type 2163 where 2164 name %(fragment_condition)s 2165 order by name 2166 limit 50""" 2167 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 2168 mp.setThresholds(1, 2, 4) 2169 self._PRW_name.matcher = mp 2170 self._PRW_name.selection_only = False 2171 self._PRW_name.add_callback_on_lose_focus(callback = self._on_name_lost_focus) 2172 2173 # abbreviation 2174 query = u""" 2175 select distinct on (abbrev) 2176 pk, 2177 abbrev 2178 from clin.test_type 2179 where 2180 abbrev %(fragment_condition)s 2181 order by abbrev 2182 limit 50""" 2183 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 2184 mp.setThresholds(1, 2, 3) 2185 self._PRW_abbrev.matcher = mp 2186 self._PRW_abbrev.selection_only = False 2187 2188 # unit 2189 self._PRW_conversion_unit.selection_only = False 2190 2191 # loinc 2192 mp = gmLOINC.cLOINCMatchProvider() 2193 mp.setThresholds(1, 2, 4) 2194 #mp.print_queries = True 2195 #mp.word_separators = '[ \t:@]+' 2196 self._PRW_loinc.matcher = mp 2197 self._PRW_loinc.selection_only = False 2198 self._PRW_loinc.add_callback_on_lose_focus(callback = self._on_loinc_lost_focus)
2199 #----------------------------------------------------------------
2200 - def _on_name_lost_focus(self):
2201 2202 test = self._PRW_name.GetValue().strip() 2203 2204 if test == u'': 2205 self._PRW_conversion_unit.unset_context(context = u'test_name') 2206 return 2207 2208 self._PRW_conversion_unit.set_context(context = u'test_name', val = test)
2209 #----------------------------------------------------------------
2210 - def _on_loinc_lost_focus(self):
2211 loinc = self._PRW_loinc.GetData() 2212 2213 if loinc is None: 2214 self._TCTRL_loinc_info.SetValue(u'') 2215 self._PRW_conversion_unit.unset_context(context = u'loinc') 2216 return 2217 2218 self._PRW_conversion_unit.set_context(context = u'loinc', val = loinc) 2219 2220 info = gmLOINC.loinc2term(loinc = loinc) 2221 if len(info) == 0: 2222 self._TCTRL_loinc_info.SetValue(u'') 2223 return 2224 2225 self._TCTRL_loinc_info.SetValue(info[0])
2226 #---------------------------------------------------------------- 2227 # generic Edit Area mixin API 2228 #----------------------------------------------------------------
2229 - def _valid_for_save(self):
2230 2231 has_errors = False 2232 for field in [self._PRW_name, self._PRW_abbrev, self._PRW_conversion_unit]: 2233 if field.GetValue().strip() in [u'', None]: 2234 has_errors = True 2235 field.display_as_valid(valid = False) 2236 else: 2237 field.display_as_valid(valid = True) 2238 field.Refresh() 2239 2240 return (not has_errors)
2241 #----------------------------------------------------------------
2242 - def _save_as_new(self):
2243 2244 pk_org = self._PRW_test_org.GetData() 2245 if pk_org is None: 2246 pk_org = gmPathLab.create_test_org ( 2247 name = gmTools.none_if(self._PRW_test_org.GetValue().strip(), u'') 2248 )['pk_test_org'] 2249 2250 tt = gmPathLab.create_measurement_type ( 2251 lab = pk_org, 2252 abbrev = self._PRW_abbrev.GetValue().strip(), 2253 name = self._PRW_name.GetValue().strip(), 2254 unit = gmTools.coalesce ( 2255 self._PRW_conversion_unit.GetData(), 2256 self._PRW_conversion_unit.GetValue() 2257 ).strip() 2258 ) 2259 if self._PRW_loinc.GetData() is not None: 2260 tt['loinc'] = gmTools.none_if(self._PRW_loinc.GetData().strip(), u'') 2261 else: 2262 tt['loinc'] = gmTools.none_if(self._PRW_loinc.GetValue().strip(), u'') 2263 tt['comment_type'] = gmTools.none_if(self._TCTRL_comment_type.GetValue().strip(), u'') 2264 tt['pk_meta_test_type'] = self._PRW_meta_type.GetData() 2265 2266 tt.save() 2267 2268 self.data = tt 2269 2270 return True
2271 #----------------------------------------------------------------
2272 - def _save_as_update(self):
2273 2274 pk_org = self._PRW_test_org.GetData() 2275 if pk_org is None: 2276 pk_org = gmPathLab.create_test_org ( 2277 name = gmTools.none_if(self._PRW_test_org.GetValue().strip(), u'') 2278 )['pk_test_org'] 2279 2280 self.data['pk_test_org'] = pk_org 2281 self.data['abbrev'] = self._PRW_abbrev.GetValue().strip() 2282 self.data['name'] = self._PRW_name.GetValue().strip() 2283 self.data['conversion_unit'] = gmTools.coalesce ( 2284 self._PRW_conversion_unit.GetData(), 2285 self._PRW_conversion_unit.GetValue() 2286 ).strip() 2287 if self._PRW_loinc.GetData() is not None: 2288 self.data['loinc'] = gmTools.none_if(self._PRW_loinc.GetData().strip(), u'') 2289 if self._PRW_loinc.GetData() is not None: 2290 self.data['loinc'] = gmTools.none_if(self._PRW_loinc.GetData().strip(), u'') 2291 else: 2292 self.data['loinc'] = gmTools.none_if(self._PRW_loinc.GetValue().strip(), u'') 2293 self.data['comment_type'] = gmTools.none_if(self._TCTRL_comment_type.GetValue().strip(), u'') 2294 self.data['pk_meta_test_type'] = self._PRW_meta_type.GetData() 2295 self.data.save() 2296 2297 return True
2298 #----------------------------------------------------------------
2299 - def _refresh_as_new(self):
2300 self._PRW_name.SetText(u'', None, True) 2301 self._on_name_lost_focus() 2302 self._PRW_abbrev.SetText(u'', None, True) 2303 self._PRW_conversion_unit.SetText(u'', None, True) 2304 self._PRW_loinc.SetText(u'', None, True) 2305 self._on_loinc_lost_focus() 2306 self._TCTRL_comment_type.SetValue(u'') 2307 self._PRW_test_org.SetText(u'', None, True) 2308 self._PRW_meta_type.SetText(u'', None, True) 2309 2310 self._PRW_name.SetFocus()
2311 #----------------------------------------------------------------
2312 - def _refresh_from_existing(self):
2313 self._PRW_name.SetText(self.data['name'], self.data['name'], True) 2314 self._on_name_lost_focus() 2315 self._PRW_abbrev.SetText(self.data['abbrev'], self.data['abbrev'], True) 2316 self._PRW_conversion_unit.SetText ( 2317 gmTools.coalesce(self.data['conversion_unit'], u''), 2318 self.data['conversion_unit'], 2319 True 2320 ) 2321 self._PRW_loinc.SetText ( 2322 gmTools.coalesce(self.data['loinc'], u''), 2323 self.data['loinc'], 2324 True 2325 ) 2326 self._on_loinc_lost_focus() 2327 self._TCTRL_comment_type.SetValue(gmTools.coalesce(self.data['comment_type'], u'')) 2328 self._PRW_test_org.SetText ( 2329 gmTools.coalesce(self.data['pk_test_org'], u'', self.data['name_org']), 2330 self.data['pk_test_org'], 2331 True 2332 ) 2333 if self.data['pk_meta_test_type'] is None: 2334 self._PRW_meta_type.SetText(u'', None, True) 2335 else: 2336 self._PRW_meta_type.SetText(u'%s: %s' % (self.data['abbrev_meta'], self.data['name_meta']), self.data['pk_meta_test_type'], True) 2337 2338 self._PRW_name.SetFocus()
2339 #----------------------------------------------------------------
2341 self._refresh_as_new() 2342 self._PRW_test_org.SetText ( 2343 gmTools.coalesce(self.data['pk_test_org'], u'', self.data['name_org']), 2344 self.data['pk_test_org'], 2345 True 2346 ) 2347 self._PRW_name.SetFocus()
2348 2349 #================================================================ 2350 _SQL_units_from_test_results = u""" 2351 -- via clin.v_test_results.pk_type (for types already used in results) 2352 SELECT 2353 val_unit AS data, 2354 val_unit AS field_label, 2355 val_unit || ' (' || name_tt || ')' AS list_label, 2356 1 AS rank 2357 FROM 2358 clin.v_test_results 2359 WHERE 2360 ( 2361 val_unit %(fragment_condition)s 2362 OR 2363 conversion_unit %(fragment_condition)s 2364 ) 2365 %(ctxt_type_pk)s 2366 %(ctxt_test_name)s 2367 """ 2368 2369 _SQL_units_from_test_types = u""" 2370 -- via clin.test_type (for types not yet used in results) 2371 SELECT 2372 conversion_unit AS data, 2373 conversion_unit AS field_label, 2374 conversion_unit || ' (' || name || ')' AS list_label, 2375 2 AS rank 2376 FROM 2377 clin.test_type 2378 WHERE 2379 conversion_unit %(fragment_condition)s 2380 %(ctxt_ctt)s 2381 """ 2382 2383 _SQL_units_from_loinc_ipcc = u""" 2384 -- via ref.loinc.ipcc_units 2385 SELECT 2386 ipcc_units AS data, 2387 ipcc_units AS field_label, 2388 ipcc_units || ' (LOINC.ipcc: ' || term || ')' AS list_label, 2389 3 AS rank 2390 FROM 2391 ref.loinc 2392 WHERE 2393 ipcc_units %(fragment_condition)s 2394 %(ctxt_loinc)s 2395 %(ctxt_loinc_term)s 2396 """ 2397 2398 _SQL_units_from_loinc_submitted = u""" 2399 -- via ref.loinc.submitted_units 2400 SELECT 2401 submitted_units AS data, 2402 submitted_units AS field_label, 2403 submitted_units || ' (LOINC.submitted:' || term || ')' AS list_label, 2404 3 AS rank 2405 FROM 2406 ref.loinc 2407 WHERE 2408 submitted_units %(fragment_condition)s 2409 %(ctxt_loinc)s 2410 %(ctxt_loinc_term)s 2411 """ 2412 2413 _SQL_units_from_loinc_example = u""" 2414 -- via ref.loinc.example_units 2415 SELECT 2416 example_units AS data, 2417 example_units AS field_label, 2418 example_units || ' (LOINC.example: ' || term || ')' AS list_label, 2419 3 AS rank 2420 FROM 2421 ref.loinc 2422 WHERE 2423 example_units %(fragment_condition)s 2424 %(ctxt_loinc)s 2425 %(ctxt_loinc_term)s 2426 """ 2427 2428 _SQL_units_from_atc = u""" 2429 -- via ref.atc.unit 2430 SELECT 2431 unit AS data, 2432 unit AS field_label, 2433 unit || ' (ATC: ' || term || ')' AS list_label, 2434 2 AS rank 2435 FROM 2436 ref.atc 2437 WHERE 2438 unit IS NOT NULL 2439 AND 2440 unit %(fragment_condition)s 2441 """ 2442 2443 _SQL_units_from_consumable_substance = u""" 2444 -- via ref.consumable_substance.unit 2445 SELECT 2446 unit AS data, 2447 unit AS field_label, 2448 unit || ' (' || description || ')' AS list_label, 2449 2 AS rank 2450 FROM 2451 ref.consumable_substance 2452 WHERE 2453 unit %(fragment_condition)s 2454 %(ctxt_substance)s 2455 """ 2456 2457 #----------------------------------------------------------------
2458 -class cUnitPhraseWheel(gmPhraseWheel.cPhraseWheel):
2459
2460 - def __init__(self, *args, **kwargs):
2461 2462 query = u""" 2463 SELECT DISTINCT ON (data) 2464 data, 2465 field_label, 2466 list_label 2467 FROM ( 2468 2469 SELECT 2470 data, 2471 field_label, 2472 list_label, 2473 rank 2474 FROM ( 2475 (%s) UNION ALL 2476 (%s) UNION ALL 2477 (%s) UNION ALL 2478 (%s) UNION ALL 2479 (%s) UNION ALL 2480 (%s) UNION ALL 2481 (%s) 2482 ) AS all_matching_units 2483 WHERE data IS NOT NULL 2484 ORDER BY rank, list_label 2485 2486 ) AS ranked_matching_units 2487 LIMIT 50""" % ( 2488 _SQL_units_from_test_results, 2489 _SQL_units_from_test_types, 2490 _SQL_units_from_loinc_ipcc, 2491 _SQL_units_from_loinc_submitted, 2492 _SQL_units_from_loinc_example, 2493 _SQL_units_from_atc, 2494 _SQL_units_from_consumable_substance 2495 ) 2496 2497 ctxt = { 2498 'ctxt_type_pk': { 2499 'where_part': u'AND pk_test_type = %(pk_type)s', 2500 'placeholder': u'pk_type' 2501 }, 2502 'ctxt_test_name': { 2503 'where_part': u'AND %(test_name)s IN (name_tt, name_meta, abbrev_meta)', 2504 'placeholder': u'test_name' 2505 }, 2506 'ctxt_ctt': { 2507 'where_part': u'AND %(test_name)s IN (name, abbrev)', 2508 'placeholder': u'test_name' 2509 }, 2510 'ctxt_loinc': { 2511 'where_part': u'AND code = %(loinc)s', 2512 'placeholder': u'loinc' 2513 }, 2514 'ctxt_loinc_term': { 2515 'where_part': u'AND term ~* %(test_name)s', 2516 'placeholder': u'test_name' 2517 }, 2518 'ctxt_substance': { 2519 'where_part': u'AND description ~* %(substance)s', 2520 'placeholder': u'substance' 2521 } 2522 } 2523 2524 mp = gmMatchProvider.cMatchProvider_SQL2(queries = query, context = ctxt) 2525 mp.setThresholds(1, 2, 4) 2526 #mp.print_queries = True 2527 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 2528 self.matcher = mp 2529 self.SetToolTipString(_('Select the desired unit for the amount or measurement.')) 2530 self.selection_only = False 2531 self.phrase_separators = u'[;|]+'
2532 #================================================================ 2533 2534 #================================================================
2535 -class cTestResultIndicatorPhraseWheel(gmPhraseWheel.cPhraseWheel):
2536
2537 - def __init__(self, *args, **kwargs):
2538 2539 query = u""" 2540 select distinct abnormality_indicator, 2541 abnormality_indicator, abnormality_indicator 2542 from clin.v_test_results 2543 where 2544 abnormality_indicator %(fragment_condition)s 2545 order by abnormality_indicator 2546 limit 25""" 2547 2548 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 2549 mp.setThresholds(1, 1, 2) 2550 mp.ignored_chars = "[.'\\\[\]#$%_]+" + '"' 2551 mp.word_separators = '[ \t&:]+' 2552 gmPhraseWheel.cPhraseWheel.__init__ ( 2553 self, 2554 *args, 2555 **kwargs 2556 ) 2557 self.matcher = mp 2558 self.SetToolTipString(_('Select an indicator for the level of abnormality.')) 2559 self.selection_only = False
2560 2561 #================================================================ 2562 # measurement org widgets / functions 2563 #----------------------------------------------------------------
2564 -def edit_measurement_org(parent=None, org=None):
2565 ea = cMeasurementOrgEAPnl(parent = parent, id = -1) 2566 ea.data = org 2567 ea.mode = gmTools.coalesce(org, 'new', 'edit') 2568 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea) 2569 dlg.SetTitle(gmTools.coalesce(org, _('Adding new diagnostic org'), _('Editing diagnostic org'))) 2570 if dlg.ShowModal() == wx.ID_OK: 2571 dlg.Destroy() 2572 return True 2573 dlg.Destroy() 2574 return False
2575 #----------------------------------------------------------------
2576 -def manage_measurement_orgs(parent=None):
2577 2578 if parent is None: 2579 parent = wx.GetApp().GetTopWindow() 2580 2581 #------------------------------------------------------------ 2582 def edit(org=None): 2583 return edit_measurement_org(parent = parent, org = org)
2584 #------------------------------------------------------------ 2585 def refresh(lctrl): 2586 orgs = gmPathLab.get_test_orgs() 2587 lctrl.set_string_items ([ 2588 (o['unit'], o['organization'], gmTools.coalesce(o['test_org_contact'], u''), gmTools.coalesce(o['comment'], u''), o['pk_test_org']) 2589 for o in orgs 2590 ]) 2591 lctrl.set_data(orgs) 2592 #------------------------------------------------------------ 2593 def delete(test_org): 2594 gmPathLab.delete_test_org(test_org = test_org['pk_test_org']) 2595 return True 2596 #------------------------------------------------------------ 2597 gmListWidgets.get_choices_from_list ( 2598 parent = parent, 2599 msg = _('\nThese are the diagnostic orgs (path labs etc) currently defined in GNUmed.\n\n'), 2600 caption = _('Showing diagnostic orgs.'), 2601 columns = [_('Name'), _('Organization'), _('Contact'), _('Comment'), u'#'], 2602 single_selection = True, 2603 refresh_callback = refresh, 2604 edit_callback = edit, 2605 new_callback = edit, 2606 delete_callback = delete 2607 ) 2608 2609 #---------------------------------------------------------------- 2610 from Gnumed.wxGladeWidgets import wxgMeasurementOrgEAPnl 2611
2612 -class cMeasurementOrgEAPnl(wxgMeasurementOrgEAPnl.wxgMeasurementOrgEAPnl, gmEditArea.cGenericEditAreaMixin):
2613
2614 - def __init__(self, *args, **kwargs):
2615 2616 try: 2617 data = kwargs['org'] 2618 del kwargs['org'] 2619 except KeyError: 2620 data = None 2621 2622 wxgMeasurementOrgEAPnl.wxgMeasurementOrgEAPnl.__init__(self, *args, **kwargs) 2623 gmEditArea.cGenericEditAreaMixin.__init__(self) 2624 2625 self.mode = 'new' 2626 self.data = data 2627 if data is not None: 2628 self.mode = 'edit'
2629 2630 #self.__init_ui() 2631 #---------------------------------------------------------------- 2632 # def __init_ui(self): 2633 # # adjust phrasewheels etc 2634 #---------------------------------------------------------------- 2635 # generic Edit Area mixin API 2636 #----------------------------------------------------------------
2637 - def _valid_for_save(self):
2638 has_errors = False 2639 if self._PRW_org_unit.GetData() is None: 2640 if self._PRW_org_unit.GetValue().strip() == u'': 2641 has_errors = True 2642 self._PRW_org_unit.display_as_valid(valid = False) 2643 else: 2644 self._PRW_org_unit.display_as_valid(valid = True) 2645 else: 2646 self._PRW_org_unit.display_as_valid(valid = True) 2647 2648 return (not has_errors)
2649 #----------------------------------------------------------------
2650 - def _save_as_new(self):
2651 data = gmPathLab.create_test_org ( 2652 name = self._PRW_org_unit.GetValue().strip(), 2653 comment = self._TCTRL_comment.GetValue().strip(), 2654 pk_org_unit = self._PRW_org_unit.GetData() 2655 ) 2656 data['test_org_contact'] = self._TCTRL_contact.GetValue().strip() 2657 data.save() 2658 self.data = data 2659 return True
2660 #----------------------------------------------------------------
2661 - def _save_as_update(self):
2662 # get or create the org unit 2663 name = self._PRW_org_unit.GetValue().strip() 2664 org = gmOrganization.org_exists(organization = name) 2665 if org is None: 2666 org = gmOrganization.create_org ( 2667 organization = name, 2668 category = u'Laboratory' 2669 ) 2670 org_unit = gmOrganization.create_org_unit ( 2671 pk_organization = org['pk_org'], 2672 unit = name 2673 ) 2674 # update test_org fields 2675 self.data['pk_org_unit'] = org_unit['pk_org_unit'] 2676 self.data['test_org_contact'] = self._TCTRL_contact.GetValue().strip() 2677 self.data['comment'] = self._TCTRL_comment.GetValue().strip() 2678 self.data.save() 2679 return True
2680 #----------------------------------------------------------------
2681 - def _refresh_as_new(self):
2682 self._PRW_org_unit.SetText(value = u'', data = None) 2683 self._TCTRL_contact.SetValue(u'') 2684 self._TCTRL_comment.SetValue(u'')
2685 #----------------------------------------------------------------
2686 - def _refresh_from_existing(self):
2687 self._PRW_org_unit.SetText(value = self.data['unit'], data = self.data['pk_org_unit']) 2688 self._TCTRL_contact.SetValue(gmTools.coalesce(self.data['test_org_contact'], u'')) 2689 self._TCTRL_comment.SetValue(gmTools.coalesce(self.data['comment'], u''))
2690 #----------------------------------------------------------------
2692 self._refresh_as_new()
2693 #----------------------------------------------------------------
2694 - def _on_manage_orgs_button_pressed(self, event):
2695 gmOrganizationWidgets.manage_orgs(parent = self)
2696 2697 #----------------------------------------------------------------
2698 -class cMeasurementOrgPhraseWheel(gmPhraseWheel.cPhraseWheel):
2699
2700 - def __init__(self, *args, **kwargs):
2701 2702 query = u""" 2703 SELECT DISTINCT ON (list_label) 2704 pk_test_org AS data, 2705 unit || ' (' || organization || ')' AS field_label, 2706 unit || ' @ ' || organization AS list_label 2707 FROM clin.v_test_orgs 2708 WHERE 2709 unit %(fragment_condition)s 2710 OR 2711 organization %(fragment_condition)s 2712 ORDER BY list_label 2713 LIMIT 50""" 2714 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 2715 mp.setThresholds(1, 2, 4) 2716 #mp.word_separators = '[ \t:@]+' 2717 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 2718 self.matcher = mp 2719 self.SetToolTipString(_('The name of the path lab/diagnostic organisation.')) 2720 self.selection_only = False
2721 #------------------------------------------------------------
2722 - def _create_data(self):
2723 if self.GetData() is not None: 2724 _log.debug('data already set, not creating') 2725 return 2726 2727 if self.GetValue().strip() == u'': 2728 _log.debug('cannot create new lab, missing name') 2729 return 2730 2731 lab = gmPathLab.create_test_org(name = self.GetValue().strip()) 2732 self.SetText(value = lab['unit'], data = lab['pk_test_org']) 2733 return
2734 #------------------------------------------------------------
2735 - def _data2instance(self):
2736 return gmPathLab.cTestOrg(aPK_obj = self.GetData())
2737 2738 #================================================================
2739 -def manage_meta_test_types(parent=None):
2740 2741 if parent is None: 2742 parent = wx.GetApp().GetTopWindow() 2743 2744 #---------------------------------------- 2745 def get_tooltip(data): 2746 if data is None: 2747 return None 2748 return data.format(with_tests = True)
2749 #---------------------------------------- 2750 2751 msg = _( 2752 '\n' 2753 'These are the meta test types currently defined in GNUmed.\n' 2754 '\n' 2755 'Meta test types allow you to aggregate several actual test types used\n' 2756 'by pathology labs into one logical type.\n' 2757 '\n' 2758 'This is useful for grouping together results of tests which come under\n' 2759 'different names but really are the same thing. This often happens when\n' 2760 'you switch labs or the lab starts using another test method.\n' 2761 ) 2762 2763 mtts = gmPathLab.get_meta_test_types() 2764 2765 gmListWidgets.get_choices_from_list ( 2766 parent = parent, 2767 msg = msg, 2768 caption = _('Showing meta test types.'), 2769 columns = [_('Abbrev'), _('Name'), _('LOINC'), _('Comment'), u'#'], 2770 choices = [ [ 2771 m['abbrev'], 2772 m['name'], 2773 gmTools.coalesce(m['loinc'], u''), 2774 gmTools.coalesce(m['comment'], u''), 2775 m['pk'] 2776 ] for m in mtts ], 2777 data = mtts, 2778 single_selection = True, 2779 list_tooltip_callback = get_tooltip 2780 #edit_callback = edit, 2781 #new_callback = edit, 2782 #delete_callback = delete, 2783 #refresh_callback = refresh 2784 ) 2785 #----------------------------------------------------------------
2786 -class cMetaTestTypePRW(gmPhraseWheel.cPhraseWheel):
2787
2788 - def __init__(self, *args, **kwargs):
2789 2790 query = u""" 2791 SELECT DISTINCT ON (field_label) 2792 c_mtt.pk 2793 AS data, 2794 c_mtt.abbrev || ': ' || name 2795 AS field_label, 2796 c_mtt.abbrev || ': ' || name 2797 || coalesce ( 2798 ' (' || c_mtt.comment || ')', 2799 '' 2800 ) 2801 || coalesce ( 2802 ', LOINC: ' || c_mtt.loinc, 2803 '' 2804 ) 2805 AS list_label 2806 FROM 2807 clin.meta_test_type c_mtt 2808 WHERE 2809 abbrev %(fragment_condition)s 2810 OR 2811 name %(fragment_condition)s 2812 OR 2813 loinc %(fragment_condition)s 2814 ORDER BY field_label 2815 LIMIT 50""" 2816 2817 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 2818 mp.setThresholds(1, 2, 4) 2819 mp.word_separators = '[ \t:@]+' 2820 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 2821 self.matcher = mp 2822 self.SetToolTipString(_('Select the meta test type.')) 2823 self.selection_only = True
2824 #------------------------------------------------------------
2825 - def _data2instance(self):
2826 if self.GetData() is None: 2827 return None 2828 2829 return gmPathLab.cMetaTestType(aPK_obj = self.GetData())
2830 2831 #================================================================ 2832 # test panel handling 2833 #================================================================
2834 -def edit_test_panel(parent=None, test_panel=None):
2835 ea = cTestPanelEAPnl(parent = parent, id = -1) 2836 ea.data = test_panel 2837 ea.mode = gmTools.coalesce(test_panel, 'new', 'edit') 2838 dlg = gmEditArea.cGenericEditAreaDlg2 ( 2839 parent = parent, 2840 id = -1, 2841 edit_area = ea, 2842 single_entry = gmTools.bool2subst((test_panel is None), False, True) 2843 ) 2844 dlg.SetTitle(gmTools.coalesce(test_panel, _('Adding new test panel'), _('Editing test panel'))) 2845 if dlg.ShowModal() == wx.ID_OK: 2846 dlg.Destroy() 2847 return True 2848 dlg.Destroy() 2849 return False
2850 2851 #----------------------------------------------------------------
2852 -def manage_test_panels(parent=None):
2853 2854 if parent is None: 2855 parent = wx.GetApp().GetTopWindow() 2856 2857 #------------------------------------------------------------ 2858 def edit(test_panel=None): 2859 return edit_test_panel(parent = parent, test_panel = test_panel)
2860 #------------------------------------------------------------ 2861 def delete(test_panel): 2862 gmPathLab.delete_test_panel(pk = test_panel['pk_test_panel']) 2863 return True 2864 #------------------------------------------------------------ 2865 def get_tooltip(test_panel): 2866 return test_panel.format() 2867 #------------------------------------------------------------ 2868 def refresh(lctrl): 2869 panels = gmPathLab.get_test_panels(order_by = 'description') 2870 items = [ [ 2871 p['description'], 2872 gmTools.coalesce(p['comment'], u''), 2873 p['pk_test_panel'] 2874 ] for p in panels ] 2875 lctrl.set_string_items(items) 2876 lctrl.set_data(panels) 2877 #------------------------------------------------------------ 2878 msg = _( 2879 '\n' 2880 'Test panels as defined in GNUmed.\n' 2881 ) 2882 2883 gmListWidgets.get_choices_from_list ( 2884 parent = parent, 2885 msg = msg, 2886 caption = _('Showing test panels.'), 2887 columns = [ _('Name'), _('Comment'), u'#' ], 2888 single_selection = True, 2889 refresh_callback = refresh, 2890 edit_callback = edit, 2891 new_callback = edit, 2892 delete_callback = delete, 2893 list_tooltip_callback = get_tooltip 2894 ) 2895 2896 #----------------------------------------------------------------
2897 -class cTestPanelPRW(gmPhraseWheel.cPhraseWheel):
2898
2899 - def __init__(self, *args, **kwargs):
2900 query = u""" 2901 SELECT 2902 pk_test_panel 2903 AS data, 2904 description 2905 AS field_label, 2906 description 2907 AS list_label 2908 FROM 2909 clin.v_test_panels 2910 WHERE 2911 description %(fragment_condition)s 2912 ORDER BY field_label 2913 LIMIT 30""" 2914 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 2915 mp.setThresholds(1, 2, 4) 2916 #mp.word_separators = '[ \t:@]+' 2917 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 2918 self.matcher = mp 2919 self.SetToolTipString(_('Select a test panel.')) 2920 self.selection_only = True
2921 #------------------------------------------------------------
2922 - def _data2instance(self):
2923 if self.GetData() is None: 2924 return None 2925 return gmPathLab.cTestPanel(aPK_obj = self.GetData())
2926 #------------------------------------------------------------
2927 - def _get_data_tooltip(self):
2928 if self.GetData() is None: 2929 return None 2930 return gmPathLab.cTestPanel(aPK_obj = self.GetData()).format()
2931 2932 #==================================================================== 2933 from Gnumed.wxGladeWidgets import wxgTestPanelEAPnl 2934
2935 -class cTestPanelEAPnl(wxgTestPanelEAPnl.wxgTestPanelEAPnl, gmEditArea.cGenericEditAreaMixin):
2936
2937 - def __init__(self, *args, **kwargs):
2938 2939 try: 2940 data = kwargs['panel'] 2941 del kwargs['panel'] 2942 except KeyError: 2943 data = None 2944 2945 wxgTestPanelEAPnl.wxgTestPanelEAPnl.__init__(self, *args, **kwargs) 2946 gmEditArea.cGenericEditAreaMixin.__init__(self) 2947 2948 self._test_types = None 2949 2950 self.mode = 'new' 2951 self.data = data 2952 if data is not None: 2953 self.mode = 'edit'
2954 2955 #self.__init_ui() 2956 #---------------------------------------------------------------- 2957 # def __init_ui(self): 2958 # # adjust phrasewheels etc 2959 #---------------------------------------------------------------- 2960 # generic Edit Area mixin API 2961 #----------------------------------------------------------------
2962 - def _valid_for_save(self):
2963 validity = True 2964 2965 if self._test_types is None: 2966 validity = False 2967 gmDispatcher.send(signal = 'statustext', msg = _('No test types selected.')) 2968 self._BTN_select_tests.SetFocus() 2969 2970 if self._TCTRL_description.GetValue().strip() == u'': 2971 validity = False 2972 self.display_tctrl_as_valid(tctrl = self._TCTRL_description, valid = False) 2973 self._TCTRL_description.SetFocus() 2974 else: 2975 self.display_tctrl_as_valid(tctrl = self._TCTRL_description, valid = True) 2976 2977 return validity
2978 #----------------------------------------------------------------
2979 - def _save_as_new(self):
2980 data = gmPathLab.create_test_panel(description = self._TCTRL_description.GetValue().strip()) 2981 data['comment'] = self._TCTRL_comment.GetValue().strip() 2982 data['pk_test_types'] = [ tt['pk_test_type'] for tt in self._test_types ] 2983 data.save() 2984 data.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ] 2985 self.data = data 2986 return True
2987 #----------------------------------------------------------------
2988 - def _save_as_update(self):
2989 self.data['description'] = self._TCTRL_description.GetValue().strip() 2990 self.data['comment'] = self._TCTRL_comment.GetValue().strip() 2991 self.data['pk_test_types'] = [ tt['pk_test_type'] for tt in self._test_types ] 2992 self.data.save() 2993 self.data.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ] 2994 return True
2995 #----------------------------------------------------------------
2996 - def __refresh_test_types_field(self, test_types=None):
2997 self._TCTRL_tests.SetValue(u'') 2998 self._test_types = test_types 2999 if self._test_types is None: 3000 return 3001 tmp = u';\n'.join ([ 3002 u'%s: %s%s' % ( 3003 t['unified_abbrev'], 3004 t['unified_name'], 3005 gmTools.coalesce(t['name_org'], u'', u' (%s)') 3006 ) 3007 for t in self._test_types 3008 ]) 3009 self._TCTRL_tests.SetValue(tmp)
3010 #----------------------------------------------------------------
3011 - def _refresh_as_new(self):
3012 self._TCTRL_description.SetValue(u'') 3013 self._TCTRL_comment.SetValue(u'') 3014 self.__refresh_test_types_field() 3015 self._PRW_codes.SetText() 3016 3017 self._TCTRL_description.SetFocus()
3018 #----------------------------------------------------------------
3020 self._refresh_as_new() 3021 self.__refresh_test_types_field(test_types = self.data.test_types)
3022 #----------------------------------------------------------------
3023 - def _refresh_from_existing(self):
3024 self._TCTRL_description.SetValue(self.data['description']) 3025 self._TCTRL_comment.SetValue(gmTools.coalesce(self.data['comment'], u'')) 3026 self.__refresh_test_types_field(test_types = self.data.test_types) 3027 val, data = self._PRW_codes.generic_linked_codes2item_dict(self.data.generic_codes) 3028 self._PRW_codes.SetText(val, data) 3029 3030 self._BTN_select_tests.SetFocus()
3031 #----------------------------------------------------------------
3032 - def _on_select_tests_button_pressed(self, event):
3033 desc = self._TCTRL_description.GetValue().strip() 3034 if desc == u'': 3035 desc = None 3036 picked = pick_measurement_types ( 3037 parent = self, 3038 msg = _('Pick the measurement types for this panel.'), 3039 right_column = desc, 3040 picks = self._test_types 3041 ) 3042 if picked is None: 3043 return 3044 if len(picked) == 0: 3045 picked = None 3046 self.__refresh_test_types_field(test_types = picked)
3047 3048 #================================================================ 3049 # main 3050 #---------------------------------------------------------------- 3051 if __name__ == '__main__': 3052 3053 from Gnumed.pycommon import gmLog2 3054 from Gnumed.wxpython import gmPatSearchWidgets 3055 3056 gmI18N.activate_locale() 3057 gmI18N.install_domain() 3058 gmDateTime.init() 3059 3060 #------------------------------------------------------------
3061 - def test_grid():
3062 pat = gmPersonSearch.ask_for_patient() 3063 app = wx.PyWidgetTester(size = (500, 300)) 3064 lab_grid = cMeasurementsGrid(parent = app.frame, id = -1) 3065 lab_grid.patient = pat 3066 app.frame.Show() 3067 app.MainLoop()
3068 #------------------------------------------------------------
3069 - def test_test_ea_pnl():
3070 pat = gmPersonSearch.ask_for_patient() 3071 gmPatSearchWidgets.set_active_patient(patient=pat) 3072 app = wx.PyWidgetTester(size = (500, 300)) 3073 ea = cMeasurementEditAreaPnl(parent = app.frame, id = -1) 3074 app.frame.Show() 3075 app.MainLoop()
3076 #------------------------------------------------------------ 3077 # def test_primary_care_vitals_pnl(): 3078 # app = wx.PyWidgetTester(size = (500, 300)) 3079 # pnl = wxgPrimaryCareVitalsInputPnl.wxgPrimaryCareVitalsInputPnl(parent = app.frame, id = -1) 3080 # app.frame.Show() 3081 # app.MainLoop() 3082 #------------------------------------------------------------ 3083 if (len(sys.argv) > 1) and (sys.argv[1] == 'test'): 3084 #test_grid() 3085 test_test_ea_pnl() 3086 #test_primary_care_vitals_pnl() 3087 3088 #================================================================ 3089