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