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