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

Source Code for Module Gnumed.wxpython.gmMeasurementWidgets

   1  """GNUmed measurement widgets.""" 
   2  #================================================================ 
   3  __author__ = "Karsten Hilbert <Karsten.Hilbert@gmx.net>" 
   4  __license__ = "GPL" 
   5   
   6   
   7  import sys 
   8  import logging 
   9  import datetime as pyDT 
  10  import decimal 
  11  import os 
  12  import subprocess 
  13  import io 
  14  import os.path 
  15   
  16   
  17  import wx 
  18  import wx.grid 
  19  import wx.adv as wxh 
  20   
  21   
  22  if __name__ == '__main__': 
  23          sys.path.insert(0, '../../') 
  24  from Gnumed.pycommon import gmTools 
  25  from Gnumed.pycommon import gmNetworkTools 
  26  from Gnumed.pycommon import gmI18N 
  27  from Gnumed.pycommon import gmShellAPI 
  28  from Gnumed.pycommon import gmCfg 
  29  from Gnumed.pycommon import gmDateTime 
  30  from Gnumed.pycommon import gmMatchProvider 
  31  from Gnumed.pycommon import gmDispatcher 
  32  from Gnumed.pycommon import gmMimeLib 
  33   
  34  from Gnumed.business import gmPerson 
  35  from Gnumed.business import gmStaff 
  36  from Gnumed.business import gmPathLab 
  37  from Gnumed.business import gmPraxis 
  38  from Gnumed.business import gmLOINC 
  39  from Gnumed.business import gmForms 
  40  from Gnumed.business import gmPersonSearch 
  41  from Gnumed.business import gmOrganization 
  42  from Gnumed.business import gmHL7 
  43  from Gnumed.business import gmIncomingData 
  44  from Gnumed.business import gmDocuments 
  45   
  46  from Gnumed.wxpython import gmRegetMixin 
  47  from Gnumed.wxpython import gmPlugin 
  48  from Gnumed.wxpython import gmEditArea 
  49  from Gnumed.wxpython import gmPhraseWheel 
  50  from Gnumed.wxpython import gmListWidgets 
  51  from Gnumed.wxpython import gmGuiHelpers 
  52  from Gnumed.wxpython import gmAuthWidgets 
  53  from Gnumed.wxpython import gmOrganizationWidgets 
  54  from Gnumed.wxpython import gmEMRStructWidgets 
  55  from Gnumed.wxpython import gmCfgWidgets 
  56  from Gnumed.wxpython import gmDocumentWidgets 
  57   
  58   
  59  _log = logging.getLogger('gm.ui') 
  60   
  61  #================================================================ 
  62  # HL7 related widgets 
  63  #================================================================ 
64 -def show_hl7_file(parent=None):
65 66 if parent is None: 67 parent = wx.GetApp().GetTopWindow() 68 69 # select file 70 paths = gmTools.gmPaths() 71 dlg = wx.FileDialog ( 72 parent = parent, 73 message = _('Show HL7 file:'), 74 # make configurable: 75 defaultDir = os.path.join(paths.home_dir, 'gnumed'), 76 wildcard = "hl7 files|*.hl7|HL7 files|*.HL7|all files|*", 77 style = wx.FD_OPEN | wx.FD_FILE_MUST_EXIST 78 ) 79 choice = dlg.ShowModal() 80 hl7_name = dlg.GetPath() 81 dlg.DestroyLater() 82 if choice != wx.ID_OK: 83 return False 84 85 formatted_name = gmHL7.format_hl7_file ( 86 hl7_name, 87 skip_empty_fields = True, 88 return_filename = True, 89 fix_hl7 = True 90 ) 91 gmMimeLib.call_viewer_on_file(aFile = formatted_name, block = False) 92 return True
93 94 #================================================================
95 -def unwrap_HL7_from_XML(parent=None):
96 97 if parent is None: 98 parent = wx.GetApp().GetTopWindow() 99 100 # select file 101 paths = gmTools.gmPaths() 102 dlg = wx.FileDialog ( 103 parent = parent, 104 message = _('Extract HL7 from XML file:'), 105 # make configurable: 106 defaultDir = os.path.join(paths.home_dir, 'gnumed'), 107 wildcard = "xml files|*.xml|XML files|*.XML|all files|*", 108 style = wx.FD_OPEN | wx.FD_FILE_MUST_EXIST 109 ) 110 choice = dlg.ShowModal() 111 xml_name = dlg.GetPath() 112 dlg.DestroyLater() 113 if choice != wx.ID_OK: 114 return False 115 116 target_dir = os.path.split(xml_name)[0] 117 xml_path = './/Message' 118 hl7_name = gmHL7.extract_HL7_from_XML_CDATA(xml_name, xml_path, target_dir = target_dir) 119 if hl7_name is None: 120 gmGuiHelpers.gm_show_error ( 121 title = _('Extracting HL7 from XML file'), 122 error = ( 123 'Cannot unwrap HL7 data from XML file\n' 124 '\n' 125 ' [%s]\n' 126 '\n' 127 '(CDATA of [%s] nodes)' 128 ) % ( 129 xml_name, 130 xml_path 131 ) 132 ) 133 return False 134 135 gmDispatcher.send(signal = 'statustext', msg = _('Unwrapped HL7 into [%s] from [%s].') % (hl7_name, xml_name), beep = False) 136 return True
137 138 #================================================================
139 -def stage_hl7_file(parent=None):
140 141 if parent is None: 142 parent = wx.GetApp().GetTopWindow() 143 144 paths = gmTools.gmPaths() 145 dlg = wx.FileDialog ( 146 parent = parent, 147 message = _('Select HL7 file for staging:'), 148 # make configurable: 149 defaultDir = os.path.join(paths.home_dir, 'gnumed'), 150 wildcard = ".hl7 files|*.hl7|.HL7 files|*.HL7|all files|*", 151 style = wx.FD_OPEN | wx.FD_FILE_MUST_EXIST 152 ) 153 choice = dlg.ShowModal() 154 hl7_name = dlg.GetPath() 155 dlg.DestroyLater() 156 if choice != wx.ID_OK: 157 return False 158 159 target_dir = os.path.join(paths.home_dir, '.gnumed', 'hl7') 160 success, PID_names = gmHL7.split_hl7_file(hl7_name, target_dir = target_dir, encoding = 'utf8') 161 if not success: 162 gmGuiHelpers.gm_show_error ( 163 title = _('Staging HL7 file'), 164 error = _( 165 'There was a problem with splitting the HL7 file\n' 166 '\n' 167 ' %s' 168 ) % hl7_name 169 ) 170 return False 171 172 failed_files = [] 173 for PID_name in PID_names: 174 if not gmHL7.stage_single_PID_hl7_file(PID_name, source = _('generic'), encoding = 'utf8'): 175 failed_files.append(PID_name) 176 if len(failed_files) > 0: 177 gmGuiHelpers.gm_show_error ( 178 title = _('Staging HL7 file'), 179 error = _( 180 'There was a problem with staging the following files\n' 181 '\n' 182 ' %s' 183 ) % '\n '.join(failed_files) 184 ) 185 return False 186 187 gmDispatcher.send(signal = 'statustext', msg = _('Staged HL7 from [%s].') % hl7_name, beep = False) 188 return True
189 190 #================================================================
191 -def browse_incoming_unmatched(parent=None):
192 193 if parent is None: 194 parent = wx.GetApp().GetTopWindow() 195 #------------------------------------------------------------ 196 def show_hl7(staged_item): 197 if staged_item is None: 198 return False 199 if 'HL7' not in staged_item['data_type']: 200 return False 201 filename = staged_item.save_to_file() 202 if filename is None: 203 filename = gmTools.get_unique_filename() 204 tmp_file = io.open(filename, mode = 'at', encoding = 'utf8') 205 tmp_file.write('\n') 206 tmp_file.write('-' * 80) 207 tmp_file.write('\n') 208 tmp_file.write(gmTools.coalesce(staged_item['comment'], '')) 209 tmp_file.close() 210 gmMimeLib.call_viewer_on_file(aFile = filename, block = False) 211 return False
212 #------------------------------------------------------------ 213 def import_hl7(staged_item): 214 if staged_item is None: 215 return False 216 if 'HL7' not in staged_item['data_type']: 217 return False 218 unset_identity_on_error = False 219 if staged_item['pk_identity_disambiguated'] is None: 220 pat = gmPerson.gmCurrentPatient() 221 if pat.connected: 222 answer = gmGuiHelpers.gm_show_question ( 223 title = _('Importing HL7 data'), 224 question = _( 225 'There has not been a patient explicitely associated\n' 226 'with this chunk of HL7 data. However, the data file\n' 227 'contains the following patient identification information:\n' 228 '\n' 229 ' %s\n' 230 '\n' 231 'Do you want to import the HL7 under the current patient ?\n' 232 '\n' 233 ' %s\n' 234 '\n' 235 'Selecting [NO] makes GNUmed try to find a patient matching the HL7 data.\n' 236 ) % ( 237 staged_item.patient_identification, 238 pat['description_gender'] 239 ), 240 cancel_button = True 241 ) 242 if answer is None: 243 return False 244 if answer is True: 245 unset_identity_on_error = True 246 staged_item['pk_identity_disambiguated'] = pat.ID 247 248 success, log_name = gmHL7.process_staged_single_PID_hl7_file(staged_item) 249 if success: 250 return True 251 252 if unset_identity_on_error: 253 staged_item['pk_identity_disambiguated'] = None 254 staged_item.save() 255 256 gmGuiHelpers.gm_show_error ( 257 error = _('Error processing HL7 data.'), 258 title = _('Processing staged HL7 data.') 259 ) 260 return False 261 262 #------------------------------------------------------------ 263 def delete(staged_item): 264 if staged_item is None: 265 return False 266 do_delete = gmGuiHelpers.gm_show_question ( 267 title = _('Deleting incoming data'), 268 question = _( 269 'Do you really want to delete the incoming data ?\n' 270 '\n' 271 'Note that deletion is not reversible.' 272 ) 273 ) 274 if not do_delete: 275 return False 276 return gmIncomingData.delete_incoming_data(pk_incoming_data = staged_item['pk_incoming_data_unmatched']) 277 #------------------------------------------------------------ 278 def refresh(lctrl): 279 incoming = gmIncomingData.get_incoming_data() 280 items = [ [ 281 gmTools.coalesce(i['data_type'], ''), 282 '%s, %s (%s) %s' % ( 283 gmTools.coalesce(i['lastnames'], ''), 284 gmTools.coalesce(i['firstnames'], ''), 285 gmDateTime.pydt_strftime(dt = i['dob'], format = '%Y %b %d', accuracy = gmDateTime.acc_days, none_str = _('unknown DOB')), 286 gmTools.coalesce(i['gender'], '') 287 ), 288 gmTools.coalesce(i['external_data_id'], ''), 289 i['pk_incoming_data_unmatched'] 290 ] for i in incoming ] 291 lctrl.set_string_items(items) 292 lctrl.set_data(incoming) 293 #------------------------------------------------------------ 294 gmListWidgets.get_choices_from_list ( 295 parent = parent, 296 msg = None, 297 caption = _('Showing unmatched incoming data'), 298 columns = [ _('Type'), _('Identification'), _('Reference'), '#' ], 299 single_selection = True, 300 can_return_empty = False, 301 ignore_OK_button = True, 302 refresh_callback = refresh, 303 # edit_callback=None, 304 # new_callback=None, 305 delete_callback = delete, 306 left_extra_button = [_('Show'), _('Show formatted HL7'), show_hl7], 307 middle_extra_button = [_('Import'), _('Import HL7 data into patient chart'), import_hl7] 308 # right_extra_button=None 309 ) 310 311 #================================================================ 312 # convenience functions 313 #================================================================
314 -def call_browser_on_measurement_type(measurement_type=None):
315 316 dbcfg = gmCfg.cCfgSQL() 317 318 url = dbcfg.get2 ( 319 option = 'external.urls.measurements_search', 320 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace, 321 bias = 'user', 322 default = gmPathLab.URL_test_result_information_search 323 ) 324 325 base_url = dbcfg.get2 ( 326 option = 'external.urls.measurements_encyclopedia', 327 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace, 328 bias = 'user', 329 default = gmPathLab.URL_test_result_information 330 ) 331 332 if measurement_type is None: 333 url = base_url 334 335 measurement_type = measurement_type.strip() 336 337 if measurement_type == '': 338 url = base_url 339 340 url = url % {'search_term': measurement_type} 341 342 gmNetworkTools.open_url_in_browser(url = url)
343 344 #----------------------------------------------------------------
345 -def edit_measurement(parent=None, measurement=None, single_entry=False, presets=None):
346 ea = cMeasurementEditAreaPnl(parent, -1) 347 ea.data = measurement 348 ea.mode = gmTools.coalesce(measurement, 'new', 'edit') 349 dlg = gmEditArea.cGenericEditAreaDlg2(parent, -1, edit_area = ea, single_entry = single_entry) 350 dlg.SetTitle(gmTools.coalesce(measurement, _('Adding new measurement'), _('Editing measurement'))) 351 if presets is not None: 352 ea.set_fields(presets) 353 if dlg.ShowModal() == wx.ID_OK: 354 dlg.DestroyLater() 355 return True 356 dlg.DestroyLater() 357 return False
358 359 #----------------------------------------------------------------
360 -def manage_measurements(parent=None, single_selection=False, emr=None):
361 362 if parent is None: 363 parent = wx.GetApp().GetTopWindow() 364 365 if emr is None: 366 emr = gmPerson.gmCurrentPatient().emr 367 368 #------------------------------------------------------------ 369 def edit(measurement=None): 370 return edit_measurement(parent = parent, measurement = measurement, single_entry = True)
371 #------------------------------------------------------------ 372 def delete(measurement): 373 gmPathLab.delete_test_result(result = measurement) 374 return True 375 #------------------------------------------------------------ 376 def do_review(lctrl): 377 data = lctrl.get_selected_item_data() 378 if len(data) == 0: 379 return 380 return review_tests(parent = parent, tests = data) 381 #------------------------------------------------------------ 382 def do_plot(lctrl): 383 data = lctrl.get_selected_item_data() 384 if len(data) == 0: 385 return 386 return plot_measurements(parent = parent, tests = data) 387 #------------------------------------------------------------ 388 def get_tooltip(measurement): 389 return measurement.format(with_review=True, with_evaluation=True, with_ranges=True) 390 #------------------------------------------------------------ 391 def refresh(lctrl): 392 results = emr.get_test_results(order_by = 'clin_when DESC, unified_abbrev, unified_name') 393 items = [ [ 394 gmDateTime.pydt_strftime ( 395 r['clin_when'], 396 '%Y %b %d %H:%M', 397 accuracy = gmDateTime.acc_minutes 398 ), 399 r['unified_abbrev'], 400 '%s%s%s%s' % ( 401 gmTools.bool2subst ( 402 boolean = (not r['reviewed'] or (not r['review_by_you'] and r['you_are_responsible'])), 403 true_return = 'u' + gmTools.u_writing_hand, 404 false_return = '' 405 ), 406 r['unified_val'], 407 gmTools.coalesce(r['val_unit'], '', ' %s'), 408 gmTools.coalesce(r['abnormality_indicator'], '', ' %s') 409 ), 410 r['unified_name'], 411 # u'%s%s' % ( 412 # gmTools.bool2subst ( 413 # boolean = not r['reviewed'], 414 # true_return = _('no review at all'), 415 # false_return = gmTools.bool2subst ( 416 # boolean = (r['you_are_responsible'] and not r['review_by_you']), 417 # true_return = _('no review by you (you are responsible)'), 418 # false_return = _('reviewed') 419 # ) 420 # ), 421 # gmTools.coalesce(r['comment'], u'', u' / %s') 422 # ), 423 gmTools.coalesce(r['comment'], ''), 424 r['pk_test_result'] 425 ] for r in results ] 426 lctrl.set_string_items(items) 427 lctrl.set_data(results) 428 429 #------------------------------------------------------------ 430 msg = _('Test results (ordered reverse-chronologically)') 431 432 return gmListWidgets.get_choices_from_list ( 433 parent = parent, 434 msg = msg, 435 caption = _('Showing test results.'), 436 columns = [ _('When'), _('Abbrev'), _('Value'), _('Name'), _('Comment'), '#' ], 437 single_selection = single_selection, 438 can_return_empty = False, 439 refresh_callback = refresh, 440 edit_callback = edit, 441 new_callback = edit, 442 delete_callback = delete, 443 list_tooltip_callback = get_tooltip, 444 left_extra_button = (_('Review'), _('Review current selection'), do_review, True), 445 middle_extra_button = (_('Plot'), _('Plot current selection'), do_plot, True) 446 ) 447 448 #================================================================
449 -def configure_default_top_lab_panel(parent=None):
450 451 if parent is None: 452 parent = wx.GetApp().GetTopWindow() 453 454 panels = gmPathLab.get_test_panels(order_by = 'description') 455 gmCfgWidgets.configure_string_from_list_option ( 456 parent = parent, 457 message = _('Select the measurements panel to show in the top pane for continuous monitoring.'), 458 option = 'horstspace.top_panel.lab_panel', 459 bias = 'user', 460 default_value = None, 461 choices = [ '%s%s' % (p['description'], gmTools.coalesce(p['comment'], '', ' (%s)')) for p in panels ], 462 columns = [_('Lab panel')], 463 data = [ p['pk_test_panel'] for p in panels ], 464 caption = _('Configuring continuous monitoring measurements panel') 465 )
466 467 #================================================================
468 -def configure_default_gnuplot_template(parent=None):
469 470 from Gnumed.wxpython import gmFormWidgets 471 472 if parent is None: 473 parent = wx.GetApp().GetTopWindow() 474 475 template = gmFormWidgets.manage_form_templates ( 476 parent = parent, 477 active_only = True, 478 template_types = ['gnuplot script'] 479 ) 480 481 option = 'form_templates.default_gnuplot_template' 482 483 if template is None: 484 gmDispatcher.send(signal = 'statustext', msg = _('No default Gnuplot script template selected.'), beep = True) 485 return None 486 487 if template['engine'] != 'G': 488 gmDispatcher.send(signal = 'statustext', msg = _('No default Gnuplot script template selected.'), beep = True) 489 return None 490 491 dbcfg = gmCfg.cCfgSQL() 492 dbcfg.set ( 493 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace, 494 option = option, 495 value = '%s - %s' % (template['name_long'], template['external_version']) 496 ) 497 return template
498 499 #============================================================
500 -def get_default_gnuplot_template(parent = None):
501 502 option = 'form_templates.default_gnuplot_template' 503 504 dbcfg = gmCfg.cCfgSQL() 505 506 # load from option 507 default_template_name = dbcfg.get2 ( 508 option = option, 509 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace, 510 bias = 'user' 511 ) 512 513 # not configured -> try to configure 514 if default_template_name is None: 515 gmDispatcher.send('statustext', msg = _('No default Gnuplot template configured.'), beep = False) 516 default_template = configure_default_gnuplot_template(parent = parent) 517 # still not configured -> return 518 if default_template is None: 519 gmGuiHelpers.gm_show_error ( 520 aMessage = _('There is no default Gnuplot one-type script template configured.'), 521 aTitle = _('Plotting test results') 522 ) 523 return None 524 return default_template 525 526 # now it MUST be configured (either newly or previously) 527 # but also *validly* ? 528 try: 529 name, ver = default_template_name.split(' - ') 530 except: 531 # not valid 532 _log.exception('problem splitting Gnuplot script template name [%s]', default_template_name) 533 gmDispatcher.send(signal = 'statustext', msg = _('Problem loading Gnuplot script template.'), beep = True) 534 return None 535 536 default_template = gmForms.get_form_template(name_long = name, external_version = ver) 537 if default_template is None: 538 default_template = configure_default_gnuplot_template(parent = parent) 539 # still not configured -> return 540 if default_template is None: 541 gmGuiHelpers.gm_show_error ( 542 aMessage = _('Cannot load default Gnuplot script template [%s - %s]') % (name, ver), 543 aTitle = _('Plotting test results') 544 ) 545 return None 546 547 return default_template
548 549 #----------------------------------------------------------------
550 -def plot_measurements(parent=None, tests=None, format=None, show_year = True, use_default_template=False):
551 552 from Gnumed.wxpython import gmFormWidgets 553 554 # only valid for one-type plotting 555 if use_default_template: 556 template = get_default_gnuplot_template() 557 else: 558 template = gmFormWidgets.manage_form_templates ( 559 parent = parent, 560 active_only = True, 561 template_types = ['gnuplot script'] 562 ) 563 564 if template is None: 565 gmGuiHelpers.gm_show_error ( 566 aMessage = _('Cannot plot without a plot script.'), 567 aTitle = _('Plotting test results') 568 ) 569 return False 570 571 fname_data = gmPathLab.export_results_for_gnuplot(results = tests, show_year = show_year) 572 573 script = template.instantiate() 574 script.data_filename = fname_data 575 script.generate_output(format = format) # Gnuplot output terminal, wxt = wxWidgets window
576 577 #----------------------------------------------------------------
578 -def plot_adjacent_measurements(parent=None, test=None, format=None, show_year=True, plot_singular_result=True, use_default_template=False):
579 580 earlier, later = test.get_adjacent_results(desired_earlier_results = 2, desired_later_results = 2) 581 results2plot = [] 582 if earlier is not None: 583 results2plot.extend(earlier) 584 results2plot.append(test) 585 if later is not None: 586 results2plot.extend(later) 587 if len(results2plot) == 1: 588 if not plot_singular_result: 589 return 590 plot_measurements ( 591 parent = parent, 592 tests = results2plot, 593 format = format, 594 show_year = show_year, 595 use_default_template = use_default_template 596 )
597 598 #================================================================ 599 #from Gnumed.wxGladeWidgets import wxgPrimaryCareVitalsInputPnl 600 # 601 # Taillenumfang: Mitte zwischen unterster Rippe und 602 # hoechstem Teil des Beckenkamms 603 # Maenner: maessig: 94-102, deutlich: > 102 .. erhoeht 604 # Frauen: maessig: 80-88, deutlich: > 88 .. erhoeht 605 # 606 #================================================================ 607 # display widgets 608 #================================================================ 609 from Gnumed.wxGladeWidgets import wxgLabRelatedDocumentsPnl 610
611 -class cLabRelatedDocumentsPnl(wxgLabRelatedDocumentsPnl.wxgLabRelatedDocumentsPnl):
612 """This panel handles documents related to the lab result it is handed. 613 """
614 - def __init__(self, *args, **kwargs):
615 wxgLabRelatedDocumentsPnl.wxgLabRelatedDocumentsPnl.__init__(self, *args, **kwargs) 616 617 self.__reference = None 618 619 self.__init_ui() 620 self.__register_events()
621 622 #------------------------------------------------------------ 623 # internal helpers 624 #------------------------------------------------------------
625 - def __init_ui(self):
626 self.__repopulate_ui()
627 628 #------------------------------------------------------------
629 - def __register_events(self):
630 gmDispatcher.connect(signal = 'gm_table_mod', receiver = self._on_database_signal)
631 632 #------------------------------------------------------------
633 - def __repopulate_ui(self):
634 self._BTN_list_documents.Disable() 635 self._LBL_no_of_docs.SetLabel(_('no related documents')) 636 self._LBL_no_of_docs.ContainingSizer.Layout() 637 638 if self.__reference is None: 639 self._LBL_no_of_docs.SetToolTip(_('There is no lab reference to find related documents for.')) 640 return 641 642 dbcfg = gmCfg.cCfgSQL() 643 lab_doc_types = dbcfg.get2 ( 644 option = 'horstspace.lab_doc_types', 645 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace, 646 bias = 'user' 647 ) 648 if lab_doc_types is None: 649 self._LBL_no_of_docs.SetToolTip(_('No document types declared to contain lab results.')) 650 return 651 652 if len(lab_doc_types) == 0: 653 self._LBL_no_of_docs.SetToolTip(_('No document types declared to contain lab results.')) 654 return 655 656 pks_doc_types = gmDocuments.map_types2pk(lab_doc_types) 657 if len(pks_doc_types) == 0: 658 self._LBL_no_of_docs.SetToolTip(_('No valid document types declared to contain lab results.')) 659 return 660 661 txt = _('Document types assumed to contain lab results:') 662 txt += '\n ' 663 txt += '\n '.join(lab_doc_types) 664 self._LBL_no_of_docs.SetToolTip(txt) 665 if isinstance(self.__reference, gmPathLab.cTestResult): 666 pk_current_episode = self.__reference['pk_episode'] 667 else: 668 pk_current_episode = self.__reference 669 docs = gmDocuments.search_for_documents ( 670 pk_episode = pk_current_episode, 671 pk_types = [ dt['pk_doc_type'] for dt in pks_doc_types ] 672 ) 673 if len(docs) == 0: 674 return 675 676 self._LBL_no_of_docs.SetLabel(_('Related documents: %s') % len(docs)) 677 self._LBL_no_of_docs.ContainingSizer.Layout() 678 self._BTN_list_documents.Enable()
679 680 #------------------------------------------------------------ 681 # event handlers 682 #------------------------------------------------------------
683 - def _on_database_signal(self, **kwds):
684 if self.__reference is None: 685 return True 686 687 if kwds['table'] not in ['clin.test_result', 'blobs.doc_med']: 688 return True 689 690 if isinstance(self.__reference, gmPathLab.cTestResult): 691 if kwds['pk_of_row'] != self.__reference['pk_test_result']: 692 return True 693 694 self.__repopulate_ui() 695 return True
696 697 #------------------------------------------------------------
699 event.Skip() 700 doc_types = gmDocuments.get_document_types() 701 gmCfgWidgets.configure_list_from_list_option ( 702 parent = self, 703 message = _('Select the document types which are assumed to contain lab results.'), 704 option = 'horstspace.lab_doc_types', 705 bias = 'user', 706 choices = [ dt['l10n_type'] for dt in doc_types ], 707 columns = [_('Document types')]#, 708 #data = None, 709 #caption = None, 710 #picks = None 711 ) 712 self.__repopulate_ui()
713 714 #------------------------------------------------------------
715 - def _on_list_documents_button_pressed(self, event):
716 event.Skip() 717 dbcfg = gmCfg.cCfgSQL() 718 lab_doc_types = dbcfg.get2 ( 719 option = 'horstspace.lab_doc_types', 720 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace, 721 bias = 'user' 722 ) 723 d_types = gmDocuments.map_types2pk(lab_doc_types) 724 if isinstance(self.__reference, gmPathLab.cTestResult): 725 pk_current_episode = self.__reference['pk_episode'] 726 else: 727 pk_current_episode = self.__reference 728 gmDocumentWidgets.manage_documents ( 729 parent = self, 730 msg = _('Documents possibly related to this episode'), 731 pk_types = [ dt['pk_doc_type'] for dt in d_types ], 732 pk_episodes = [ pk_current_episode ] 733 )
734 735 #------------------------------------------------------------ 736 # properties 737 #------------------------------------------------------------
738 - def _set_lab_reference(self, value):
739 """Either a test result or an episode PK.""" 740 if isinstance(self.__reference, gmPathLab.cTestResult): 741 pk_old_episode = self.__reference['pk_episode'] 742 else: 743 pk_old_episode = self.__reference 744 if isinstance(value, gmPathLab.cTestResult): 745 pk_new_episode = value['pk_episode'] 746 else: 747 pk_new_episode = value 748 self.__reference = value 749 if pk_new_episode != pk_old_episode: 750 self.__repopulate_ui() 751 return
752 753 lab_reference = property(lambda x:x, _set_lab_reference)
754 755 #================================================================ 756 from Gnumed.wxGladeWidgets import wxgMeasurementsAsListPnl 757
758 -class cMeasurementsAsListPnl(wxgMeasurementsAsListPnl.wxgMeasurementsAsListPnl, gmRegetMixin.cRegetOnPaintMixin):
759 """A class for displaying all measurement results as a simple list. 760 761 - operates on a cPatient instance handed to it and NOT on the currently active patient 762 """
763 - def __init__(self, *args, **kwargs):
764 wxgMeasurementsAsListPnl.wxgMeasurementsAsListPnl.__init__(self, *args, **kwargs) 765 766 gmRegetMixin.cRegetOnPaintMixin.__init__(self) 767 768 self.__patient = None 769 770 self.__init_ui() 771 self.__register_events()
772 773 #------------------------------------------------------------ 774 # internal helpers 775 #------------------------------------------------------------
776 - def __init_ui(self):
777 self._LCTRL_results.set_columns([_('When'), _('Test'), _('Result'), _('Reference')]) 778 self._LCTRL_results.edit_callback = self._on_edit 779 self._PNL_related_documents.lab_reference = None
780 781 #------------------------------------------------------------
782 - def __register_events(self):
783 gmDispatcher.connect(signal = 'gm_table_mod', receiver = self._on_database_signal)
784 785 #------------------------------------------------------------
786 - def __repopulate_ui(self):
787 if self.__patient is None: 788 self._LCTRL_results.set_string_items([]) 789 self._TCTRL_measurements.SetValue('') 790 self._PNL_related_documents.lab_reference = None 791 return 792 793 results = self.__patient.emr.get_test_results(order_by = 'clin_when DESC, unified_abbrev, unified_name') 794 items = [] 795 data = [] 796 for r in results: 797 range_info = gmTools.coalesce ( 798 r.formatted_clinical_range, 799 r.formatted_normal_range 800 ) 801 review = gmTools.bool2subst ( 802 r['reviewed'], 803 '', 804 ' ' + gmTools.u_writing_hand, 805 ' ' + gmTools.u_writing_hand 806 ) 807 items.append ([ 808 gmDateTime.pydt_strftime(r['clin_when'], '%Y %b %d %H:%M', accuracy = gmDateTime.acc_minutes), 809 r['abbrev_tt'], 810 '%s%s%s%s' % ( 811 gmTools.strip_empty_lines(text = r['unified_val'])[0], 812 gmTools.coalesce(r['val_unit'], '', ' %s'), 813 gmTools.coalesce(r['abnormality_indicator'], '', ' %s'), 814 review 815 ), 816 gmTools.coalesce(range_info, '') 817 ]) 818 data.append({'data': r, 'formatted': r.format(with_source_data = True)}) 819 820 self._LCTRL_results.set_string_items(items) 821 self._LCTRL_results.set_column_widths([wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE]) 822 self._LCTRL_results.set_data(data) 823 if len(items) > 0: 824 self._LCTRL_results.Select(idx = 0, on = 1) 825 self._TCTRL_measurements.SetValue(self._LCTRL_results.get_item_data(item_idx = 0)['formatted']) 826 827 self._LCTRL_results.SetFocus()
828 829 #------------------------------------------------------------
830 - def _on_edit(self):
831 item_data = self._LCTRL_results.get_selected_item_data(only_one = True) 832 if item_data is None: 833 return 834 if edit_measurement(parent = self, measurement = item_data['data'], single_entry = True): 835 self.__repopulate_ui()
836 837 #------------------------------------------------------------ 838 # event handlers 839 #------------------------------------------------------------
840 - def _on_database_signal(self, **kwds):
841 if self.__patient is None: 842 return True 843 844 if kwds['pk_identity'] is not None: # review table doesn't have pk_identity yet 845 if kwds['pk_identity'] != self.__patient.ID: 846 return True 847 848 if kwds['table'] not in ['clin.test_result', 'clin.reviewed_test_results']: 849 return True 850 851 self._schedule_data_reget() 852 return True
853 854 #------------------------------------------------------------
855 - def _on_result_selected(self, event):
856 event.Skip() 857 item_data = self._LCTRL_results.get_item_data(item_idx = event.Index) 858 self._TCTRL_measurements.SetValue(item_data['formatted']) 859 self._PNL_related_documents.lab_reference = item_data['data']
860 861 #------------------------------------------------------------ 862 # reget mixin API 863 #------------------------------------------------------------
864 - def _populate_with_data(self):
865 self.__repopulate_ui() 866 return True
867 868 #------------------------------------------------------------ 869 # properties 870 #------------------------------------------------------------
871 - def _get_patient(self):
872 return self.__patient
873
874 - def _set_patient(self, patient):
875 if (self.__patient is None) and (patient is None): 876 return 877 if (self.__patient is None) or (patient is None): 878 self.__patient = patient 879 self._schedule_data_reget() 880 return 881 if self.__patient.ID == patient.ID: 882 return 883 self.__patient = patient 884 self._schedule_data_reget()
885 886 patient = property(_get_patient, _set_patient)
887 888 #================================================================ 889 from Gnumed.wxGladeWidgets import wxgMeasurementsByDayPnl 890
891 -class cMeasurementsByDayPnl(wxgMeasurementsByDayPnl.wxgMeasurementsByDayPnl, gmRegetMixin.cRegetOnPaintMixin):
892 """A class for displaying measurement results as a list partitioned by day. 893 894 - operates on a cPatient instance handed to it and NOT on the currently active patient 895 """
896 - def __init__(self, *args, **kwargs):
897 wxgMeasurementsByDayPnl.wxgMeasurementsByDayPnl.__init__(self, *args, **kwargs) 898 899 gmRegetMixin.cRegetOnPaintMixin.__init__(self) 900 901 self.__patient = None 902 self.__date_format = str('%Y %b %d') 903 904 self.__init_ui() 905 self.__register_events()
906 907 #------------------------------------------------------------ 908 # internal helpers 909 #------------------------------------------------------------
910 - def __init_ui(self):
911 self._LCTRL_days.set_columns([_('Day')]) 912 self._LCTRL_results.set_columns([_('Time'), _('Test'), _('Result'), _('Reference')]) 913 self._LCTRL_results.edit_callback = self._on_edit 914 self._PNL_related_documents.lab_reference = None
915 916 #------------------------------------------------------------
917 - def __register_events(self):
918 gmDispatcher.connect(signal = 'gm_table_mod', receiver = self._on_database_signal)
919 920 #------------------------------------------------------------
921 - def __clear(self):
922 self._LCTRL_days.set_string_items() 923 self._LCTRL_results.set_string_items() 924 self._TCTRL_measurements.SetValue('') 925 self._PNL_related_documents.lab_reference = None
926 927 #------------------------------------------------------------
928 - def __repopulate_ui(self):
929 if self.__patient is None: 930 self.__clear() 931 return 932 933 dates = self.__patient.emr.get_dates_for_results(reverse_chronological = True) 934 items = [ ['%s%s' % ( 935 gmDateTime.pydt_strftime(d['clin_when_day'], self.__date_format), 936 gmTools.bool2subst(d['is_reviewed'], '', gmTools.u_writing_hand, gmTools.u_writing_hand) 937 )] 938 for d in dates 939 ] 940 941 self._LCTRL_days.set_string_items(items) 942 self._LCTRL_days.set_data(dates) 943 if len(items) > 0: 944 self._LCTRL_days.Select(idx = 0, on = 1) 945 self._LCTRL_days.SetFocus()
946 947 #------------------------------------------------------------
948 - def _on_edit(self):
949 item_data = self._LCTRL_results.get_selected_item_data(only_one = True) 950 if item_data is None: 951 return 952 if edit_measurement(parent = self, measurement = item_data['data'], single_entry = True): 953 self.__repopulate_ui()
954 955 #------------------------------------------------------------ 956 # event handlers 957 #------------------------------------------------------------
958 - def _on_database_signal(self, **kwds):
959 if self.__patient is None: 960 return True 961 962 if kwds['pk_identity'] is not None: # review table doesn't have pk_identity yet 963 if kwds['pk_identity'] != self.__patient.ID: 964 return True 965 966 if kwds['table'] not in ['clin.test_result', 'clin.reviewed_test_results']: 967 return True 968 969 self._schedule_data_reget() 970 return True
971 972 #------------------------------------------------------------
973 - def _on_day_selected(self, event):
974 event.Skip() 975 976 day = self._LCTRL_days.get_item_data(item_idx = event.Index)['clin_when_day'] 977 results = self.__patient.emr.get_results_for_day(timestamp = day) 978 items = [] 979 data = [] 980 for r in results: 981 range_info = gmTools.coalesce ( 982 r.formatted_clinical_range, 983 r.formatted_normal_range 984 ) 985 review = gmTools.bool2subst ( 986 r['reviewed'], 987 '', 988 ' ' + gmTools.u_writing_hand, 989 ' ' + gmTools.u_writing_hand 990 ) 991 items.append ([ 992 gmDateTime.pydt_strftime(r['clin_when'], '%H:%M'), 993 r['abbrev_tt'], 994 '%s%s%s%s' % ( 995 gmTools.strip_empty_lines(text = r['unified_val'])[0], 996 gmTools.coalesce(r['val_unit'], '', ' %s'), 997 gmTools.coalesce(r['abnormality_indicator'], '', ' %s'), 998 review 999 ), 1000 gmTools.coalesce(range_info, '') 1001 ]) 1002 data.append({'data': r, 'formatted': r.format(with_source_data = True)}) 1003 1004 self._LCTRL_results.set_string_items(items) 1005 self._LCTRL_results.set_column_widths([wx.LIST_AUTOSIZE_USEHEADER, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE]) 1006 self._LCTRL_results.set_data(data) 1007 self._LCTRL_results.Select(idx = 0, on = 1)
1008 1009 #------------------------------------------------------------
1010 - def _on_result_selected(self, event):
1011 event.Skip() 1012 item_data = self._LCTRL_results.get_item_data(item_idx = event.Index) 1013 self._TCTRL_measurements.SetValue(item_data['formatted']) 1014 self._PNL_related_documents.lab_reference = item_data['data']
1015 1016 #------------------------------------------------------------ 1017 # reget mixin API 1018 #------------------------------------------------------------
1019 - def _populate_with_data(self):
1020 self.__repopulate_ui() 1021 return True
1022 1023 #------------------------------------------------------------ 1024 # properties 1025 #------------------------------------------------------------
1026 - def _get_patient(self):
1027 return self.__patient
1028
1029 - def _set_patient(self, patient):
1030 if (self.__patient is None) and (patient is None): 1031 return 1032 if patient is None: 1033 self.__patient = None 1034 self.__clear() 1035 return 1036 if self.__patient is None: 1037 self.__patient = patient 1038 self._schedule_data_reget() 1039 return 1040 if self.__patient.ID == patient.ID: 1041 return 1042 self.__patient = patient 1043 self._schedule_data_reget()
1044 1045 patient = property(_get_patient, _set_patient)
1046 1047 #================================================================ 1048 from Gnumed.wxGladeWidgets import wxgMeasurementsByIssuePnl 1049
1050 -class cMeasurementsByIssuePnl(wxgMeasurementsByIssuePnl.wxgMeasurementsByIssuePnl, gmRegetMixin.cRegetOnPaintMixin):
1051 """A class for displaying measurement results as a list partitioned by issue/episode. 1052 1053 - operates on a cPatient instance handed to it and NOT on the currently active patient 1054 """
1055 - def __init__(self, *args, **kwargs):
1056 wxgMeasurementsByIssuePnl.wxgMeasurementsByIssuePnl.__init__(self, *args, **kwargs) 1057 1058 gmRegetMixin.cRegetOnPaintMixin.__init__(self) 1059 1060 self.__patient = None 1061 1062 self.__init_ui() 1063 self.__register_events()
1064 1065 #------------------------------------------------------------ 1066 # internal helpers 1067 #------------------------------------------------------------
1068 - def __init_ui(self):
1069 self._LCTRL_issues.set_columns([_('Problem')]) 1070 self._LCTRL_results.set_columns([_('When'), _('Test'), _('Result'), _('Reference')]) 1071 self._PNL_related_documents.lab_reference = None
1072 1073 #------------------------------------------------------------
1074 - def __register_events(self):
1075 gmDispatcher.connect(signal = 'gm_table_mod', receiver = self._on_database_signal) 1076 self._LCTRL_issues.select_callback = self._on_problem_selected 1077 self._LCTRL_results.edit_callback = self._on_edit 1078 self._LCTRL_results.select_callback = self._on_result_selected
1079 1080 #------------------------------------------------------------
1081 - def __clear(self):
1082 self._LCTRL_issues.set_string_items() 1083 self._LCTRL_results.set_string_items() 1084 self._TCTRL_measurements.SetValue('') 1085 self._PNL_related_documents.lab_reference = None
1086 1087 #------------------------------------------------------------
1088 - def __repopulate_ui(self):
1089 if self.__patient is None: 1090 self.__clear() 1091 return 1092 1093 probs = self.__patient.emr.get_issues_or_episodes_for_results() 1094 items = [ ['%s%s' % ( 1095 gmTools.coalesce(p['pk_health_issue'], gmTools.u_diameter + ':', ''), 1096 gmTools.shorten_words_in_line(text = p['problem'], min_word_length = 5, max_length = 30) 1097 )] for p in probs ] 1098 self._LCTRL_issues.set_string_items(items) 1099 self._LCTRL_issues.set_data([ {'pk_issue': p['pk_health_issue'], 'pk_episode': p['pk_episode']} for p in probs ]) 1100 if len(items) > 0: 1101 self._LCTRL_issues.Select(idx = 0, on = 1) 1102 self._LCTRL_issues.SetFocus()
1103 1104 #------------------------------------------------------------
1105 - def _on_edit(self):
1106 item_data = self._LCTRL_results.get_selected_item_data(only_one = True) 1107 if item_data is None: 1108 return 1109 if edit_measurement(parent = self, measurement = item_data['data'], single_entry = True): 1110 self.__repopulate_ui()
1111 1112 #------------------------------------------------------------ 1113 # event handlers 1114 #------------------------------------------------------------
1115 - def _on_database_signal(self, **kwds):
1116 if self.__patient is None: 1117 return True 1118 1119 if kwds['pk_identity'] is not None: # review table doesn't have pk_identity yet 1120 if kwds['pk_identity'] != self.__patient.ID: 1121 return True 1122 1123 if kwds['table'] not in ['clin.test_result', 'clin.reviewed_test_results']: 1124 return True 1125 1126 self._schedule_data_reget() 1127 return True
1128 1129 #------------------------------------------------------------
1130 - def _on_problem_selected(self, event):
1131 event.Skip() 1132 1133 pk_issue = self._LCTRL_issues.get_item_data(item_idx = event.Index)['pk_issue'] 1134 if pk_issue is None: 1135 pk_episode = self._LCTRL_issues.get_item_data(item_idx = event.Index)['pk_episode'] 1136 results = self.__patient.emr.get_results_for_episode(pk_episode = pk_episode) 1137 else: 1138 results = self.__patient.emr.get_results_for_issue(pk_health_issue = pk_issue) 1139 items = [] 1140 data = [] 1141 for r in results: 1142 range_info = gmTools.coalesce ( 1143 r.formatted_clinical_range, 1144 r.formatted_normal_range 1145 ) 1146 review = gmTools.bool2subst ( 1147 r['reviewed'], 1148 '', 1149 ' ' + gmTools.u_writing_hand, 1150 ' ' + gmTools.u_writing_hand 1151 ) 1152 items.append ([ 1153 gmDateTime.pydt_strftime(r['clin_when'], '%Y %b %d %H:%M'), 1154 r['abbrev_tt'], 1155 '%s%s%s%s' % ( 1156 gmTools.strip_empty_lines(text = r['unified_val'])[0], 1157 gmTools.coalesce(r['val_unit'], '', ' %s'), 1158 gmTools.coalesce(r['abnormality_indicator'], '', ' %s'), 1159 review 1160 ), 1161 gmTools.coalesce(range_info, '') 1162 ]) 1163 data.append({'data': r, 'formatted': r.format(with_source_data = True)}) 1164 1165 self._LCTRL_results.set_string_items(items) 1166 self._LCTRL_results.set_column_widths([wx.LIST_AUTOSIZE_USEHEADER, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE]) 1167 self._LCTRL_results.set_data(data) 1168 self._LCTRL_results.Select(idx = 0, on = 1) 1169 self._TCTRL_measurements.SetValue(self._LCTRL_results.get_item_data(item_idx = 0)['formatted'])
1170 1171 #------------------------------------------------------------
1172 - def _on_result_selected(self, event):
1173 event.Skip() 1174 item_data = self._LCTRL_results.get_item_data(item_idx = event.Index) 1175 self._TCTRL_measurements.SetValue(item_data['formatted']) 1176 self._PNL_related_documents.lab_reference = item_data['data']
1177 1178 #------------------------------------------------------------ 1179 # reget mixin API 1180 #------------------------------------------------------------
1181 - def _populate_with_data(self):
1182 self.__repopulate_ui() 1183 return True
1184 1185 #------------------------------------------------------------ 1186 # properties 1187 #------------------------------------------------------------
1188 - def _get_patient(self):
1189 return self.__patient
1190
1191 - def _set_patient(self, patient):
1192 if (self.__patient is None) and (patient is None): 1193 return 1194 if patient is None: 1195 self.__patient = None 1196 self.__clear() 1197 return 1198 if self.__patient is None: 1199 self.__patient = patient 1200 self._schedule_data_reget() 1201 return 1202 if self.__patient.ID == patient.ID: 1203 return 1204 self.__patient = patient 1205 self._schedule_data_reget()
1206 1207 patient = property(_get_patient, _set_patient)
1208 1209 #================================================================ 1210 from Gnumed.wxGladeWidgets import wxgMeasurementsByBatteryPnl 1211
1212 -class cMeasurementsByBatteryPnl(wxgMeasurementsByBatteryPnl.wxgMeasurementsByBatteryPnl, gmRegetMixin.cRegetOnPaintMixin):
1213 """A grid class for displaying measurement results filtered by battery/panel. 1214 1215 - operates on a cPatient instance handed to it and NOT on the currently active patient 1216 """
1217 - def __init__(self, *args, **kwargs):
1218 wxgMeasurementsByBatteryPnl.wxgMeasurementsByBatteryPnl.__init__(self, *args, **kwargs) 1219 1220 gmRegetMixin.cRegetOnPaintMixin.__init__(self) 1221 1222 self.__patient = None 1223 1224 self.__init_ui() 1225 self.__register_events()
1226 1227 #------------------------------------------------------------ 1228 # internal helpers 1229 #------------------------------------------------------------
1230 - def __init_ui(self):
1231 self._GRID_results_battery.show_by_panel = True
1232 1233 #------------------------------------------------------------
1234 - def __register_events(self):
1235 gmDispatcher.connect(signal = 'gm_table_mod', receiver = self._on_database_signal) 1236 1237 self._PRW_panel.add_callback_on_selection(callback = self._on_panel_selected) 1238 self._PRW_panel.add_callback_on_modified(callback = self._on_panel_selection_modified)
1239 1240 #------------------------------------------------------------
1241 - def __repopulate_ui(self):
1242 self._GRID_results_battery.patient = self.__patient 1243 return True
1244 1245 #--------------------------------------------------------
1246 - def __on_panel_selected(self, panel):
1247 if panel is None: 1248 self._TCTRL_panel_comment.SetValue('') 1249 self._GRID_results_battery.panel_to_show = None 1250 else: 1251 pnl = self._PRW_panel.GetData(as_instance = True) 1252 self._TCTRL_panel_comment.SetValue(gmTools.coalesce ( 1253 pnl['comment'], 1254 '' 1255 )) 1256 self._GRID_results_battery.panel_to_show = pnl
1257 # self.Layout() 1258 1259 #--------------------------------------------------------
1261 self._TCTRL_panel_comment.SetValue('') 1262 if self._PRW_panel.GetValue().strip() == '': 1263 self._GRID_results_battery.panel_to_show = None
1264 # self.Layout() 1265 1266 #------------------------------------------------------------ 1267 # event handlers 1268 #------------------------------------------------------------
1269 - def _on_database_signal(self, **kwds):
1270 if self.__patient is None: 1271 return True 1272 1273 if kwds['pk_identity'] is not None: # review table doesn't have pk_identity yet 1274 if kwds['pk_identity'] != self.__patient.ID: 1275 return True 1276 1277 if kwds['table'] not in ['clin.test_result', 'clin.reviewed_test_results']: 1278 return True 1279 1280 self._schedule_data_reget() 1281 return True
1282 1283 #------------------------------------------------------------
1284 - def _on_manage_panels_button_pressed(self, event):
1286 1287 #--------------------------------------------------------
1288 - def _on_panel_selected(self, panel):
1289 wx.CallAfter(self.__on_panel_selected, panel=panel)
1290 1291 #--------------------------------------------------------
1293 wx.CallAfter(self.__on_panel_selection_modified)
1294 1295 #------------------------------------------------------------ 1296 # reget mixin API 1297 #------------------------------------------------------------
1298 - def _populate_with_data(self):
1299 self.__repopulate_ui() 1300 return True
1301 1302 #------------------------------------------------------------ 1303 # properties 1304 #------------------------------------------------------------
1305 - def _get_patient(self):
1306 return self.__patient
1307
1308 - def _set_patient(self, patient):
1309 if (self.__patient is None) and (patient is None): 1310 return 1311 if (self.__patient is None) or (patient is None): 1312 self.__patient = patient 1313 self._schedule_data_reget() 1314 return 1315 if self.__patient.ID == patient.ID: 1316 return 1317 self.__patient = patient 1318 self._schedule_data_reget()
1319 1320 patient = property(_get_patient, _set_patient)
1321 1322 #================================================================ 1323 from Gnumed.wxGladeWidgets import wxgMeasurementsAsMostRecentListPnl 1324
1325 -class cMeasurementsAsMostRecentListPnl(wxgMeasurementsAsMostRecentListPnl.wxgMeasurementsAsMostRecentListPnl, gmRegetMixin.cRegetOnPaintMixin):
1326 """A list ctrl class for displaying measurement results. 1327 1328 - most recent results 1329 - possibly filtered by battery/panel 1330 1331 - operates on a cPatient instance handed to it and NOT on the currently active patient 1332 """
1333 - def __init__(self, *args, **kwargs):
1334 wxgMeasurementsAsMostRecentListPnl.wxgMeasurementsAsMostRecentListPnl.__init__(self, *args, **kwargs) 1335 1336 gmRegetMixin.cRegetOnPaintMixin.__init__(self) 1337 1338 self.__patient = None 1339 1340 self.__init_ui() 1341 self.__register_events()
1342 1343 #------------------------------------------------------------ 1344 # internal helpers 1345 #------------------------------------------------------------
1346 - def __init_ui(self):
1347 self._LCTRL_results.set_columns([_('Test'), _('Result'), _('When'), _('Range')]) 1348 self._CHBOX_show_missing.Disable() 1349 self._PNL_related_documents.lab_reference = None
1350 1351 #------------------------------------------------------------
1352 - def __register_events(self):
1353 gmDispatcher.connect(signal = 'gm_table_mod', receiver = self._on_database_signal) 1354 1355 self._PRW_panel.add_callback_on_selection(callback = self._on_panel_selected) 1356 self._PRW_panel.add_callback_on_modified(callback = self._on_panel_selection_modified) 1357 1358 self._LCTRL_results.select_callback = self._on_result_selected 1359 self._LCTRL_results.edit_callback = self._on_edit
1360 1361 #------------------------------------------------------------
1362 - def __repopulate_ui(self):
1363 1364 self._TCTRL_details.SetValue('') 1365 self._PNL_related_documents.lab_reference = None 1366 1367 pnl = self._PRW_panel.GetData(as_instance = True) 1368 if pnl is None: 1369 results = gmPathLab.get_most_recent_result_for_test_types ( 1370 pk_patient = self.__patient.ID, 1371 consider_meta_type = True 1372 ) 1373 else: 1374 results = pnl.get_most_recent_results ( 1375 pk_patient = self.__patient.ID, 1376 #order_by = , 1377 group_by_meta_type = True, 1378 include_missing = self._CHBOX_show_missing.IsChecked() 1379 ) 1380 items = [] 1381 data = [] 1382 for r in results: 1383 if isinstance(r, gmPathLab.cTestResult): 1384 result_type = gmTools.coalesce ( 1385 value2test = r['pk_meta_test_type'], 1386 return_instead = r['abbrev_tt'], 1387 value2return = '%s%s' % (gmTools.u_sum, r['abbrev_meta']) 1388 ) 1389 review = gmTools.bool2subst ( 1390 r['reviewed'], 1391 '', 1392 ' ' + gmTools.u_writing_hand, 1393 ' ' + gmTools.u_writing_hand 1394 ) 1395 result_val = '%s%s%s%s' % ( 1396 gmTools.strip_empty_lines(text = r['unified_val'])[0], 1397 gmTools.coalesce(r['val_unit'], '', ' %s'), 1398 gmTools.coalesce(r['abnormality_indicator'], '', ' %s'), 1399 review 1400 ) 1401 result_when = _('%s ago (%s)') % ( 1402 gmDateTime.format_interval_medically(interval = gmDateTime.pydt_now_here() - r['clin_when']), 1403 gmDateTime.pydt_strftime(r['clin_when'], '%Y %b %d %H:%M', accuracy = gmDateTime.acc_minutes) 1404 ) 1405 range_info = gmTools.coalesce ( 1406 r.formatted_clinical_range, 1407 r.formatted_normal_range 1408 ) 1409 tt = r.format(with_source_data = True) 1410 else: 1411 result_type = r 1412 result_val = _('missing') 1413 loinc_data = gmLOINC.loinc2data(r) 1414 if loinc_data is None: 1415 result_when = _('LOINC not found') 1416 tt = u'' 1417 else: 1418 result_when = loinc_data['term'] 1419 tt = gmLOINC.format_loinc(r) 1420 range_info = None 1421 items.append([result_type, result_val, result_when, gmTools.coalesce(range_info, '')]) 1422 data.append({'data': r, 'formatted': tt}) 1423 1424 self._LCTRL_results.set_string_items(items) 1425 self._LCTRL_results.set_column_widths([wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE]) 1426 self._LCTRL_results.set_data(data) 1427 1428 if len(items) > 0: 1429 self._LCTRL_results.Select(idx = 0, on = 1) 1430 self._LCTRL_results.SetFocus() 1431 1432 return True
1433 1434 #--------------------------------------------------------
1435 - def __on_panel_selected(self, panel):
1436 if panel is None: 1437 self._TCTRL_panel_comment.SetValue('') 1438 self._CHBOX_show_missing.Disable() 1439 else: 1440 pnl = self._PRW_panel.GetData(as_instance = True) 1441 self._TCTRL_panel_comment.SetValue(gmTools.coalesce(pnl['comment'], '')) 1442 self.__repopulate_ui() 1443 self._CHBOX_show_missing.Enable()
1444 1445 #--------------------------------------------------------
1447 self._TCTRL_panel_comment.SetValue('') 1448 if self._PRW_panel.Value.strip() == u'': 1449 self.__repopulate_ui() 1450 self._CHBOX_show_missing.Disable()
1451 1452 #------------------------------------------------------------ 1453 # event handlers 1454 #------------------------------------------------------------
1455 - def _on_database_signal(self, **kwds):
1456 if self.__patient is None: 1457 return True 1458 1459 if kwds['pk_identity'] is not None: # review table doesn't have pk_identity yet 1460 if kwds['pk_identity'] != self.__patient.ID: 1461 return True 1462 1463 if kwds['table'] not in ['clin.test_result', 'clin.reviewed_test_results', 'clin.test_panel']: 1464 return True 1465 1466 self._schedule_data_reget() 1467 return True
1468 1469 #------------------------------------------------------------
1470 - def _on_manage_panels_button_pressed(self, event):
1472 1473 #--------------------------------------------------------
1474 - def _on_panel_selected(self, panel):
1475 wx.CallAfter(self.__on_panel_selected, panel = panel)
1476 1477 #--------------------------------------------------------
1479 wx.CallAfter(self.__on_panel_selection_modified)
1480 1481 #------------------------------------------------------------
1482 - def _on_result_selected(self, event):
1483 event.Skip() 1484 item_data = self._LCTRL_results.get_item_data(item_idx = event.Index) 1485 self._TCTRL_details.SetValue(item_data['formatted']) 1486 if isinstance(item_data['data'], gmPathLab.cTestResult): 1487 self._PNL_related_documents.lab_reference = item_data['data'] 1488 else: 1489 self._PNL_related_documents.lab_reference = None
1490 1491 #------------------------------------------------------------
1492 - def _on_edit(self):
1493 item_data = self._LCTRL_results.get_selected_item_data(only_one = True) 1494 if item_data is None: 1495 return 1496 if isinstance(item_data['data'], gmPathLab.cTestResult): 1497 if edit_measurement(parent = self, measurement = item_data['data'], single_entry = True): 1498 self.__repopulate_ui()
1499 1500 #------------------------------------------------------------
1501 - def _on_show_missing_toggled(self, event):
1502 event.Skip() 1503 # should not happen 1504 if self._PRW_panel.GetData(as_instance = False) is None: 1505 return 1506 self.__repopulate_ui()
1507 1508 #------------------------------------------------------------ 1509 # reget mixin API 1510 #------------------------------------------------------------
1511 - def _populate_with_data(self):
1512 self.__repopulate_ui() 1513 return True
1514 1515 #------------------------------------------------------------ 1516 # properties 1517 #------------------------------------------------------------
1518 - def _get_patient(self):
1519 return self.__patient
1520
1521 - def _set_patient(self, patient):
1522 if (self.__patient is None) and (patient is None): 1523 return 1524 if (self.__patient is None) or (patient is None): 1525 self.__patient = patient 1526 self._schedule_data_reget() 1527 return 1528 if self.__patient.ID == patient.ID: 1529 return 1530 self.__patient = patient 1531 self._schedule_data_reget()
1532 1533 patient = property(_get_patient, _set_patient)
1534 1535 #================================================================ 1536 from Gnumed.wxGladeWidgets import wxgMeasurementsAsTablePnl 1537
1538 -class cMeasurementsAsTablePnl(wxgMeasurementsAsTablePnl.wxgMeasurementsAsTablePnl, gmRegetMixin.cRegetOnPaintMixin):
1539 """A panel for holding a grid displaying all measurement results. 1540 1541 - operates on a cPatient instance handed to it and NOT on the currently active patient 1542 """
1543 - def __init__(self, *args, **kwargs):
1544 wxgMeasurementsAsTablePnl.wxgMeasurementsAsTablePnl.__init__(self, *args, **kwargs) 1545 1546 gmRegetMixin.cRegetOnPaintMixin.__init__(self) 1547 1548 self.__patient = None 1549 1550 self.__init_ui() 1551 self.__register_events()
1552 1553 #------------------------------------------------------------ 1554 # internal helpers 1555 #------------------------------------------------------------
1556 - def __init_ui(self):
1557 self.__action_button_popup = wx.Menu(title = _('Perform on selected results:')) 1558 1559 item = self.__action_button_popup.Append(-1, _('Review and &sign')) 1560 self.Bind(wx.EVT_MENU, self.__on_sign_current_selection, item) 1561 1562 item = self.__action_button_popup.Append(-1, _('Plot')) 1563 self.Bind(wx.EVT_MENU, self.__on_plot_current_selection, item) 1564 1565 #item = self.__action_button_popup.Append(-1, _('Export to &file')) 1566 #self.Bind(wx.EVT_MENU, self._GRID_results_all.current_selection_to_file, item) 1567 #self.__action_button_popup.Enable(id = item.Id, enable = False) 1568 1569 #item = self.__action_button_popup.Append(-1, _('Export to &clipboard')) 1570 #self.Bind(wx.EVT_MENU, self._GRID_results_all.current_selection_to_clipboard, item) 1571 #self.__action_button_popup.Enable(id = item.Id, enable = False) 1572 1573 item = self.__action_button_popup.Append(-1, _('&Delete')) 1574 self.Bind(wx.EVT_MENU, self.__on_delete_current_selection, item) 1575 1576 # FIXME: create inbox message to staff to phone patient to come in 1577 # FIXME: generate and let edit a SOAP narrative and include the values 1578 1579 self._GRID_results_all.show_by_panel = False
1580 1581 #------------------------------------------------------------
1582 - def __register_events(self):
1583 gmDispatcher.connect(signal = 'gm_table_mod', receiver = self._on_database_signal)
1584 1585 #------------------------------------------------------------
1586 - def __repopulate_ui(self):
1587 self._GRID_results_all.patient = self.__patient 1588 #self._GRID_results_battery.Fit() 1589 self.Layout() 1590 return True
1591 1592 #------------------------------------------------------------
1593 - def __on_sign_current_selection(self, evt):
1594 self._GRID_results_all.sign_current_selection()
1595 1596 #------------------------------------------------------------
1597 - def __on_plot_current_selection(self, evt):
1598 self._GRID_results_all.plot_current_selection()
1599 1600 #------------------------------------------------------------
1601 - def __on_delete_current_selection(self, evt):
1602 self._GRID_results_all.delete_current_selection()
1603 1604 #------------------------------------------------------------ 1605 # event handlers 1606 #------------------------------------------------------------
1607 - def _on_database_signal(self, **kwds):
1608 if self.__patient is None: 1609 return True 1610 1611 if kwds['pk_identity'] is not None: # review table doesn't have pk_identity yet 1612 if kwds['pk_identity'] != self.__patient.ID: 1613 return True 1614 1615 if kwds['table'] not in ['clin.test_result', 'clin.reviewed_test_results']: 1616 return True 1617 1618 self._schedule_data_reget() 1619 return True
1620 1621 #--------------------------------------------------------
1622 - def _on_add_button_pressed(self, event):
1623 edit_measurement(parent = self, measurement = None)
1624 1625 #--------------------------------------------------------
1626 - def _on_manage_types_button_pressed(self, event):
1627 event.Skip() 1628 manage_measurement_types(parent = self)
1629 1630 #--------------------------------------------------------
1631 - def _on_review_button_pressed(self, evt):
1632 self.PopupMenu(self.__action_button_popup)
1633 1634 #--------------------------------------------------------
1635 - def _on_select_button_pressed(self, evt):
1636 if self._RBTN_my_unsigned.GetValue() is True: 1637 self._GRID_results_all.select_cells(unsigned_only = True, accountables_only = True, keep_preselections = False) 1638 elif self._RBTN_all_unsigned.GetValue() is True: 1639 self._GRID_results_all.select_cells(unsigned_only = True, accountables_only = False, keep_preselections = False)
1640 1641 #------------------------------------------------------------ 1642 # reget mixin API 1643 #------------------------------------------------------------
1644 - def _populate_with_data(self):
1645 self.__repopulate_ui() 1646 return True
1647 1648 #------------------------------------------------------------ 1649 # properties 1650 #------------------------------------------------------------
1651 - def _get_patient(self):
1652 return self.__patient
1653
1654 - def _set_patient(self, patient):
1655 if (self.__patient is None) and (patient is None): 1656 return 1657 if (self.__patient is None) or (patient is None): 1658 self.__patient = patient 1659 self._schedule_data_reget() 1660 return 1661 if self.__patient.ID == patient.ID: 1662 return 1663 self.__patient = patient 1664 self._schedule_data_reget()
1665 1666 patient = property(_get_patient, _set_patient)
1667 1668 #================================================================ 1669 # notebook based measurements plugin 1670 #================================================================
1671 -class cMeasurementsNb(wx.Notebook, gmPlugin.cPatientChange_PluginMixin):
1672 """Notebook displaying measurements pages: 1673 1674 - by test battery 1675 - by day 1676 - by issue/episode 1677 - most-recent list, perhaps by panel 1678 - full grid 1679 - full list 1680 1681 Used as a main notebook plugin page. 1682 1683 Operates on the active patient. 1684 """ 1685 #--------------------------------------------------------
1686 - def __init__(self, parent, id):
1687 1688 wx.Notebook.__init__ ( 1689 self, 1690 parent = parent, 1691 id = id, 1692 style = wx.NB_TOP | wx.NB_MULTILINE | wx.NO_BORDER, 1693 name = self.__class__.__name__ 1694 ) 1695 _log.debug('created wx.Notebook: %s with ID %s', self.__class__.__name__, self.Id) 1696 gmPlugin.cPatientChange_PluginMixin.__init__(self) 1697 self.__patient = gmPerson.gmCurrentPatient() 1698 self.__init_ui() 1699 self.SetSelection(0)
1700 1701 #-------------------------------------------------------- 1702 # patient change plugin API 1703 #--------------------------------------------------------
1704 - def _on_current_patient_unset(self, **kwds):
1705 for page_idx in range(self.GetPageCount()): 1706 page = self.GetPage(page_idx) 1707 page.patient = None
1708 1709 #--------------------------------------------------------
1710 - def _post_patient_selection(self, **kwds):
1711 for page_idx in range(self.GetPageCount()): 1712 page = self.GetPage(page_idx) 1713 page.patient = self.__patient.patient
1714 1715 #-------------------------------------------------------- 1716 # notebook plugin API 1717 #--------------------------------------------------------
1718 - def repopulate_ui(self):
1719 if self.__patient.connected: 1720 pat = self.__patient.patient 1721 else: 1722 pat = None 1723 for page_idx in range(self.GetPageCount()): 1724 page = self.GetPage(page_idx) 1725 page.patient = pat 1726 1727 return True
1728 1729 #-------------------------------------------------------- 1730 # internal API 1731 #--------------------------------------------------------
1732 - def __init_ui(self):
1733 1734 # by day 1735 new_page = cMeasurementsByDayPnl(self, -1) 1736 new_page.patient = None 1737 self.AddPage ( 1738 page = new_page, 1739 text = _('Days'), 1740 select = True 1741 ) 1742 1743 # by issue 1744 new_page = cMeasurementsByIssuePnl(self, -1) 1745 new_page.patient = None 1746 self.AddPage ( 1747 page = new_page, 1748 text = _('Problems'), 1749 select = False 1750 ) 1751 1752 # by test panel 1753 new_page = cMeasurementsByBatteryPnl(self, -1) 1754 new_page.patient = None 1755 self.AddPage ( 1756 page = new_page, 1757 text = _('Panels'), 1758 select = False 1759 ) 1760 1761 # most-recent, by panel 1762 new_page = cMeasurementsAsMostRecentListPnl(self, -1) 1763 new_page.patient = None 1764 self.AddPage ( 1765 page = new_page, 1766 text = _('Most recent'), 1767 select = False 1768 ) 1769 1770 # full grid 1771 new_page = cMeasurementsAsTablePnl(self, -1) 1772 new_page.patient = None 1773 self.AddPage ( 1774 page = new_page, 1775 text = _('Table'), 1776 select = False 1777 ) 1778 1779 # full list 1780 new_page = cMeasurementsAsListPnl(self, -1) 1781 new_page.patient = None 1782 self.AddPage ( 1783 page = new_page, 1784 text = _('List'), 1785 select = False 1786 )
1787 1788 #-------------------------------------------------------- 1789 # properties 1790 #--------------------------------------------------------
1791 - def _get_patient(self):
1792 return self.__patient
1793
1794 - def _set_patient(self, patient):
1795 self.__patient = patient 1796 if self.__patient.connected: 1797 pat = self.__patient.patient 1798 else: 1799 pat = None 1800 for page_idx in range(self.GetPageCount()): 1801 page = self.GetPage(page_idx) 1802 page.patient = pat
1803 1804 patient = property(_get_patient, _set_patient)
1805 1806 #================================================================
1807 -class cMeasurementsGrid(wx.grid.Grid):
1808 """A grid class for displaying measurement results. 1809 1810 - operates on a cPatient instance handed to it 1811 - does NOT listen to the currently active patient 1812 - thereby it can display any patient at any time 1813 """ 1814 # FIXME: sort-by-battery 1815 # FIXME: filter out empty 1816 # FIXME: filter by tests of a selected date 1817 # FIXME: dates DESC/ASC by cfg 1818 # FIXME: mouse over column header: display date info
1819 - def __init__(self, *args, **kwargs):
1820 1821 wx.grid.Grid.__init__(self, *args, **kwargs) 1822 1823 self.__patient = None 1824 self.__panel_to_show = None 1825 self.__show_by_panel = False 1826 self.__cell_data = {} 1827 self.__row_label_data = [] 1828 self.__col_label_data = [] 1829 1830 self.__prev_row = None 1831 self.__prev_col = None 1832 self.__prev_label_row = None 1833 self.__date_format = str((_('lab_grid_date_format::%Y\n%b %d')).lstrip('lab_grid_date_format::')) 1834 1835 self.__init_ui() 1836 self.__register_events()
1837 1838 #------------------------------------------------------------ 1839 # external API 1840 #------------------------------------------------------------
1841 - def delete_current_selection(self):
1842 if not self.IsSelection(): 1843 gmDispatcher.send(signal = 'statustext', msg = _('No results selected for deletion.')) 1844 return True 1845 1846 selected_cells = self.get_selected_cells() 1847 if len(selected_cells) > 20: 1848 results = None 1849 msg = _( 1850 'There are %s results marked for deletion.\n' 1851 '\n' 1852 'Are you sure you want to delete these results ?' 1853 ) % len(selected_cells) 1854 else: 1855 results = self.__cells_to_data(cells = selected_cells, exclude_multi_cells = False) 1856 txt = '\n'.join([ '%s %s (%s): %s %s%s' % ( 1857 r['clin_when'].strftime('%x %H:%M'), 1858 r['unified_abbrev'], 1859 r['unified_name'], 1860 r['unified_val'], 1861 r['val_unit'], 1862 gmTools.coalesce(r['abnormality_indicator'], '', ' (%s)') 1863 ) for r in results 1864 ]) 1865 msg = _( 1866 'The following results are marked for deletion:\n' 1867 '\n' 1868 '%s\n' 1869 '\n' 1870 'Are you sure you want to delete these results ?' 1871 ) % txt 1872 1873 dlg = gmGuiHelpers.c2ButtonQuestionDlg ( 1874 self, 1875 -1, 1876 caption = _('Deleting test results'), 1877 question = msg, 1878 button_defs = [ 1879 {'label': _('Delete'), 'tooltip': _('Yes, delete all the results.'), 'default': False}, 1880 {'label': _('Cancel'), 'tooltip': _('No, do NOT delete any results.'), 'default': True} 1881 ] 1882 ) 1883 decision = dlg.ShowModal() 1884 1885 if decision == wx.ID_YES: 1886 if results is None: 1887 results = self.__cells_to_data(cells = selected_cells, exclude_multi_cells = False) 1888 for result in results: 1889 gmPathLab.delete_test_result(result)
1890 1891 #------------------------------------------------------------
1892 - def sign_current_selection(self):
1893 if not self.IsSelection(): 1894 gmDispatcher.send(signal = 'statustext', msg = _('Cannot sign results. No results selected.')) 1895 return True 1896 1897 selected_cells = self.get_selected_cells() 1898 tests = self.__cells_to_data(cells = selected_cells, exclude_multi_cells = False) 1899 1900 return review_tests(parent = self, tests = tests)
1901 1902 #------------------------------------------------------------
1903 - def plot_current_selection(self):
1904 1905 if not self.IsSelection(): 1906 gmDispatcher.send(signal = 'statustext', msg = _('Cannot plot results. No results selected.')) 1907 return True 1908 1909 tests = self.__cells_to_data ( 1910 cells = self.get_selected_cells(), 1911 exclude_multi_cells = False, 1912 auto_include_multi_cells = True 1913 ) 1914 1915 plot_measurements(parent = self, tests = tests)
1916 #------------------------------------------------------------
1917 - def get_selected_cells(self):
1918 1919 sel_block_top_left = self.GetSelectionBlockTopLeft() 1920 sel_block_bottom_right = self.GetSelectionBlockBottomRight() 1921 sel_cols = self.GetSelectedCols() 1922 sel_rows = self.GetSelectedRows() 1923 1924 selected_cells = [] 1925 1926 # individually selected cells (ctrl-click) 1927 selected_cells += self.GetSelectedCells() 1928 1929 # selected rows 1930 selected_cells += list ( 1931 (row, col) 1932 for row in sel_rows 1933 for col in range(self.GetNumberCols()) 1934 ) 1935 1936 # selected columns 1937 selected_cells += list ( 1938 (row, col) 1939 for row in range(self.GetNumberRows()) 1940 for col in sel_cols 1941 ) 1942 1943 # selection blocks 1944 for top_left, bottom_right in zip(self.GetSelectionBlockTopLeft(), self.GetSelectionBlockBottomRight()): 1945 selected_cells += [ 1946 (row, col) 1947 for row in range(top_left[0], bottom_right[0] + 1) 1948 for col in range(top_left[1], bottom_right[1] + 1) 1949 ] 1950 1951 return set(selected_cells)
1952 #------------------------------------------------------------
1953 - def select_cells(self, unsigned_only=False, accountables_only=False, keep_preselections=False):
1954 """Select a range of cells according to criteria. 1955 1956 unsigned_only: include only those which are not signed at all yet 1957 accountable_only: include only those for which the current user is responsible 1958 keep_preselections: broaden (rather than replace) the range of selected cells 1959 1960 Combinations are powerful ! 1961 """ 1962 wx.BeginBusyCursor() 1963 self.BeginBatch() 1964 1965 if not keep_preselections: 1966 self.ClearSelection() 1967 1968 for col_idx in self.__cell_data.keys(): 1969 for row_idx in self.__cell_data[col_idx].keys(): 1970 # loop over results in cell and only include 1971 # those multi-value cells that are not ambiguous 1972 do_not_include = False 1973 for result in self.__cell_data[col_idx][row_idx]: 1974 if unsigned_only: 1975 if result['reviewed']: 1976 do_not_include = True 1977 break 1978 if accountables_only: 1979 if not result['you_are_responsible']: 1980 do_not_include = True 1981 break 1982 if do_not_include: 1983 continue 1984 1985 self.SelectBlock(row_idx, col_idx, row_idx, col_idx, addToSelected = True) 1986 1987 self.EndBatch() 1988 wx.EndBusyCursor()
1989 1990 #------------------------------------------------------------
1991 - def repopulate_grid(self):
1992 self.empty_grid() 1993 if self.__patient is None: 1994 return 1995 1996 if self.__show_by_panel: 1997 if self.__panel_to_show is None: 1998 return 1999 tests = self.__panel_to_show.get_test_types_for_results ( 2000 self.__patient.ID, 2001 order_by = 'unified_abbrev', 2002 unique_meta_types = True 2003 ) 2004 self.__repopulate_grid ( 2005 tests4rows = tests, 2006 test_pks2show = [ tt['pk_test_type'] for tt in self.__panel_to_show['test_types'] ] 2007 ) 2008 return 2009 2010 emr = self.__patient.emr 2011 tests = emr.get_test_types_for_results(order_by = 'unified_abbrev', unique_meta_types = True) 2012 self.__repopulate_grid(tests4rows = tests)
2013 2014 #------------------------------------------------------------
2015 - def __repopulate_grid(self, tests4rows=None, test_pks2show=None):
2016 2017 if len(tests4rows) == 0: 2018 return 2019 2020 emr = self.__patient.emr 2021 2022 self.__row_label_data = tests4rows 2023 row_labels = [ '%s%s' % ( 2024 gmTools.bool2subst(test_type['is_fake_meta_type'], '', gmTools.u_sum, ''), 2025 test_type['unified_abbrev'] 2026 ) for test_type in self.__row_label_data 2027 ] 2028 2029 self.__col_label_data = [ d['clin_when_day'] for d in emr.get_dates_for_results ( 2030 tests = test_pks2show, 2031 reverse_chronological = True 2032 )] 2033 col_labels = [ gmDateTime.pydt_strftime(date, self.__date_format, accuracy = gmDateTime.acc_days) for date in self.__col_label_data ] 2034 2035 results = emr.get_test_results_by_date ( 2036 tests = test_pks2show, 2037 reverse_chronological = True 2038 ) 2039 2040 self.BeginBatch() 2041 2042 # rows 2043 self.AppendRows(numRows = len(row_labels)) 2044 for row_idx in range(len(row_labels)): 2045 self.SetRowLabelValue(row_idx, row_labels[row_idx]) 2046 2047 # columns 2048 self.AppendCols(numCols = len(col_labels)) 2049 for col_idx in range(len(col_labels)): 2050 self.SetColLabelValue(col_idx, col_labels[col_idx]) 2051 2052 # cell values (list of test results) 2053 for result in results: 2054 row_idx = row_labels.index('%s%s' % ( 2055 gmTools.bool2subst(result['is_fake_meta_type'], '', gmTools.u_sum, ''), 2056 result['unified_abbrev'] 2057 )) 2058 col_idx = col_labels.index(gmDateTime.pydt_strftime(result['clin_when'], self.__date_format, accuracy = gmDateTime.acc_days)) 2059 2060 try: 2061 self.__cell_data[col_idx] 2062 except KeyError: 2063 self.__cell_data[col_idx] = {} 2064 2065 # the tooltip always shows the youngest sub result details 2066 if row_idx in self.__cell_data[col_idx]: 2067 self.__cell_data[col_idx][row_idx].append(result) 2068 self.__cell_data[col_idx][row_idx].sort(key = lambda x: x['clin_when'], reverse = True) 2069 else: 2070 self.__cell_data[col_idx][row_idx] = [result] 2071 2072 # rebuild cell display string 2073 vals2display = [] 2074 cell_has_out_of_bounds_value = False 2075 for sub_result in self.__cell_data[col_idx][row_idx]: 2076 2077 if sub_result.is_considered_abnormal: 2078 cell_has_out_of_bounds_value = True 2079 2080 abnormality_indicator = sub_result.formatted_abnormality_indicator 2081 if abnormality_indicator is None: 2082 abnormality_indicator = '' 2083 if abnormality_indicator != '': 2084 abnormality_indicator = ' (%s)' % abnormality_indicator[:3] 2085 2086 missing_review = False 2087 # warn on missing review if 2088 # a) no review at all exists or 2089 if not sub_result['reviewed']: 2090 missing_review = True 2091 # b) there is a review but 2092 else: 2093 # current user is reviewer and hasn't reviewed 2094 if sub_result['you_are_responsible'] and not sub_result['review_by_you']: 2095 missing_review = True 2096 2097 needs_superscript = False 2098 2099 # can we display the full sub_result length ? 2100 if sub_result.is_long_text: 2101 lines = gmTools.strip_empty_lines ( 2102 text = sub_result['unified_val'], 2103 eol = '\n', 2104 return_list = True 2105 ) 2106 needs_superscript = True 2107 tmp = lines[0][:7] 2108 else: 2109 val = gmTools.strip_empty_lines ( 2110 text = sub_result['unified_val'], 2111 eol = '\n', 2112 return_list = False 2113 ).replace('\n', '//') 2114 if len(val) > 8: 2115 needs_superscript = True 2116 tmp = val[:7] 2117 else: 2118 tmp = '%.8s' % val[:8] 2119 2120 # abnormal ? 2121 tmp = '%s%.6s' % (tmp, abnormality_indicator) 2122 2123 # is there a comment ? 2124 has_sub_result_comment = gmTools.coalesce ( 2125 gmTools.coalesce(sub_result['note_test_org'], sub_result['comment']), 2126 '' 2127 ).strip() != '' 2128 if has_sub_result_comment: 2129 needs_superscript = True 2130 2131 if needs_superscript: 2132 tmp = '%s%s' % (tmp, gmTools.u_superscript_one) 2133 2134 # lacking a review ? 2135 if missing_review: 2136 tmp = '%s %s' % (tmp, gmTools.u_writing_hand) 2137 else: 2138 if sub_result['is_clinically_relevant']: 2139 tmp += ' !' 2140 2141 # part of a multi-result cell ? 2142 if len(self.__cell_data[col_idx][row_idx]) > 1: 2143 tmp = '%s %s' % (sub_result['clin_when'].strftime('%H:%M'), tmp) 2144 2145 vals2display.append(tmp) 2146 2147 self.SetCellValue(row_idx, col_idx, '\n'.join(vals2display)) 2148 self.SetCellAlignment(row_idx, col_idx, horiz = wx.ALIGN_RIGHT, vert = wx.ALIGN_CENTRE) 2149 # We used to color text in cells holding abnormals 2150 # in firebrick red but that would color ALL text (including 2151 # normals) and not only the abnormals within that 2152 # cell. Shading, however, only says that *something* 2153 # inside that cell is worthy of attention. 2154 #if sub_result_relevant: 2155 # font = self.GetCellFont(row_idx, col_idx) 2156 # self.SetCellTextColour(row_idx, col_idx, 'firebrick') 2157 # font.SetWeight(wx.FONTWEIGHT_BOLD) 2158 # self.SetCellFont(row_idx, col_idx, font) 2159 if cell_has_out_of_bounds_value: 2160 #self.SetCellBackgroundColour(row_idx, col_idx, 'cornflower blue') 2161 self.SetCellBackgroundColour(row_idx, col_idx, 'PALE TURQUOISE') 2162 2163 self.EndBatch() 2164 2165 self.AutoSize() 2166 self.AdjustScrollbars() 2167 self.ForceRefresh() 2168 2169 #self.Fit() 2170 2171 return
2172 2173 #------------------------------------------------------------
2174 - def empty_grid(self):
2175 self.BeginBatch() 2176 self.ClearGrid() 2177 # Windows cannot do nothing, it rather decides to assert() 2178 # on thinking it is supposed to do nothing 2179 if self.GetNumberRows() > 0: 2180 self.DeleteRows(pos = 0, numRows = self.GetNumberRows()) 2181 if self.GetNumberCols() > 0: 2182 self.DeleteCols(pos = 0, numCols = self.GetNumberCols()) 2183 self.EndBatch() 2184 self.__cell_data = {} 2185 self.__row_label_data = [] 2186 self.__col_label_data = []
2187 2188 #------------------------------------------------------------
2189 - def get_row_tooltip(self, row=None):
2190 # include details about test types included ? 2191 2192 # sometimes, for some reason, there is no row and 2193 # wxPython still tries to find a tooltip for it 2194 try: 2195 tt = self.__row_label_data[row] 2196 except IndexError: 2197 return ' ' 2198 2199 if tt['is_fake_meta_type']: 2200 return tt.format(patient = self.__patient.ID) 2201 2202 meta_tt = tt.meta_test_type 2203 txt = meta_tt.format(with_tests = True, patient = self.__patient.ID) 2204 2205 return txt
2206 2207 #------------------------------------------------------------
2208 - def get_cell_tooltip(self, col=None, row=None):
2209 try: 2210 cell_results = self.__cell_data[col][row] 2211 except KeyError: 2212 # FIXME: maybe display the most recent or when the most recent was ? 2213 cell_results = None 2214 2215 if cell_results is None: 2216 return ' ' 2217 2218 is_multi_cell = False 2219 if len(cell_results) > 1: 2220 is_multi_cell = True 2221 result = cell_results[0] 2222 2223 tt = '' 2224 # header 2225 if is_multi_cell: 2226 tt += _('Details of most recent (topmost) result ! \n') 2227 if result.is_long_text: 2228 tt += gmTools.strip_empty_lines(text = result['val_alpha'], eol = '\n', return_list = False) 2229 return tt 2230 2231 tt += result.format(with_review = True, with_evaluation = True, with_ranges = True) 2232 return tt
2233 2234 #------------------------------------------------------------ 2235 # internal helpers 2236 #------------------------------------------------------------
2237 - def __init_ui(self):
2238 #self.SetMinSize(wx.DefaultSize) 2239 self.SetMinSize((10, 10)) 2240 2241 self.CreateGrid(0, 1) 2242 self.EnableEditing(0) 2243 self.EnableDragGridSize(1) 2244 2245 # column labels 2246 # setting this screws up the labels: they are cut off and displaced 2247 #self.SetColLabelAlignment(wx.ALIGN_CENTER, wx.ALIGN_BOTTOM) 2248 2249 # row labels 2250 self.SetRowLabelSize(wx.grid.GRID_AUTOSIZE) # starting with 2.8.8 2251 #self.SetRowLabelSize(150) 2252 self.SetRowLabelAlignment(horiz = wx.ALIGN_LEFT, vert = wx.ALIGN_CENTRE) 2253 font = self.GetLabelFont() 2254 font.SetWeight(wx.FONTWEIGHT_LIGHT) 2255 self.SetLabelFont(font) 2256 2257 # add link to left upper corner 2258 dbcfg = gmCfg.cCfgSQL() 2259 url = dbcfg.get2 ( 2260 option = 'external.urls.measurements_encyclopedia', 2261 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace, 2262 bias = 'user', 2263 default = gmPathLab.URL_test_result_information 2264 ) 2265 2266 self.__WIN_corner = self.GetGridCornerLabelWindow() # a wx.Window instance 2267 2268 LNK_lab = wxh.HyperlinkCtrl ( 2269 self.__WIN_corner, 2270 -1, 2271 label = _('Tests'), 2272 style = wxh.HL_DEFAULT_STYLE # wx.TE_READONLY|wx.TE_CENTRE| wx.NO_BORDER | 2273 ) 2274 LNK_lab.SetURL(url) 2275 LNK_lab.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BACKGROUND)) 2276 LNK_lab.SetToolTip(_( 2277 'Navigate to an encyclopedia of measurements\n' 2278 'and test methods on the web.\n' 2279 '\n' 2280 ' <%s>' 2281 ) % url) 2282 2283 SZR_inner = wx.BoxSizer(wx.HORIZONTAL) 2284 SZR_inner.Add((20, 20), 1, wx.EXPAND, 0) # spacer 2285 SZR_inner.Add(LNK_lab, 0, wx.ALIGN_CENTER_VERTICAL, 0) #wx.ALIGN_CENTER wx.EXPAND 2286 SZR_inner.Add((20, 20), 1, wx.EXPAND, 0) # spacer 2287 2288 SZR_corner = wx.BoxSizer(wx.VERTICAL) 2289 SZR_corner.Add((20, 20), 1, wx.EXPAND, 0) # spacer 2290 SZR_corner.Add(SZR_inner, 0, wx.EXPAND) # inner sizer with centered hyperlink 2291 SZR_corner.Add((20, 20), 1, wx.EXPAND, 0) # spacer 2292 2293 self.__WIN_corner.SetSizer(SZR_corner) 2294 SZR_corner.Fit(self.__WIN_corner)
2295 2296 #------------------------------------------------------------
2297 - def __resize_corner_window(self, evt):
2298 self.__WIN_corner.Layout()
2299 2300 #------------------------------------------------------------
2301 - def __cells_to_data(self, cells=None, exclude_multi_cells=False, auto_include_multi_cells=False):
2302 """List of <cells> must be in row / col order.""" 2303 data = [] 2304 for row, col in cells: 2305 try: 2306 # cell data is stored col / row 2307 data_list = self.__cell_data[col][row] 2308 except KeyError: 2309 continue 2310 2311 if len(data_list) == 1: 2312 data.append(data_list[0]) 2313 continue 2314 2315 if exclude_multi_cells: 2316 gmDispatcher.send(signal = 'statustext', msg = _('Excluding multi-result field from further processing.')) 2317 continue 2318 2319 if auto_include_multi_cells: 2320 data.extend(data_list) 2321 continue 2322 2323 data_to_include = self.__get_choices_from_multi_cell(cell_data = data_list) 2324 if data_to_include is None: 2325 continue 2326 data.extend(data_to_include) 2327 2328 return data
2329 2330 #------------------------------------------------------------
2331 - def __get_choices_from_multi_cell(self, cell_data=None, single_selection=False):
2332 data = gmListWidgets.get_choices_from_list ( 2333 parent = self, 2334 msg = _( 2335 'Your selection includes a field with multiple results.\n' 2336 '\n' 2337 'Please select the individual results you want to work on:' 2338 ), 2339 caption = _('Selecting test results'), 2340 choices = [ [d['clin_when'], '%s: %s' % (d['abbrev_tt'], d['name_tt']), d['unified_val']] for d in cell_data ], 2341 columns = [ _('Date / Time'), _('Test'), _('Result') ], 2342 data = cell_data, 2343 single_selection = single_selection 2344 ) 2345 return data
2346 2347 #------------------------------------------------------------ 2348 # event handling 2349 #------------------------------------------------------------
2350 - def __register_events(self):
2351 # dynamic tooltips: GridWindow, GridRowLabelWindow, GridColLabelWindow, GridCornerLabelWindow 2352 self.GetGridWindow().Bind(wx.EVT_MOTION, self.__on_mouse_over_cells) 2353 self.GetGridRowLabelWindow().Bind(wx.EVT_MOTION, self.__on_mouse_over_row_labels) 2354 #self.GetGridColLabelWindow().Bind(wx.EVT_MOTION, self.__on_mouse_over_col_labels) 2355 2356 # sizing left upper corner window 2357 self.Bind(wx.EVT_SIZE, self.__resize_corner_window) 2358 2359 # editing cells 2360 self.Bind(wx.grid.EVT_GRID_CELL_LEFT_DCLICK, self.__on_cell_left_dclicked)
2361 2362 #------------------------------------------------------------
2363 - def __on_cell_left_dclicked(self, evt):
2364 col = evt.GetCol() 2365 row = evt.GetRow() 2366 2367 try: 2368 self.__cell_data[col][row] 2369 except KeyError: # empty cell 2370 presets = {} 2371 col_date = self.__col_label_data[col] 2372 presets['clin_when'] = {'data': col_date} 2373 test_type = self.__row_label_data[row] 2374 temporally_closest_result_of_row_type = test_type.meta_test_type.get_temporally_closest_result(col_date, self.__patient.ID) 2375 if temporally_closest_result_of_row_type is not None: 2376 presets['pk_test_type'] = {'data': temporally_closest_result_of_row_type['pk_test_type']} 2377 same_day_results = gmPathLab.get_results_for_day ( 2378 timestamp = col_date, 2379 patient = self.__patient.ID, 2380 order_by = None 2381 ) 2382 if len(same_day_results) > 0: 2383 presets['pk_episode'] = {'data': same_day_results[0]['pk_episode']} 2384 # maybe ['comment'] as in "medical context" ? - not thought through yet 2385 # no need to set because because setting pk_test_type will do so: 2386 # presets['val_unit'] 2387 # presets['val_normal_min'] 2388 # presets['val_normal_max'] 2389 # presets['val_normal_range'] 2390 # presets['val_target_min'] 2391 # presets['val_target_max'] 2392 # presets['val_target_range'] 2393 edit_measurement ( 2394 parent = self, 2395 measurement = None, 2396 single_entry = True, 2397 presets = presets 2398 ) 2399 return 2400 2401 if len(self.__cell_data[col][row]) > 1: 2402 data = self.__get_choices_from_multi_cell(cell_data = self.__cell_data[col][row], single_selection = True) 2403 else: 2404 data = self.__cell_data[col][row][0] 2405 2406 if data is None: 2407 return 2408 2409 edit_measurement(parent = self, measurement = data, single_entry = True)
2410 2411 #------------------------------------------------------------ 2412 # def OnMouseMotionRowLabel(self, evt): 2413 # x, y = self.CalcUnscrolledPosition(evt.GetPosition()) 2414 # row = self.YToRow(y) 2415 # label = self.table().GetRowHelpValue(row) 2416 # self.GetGridRowLabelWindow().SetToolTip(label or "") 2417 # evt.Skip()
2418 - def __on_mouse_over_row_labels(self, evt):
2419 2420 # Use CalcUnscrolledPosition() to get the mouse position within the 2421 # entire grid including what's offscreen 2422 x, y = self.CalcUnscrolledPosition(evt.GetX(), evt.GetY()) 2423 2424 row = self.YToRow(y) 2425 2426 if self.__prev_label_row == row: 2427 return 2428 2429 self.__prev_label_row == row 2430 2431 evt.GetEventObject().SetToolTip(self.get_row_tooltip(row = row))
2432 #------------------------------------------------------------ 2433 # def OnMouseMotionColLabel(self, evt): 2434 # x, y = self.CalcUnscrolledPosition(evt.GetPosition()) 2435 # col = self.XToCol(x) 2436 # label = self.table().GetColHelpValue(col) 2437 # self.GetGridColLabelWindow().SetToolTip(label or "") 2438 # evt.Skip() 2439 #------------------------------------------------------------
2440 - def __on_mouse_over_cells(self, evt):
2441 """Calculate where the mouse is and set the tooltip dynamically.""" 2442 2443 # Use CalcUnscrolledPosition() to get the mouse position within the 2444 # entire grid including what's offscreen 2445 x, y = self.CalcUnscrolledPosition(evt.GetX(), evt.GetY()) 2446 2447 # use this logic to prevent tooltips outside the actual cells 2448 # apply to GetRowSize, too 2449 # tot = 0 2450 # for col in range(self.NumberCols): 2451 # tot += self.GetColSize(col) 2452 # if xpos <= tot: 2453 # self.tool_tip.Tip = 'Tool tip for Column %s' % ( 2454 # self.GetColLabelValue(col)) 2455 # break 2456 # else: # mouse is in label area beyond the right-most column 2457 # self.tool_tip.Tip = '' 2458 2459 row, col = self.XYToCell(x, y) 2460 2461 if (row == self.__prev_row) and (col == self.__prev_col): 2462 return 2463 2464 self.__prev_row = row 2465 self.__prev_col = col 2466 2467 evt.GetEventObject().SetToolTip(self.get_cell_tooltip(col=col, row=row))
2468 2469 #------------------------------------------------------------ 2470 # properties 2471 #------------------------------------------------------------
2472 - def _get_patient(self):
2473 return self.__patient
2474
2475 - def _set_patient(self, patient):
2476 self.__patient = patient 2477 self.repopulate_grid()
2478 2479 patient = property(_get_patient, _set_patient) 2480 #------------------------------------------------------------
2481 - def _set_panel_to_show(self, panel):
2482 self.__panel_to_show = panel 2483 self.repopulate_grid()
2484 2485 panel_to_show = property(lambda x:x, _set_panel_to_show) 2486 #------------------------------------------------------------
2487 - def _set_show_by_panel(self, show_by_panel):
2488 self.__show_by_panel = show_by_panel 2489 self.repopulate_grid()
2490 2491 show_by_panel = property(lambda x:x, _set_show_by_panel)
2492 2493 #================================================================ 2494 # integrated measurements plugin 2495 #================================================================ 2496 from Gnumed.wxGladeWidgets import wxgMeasurementsPnl 2497
2498 -class cMeasurementsPnl(wxgMeasurementsPnl.wxgMeasurementsPnl, gmRegetMixin.cRegetOnPaintMixin):
2499 """Panel holding a grid with lab data. Used as notebook page.""" 2500
2501 - def __init__(self, *args, **kwargs):
2502 2503 wxgMeasurementsPnl.wxgMeasurementsPnl.__init__(self, *args, **kwargs) 2504 gmRegetMixin.cRegetOnPaintMixin.__init__(self) 2505 self.__display_mode = 'grid' 2506 self.__init_ui() 2507 self.__register_interests()
2508 #-------------------------------------------------------- 2509 # event handling 2510 #--------------------------------------------------------
2511 - def __register_interests(self):
2512 gmDispatcher.connect(signal = 'pre_patient_unselection', receiver = self._on_pre_patient_unselection) 2513 gmDispatcher.connect(signal = 'post_patient_selection', receiver = self._on_post_patient_selection) 2514 gmDispatcher.connect(signal = 'clin.test_result_mod_db', receiver = self._schedule_data_reget) 2515 gmDispatcher.connect(signal = 'clin.reviewed_test_results_mod_db', receiver = self._schedule_data_reget)
2516 #--------------------------------------------------------
2517 - def _on_post_patient_selection(self):
2518 self._schedule_data_reget()
2519 #--------------------------------------------------------
2521 self._GRID_results_all.patient = None 2522 self._GRID_results_battery.patient = None
2523 #--------------------------------------------------------
2524 - def _on_add_button_pressed(self, event):
2525 edit_measurement(parent = self, measurement = None)
2526 #--------------------------------------------------------
2527 - def _on_manage_types_button_pressed(self, event):
2528 event.Skip() 2529 manage_measurement_types(parent = self)
2530 #--------------------------------------------------------
2531 - def _on_list_button_pressed(self, event):
2532 event.Skip() 2533 manage_measurements(parent = self, single_selection = True)#, emr = pat.emr)
2534 #--------------------------------------------------------
2535 - def _on_review_button_pressed(self, evt):
2536 self.PopupMenu(self.__action_button_popup)
2537 #--------------------------------------------------------
2538 - def _on_select_button_pressed(self, evt):
2539 if self._RBTN_my_unsigned.GetValue() is True: 2540 self._GRID_results_all.select_cells(unsigned_only = True, accountables_only = True, keep_preselections = False) 2541 elif self._RBTN_all_unsigned.GetValue() is True: 2542 self._GRID_results_all.select_cells(unsigned_only = True, accountables_only = False, keep_preselections = False)
2543 #--------------------------------------------------------
2544 - def _on_manage_panels_button_pressed(self, event):
2546 #--------------------------------------------------------
2547 - def _on_display_mode_button_pressed(self, event):
2548 event.Skip() 2549 if self.__display_mode == 'grid': 2550 self._BTN_display_mode.SetLabel(_('All: as &Grid')) 2551 self.__display_mode = 'day' 2552 #self._GRID_results_all.Hide() 2553 self._PNL_results_all_grid.Hide() 2554 if self._PNL_results_all_listed.patient is None: 2555 self._PNL_results_all_listed.patient = self._GRID_results_all.patient 2556 self._PNL_results_all_listed.Show() 2557 else: 2558 self._BTN_display_mode.SetLabel(_('All: by &Day')) 2559 self.__display_mode = 'grid' 2560 self._PNL_results_all_listed.Hide() 2561 if self._GRID_results_all.patient is None: 2562 self._GRID_results_all.patient = self._PNL_results_all_listed.patient 2563 #self._GRID_results_all.Show() 2564 self._PNL_results_all_grid.Show() 2565 self.Layout()
2566 #--------------------------------------------------------
2567 - def __on_sign_current_selection(self, evt):
2568 self._GRID_results_all.sign_current_selection()
2569 #--------------------------------------------------------
2570 - def __on_plot_current_selection(self, evt):
2571 self._GRID_results_all.plot_current_selection()
2572 #--------------------------------------------------------
2573 - def __on_delete_current_selection(self, evt):
2574 self._GRID_results_all.delete_current_selection()
2575 #--------------------------------------------------------
2576 - def _on_panel_selected(self, panel):
2577 wx.CallAfter(self.__on_panel_selected, panel=panel)
2578 #--------------------------------------------------------
2579 - def __on_panel_selected(self, panel):
2580 if panel is None: 2581 self._TCTRL_panel_comment.SetValue('') 2582 self._GRID_results_battery.panel_to_show = None 2583 #self._GRID_results_battery.Hide() 2584 self._PNL_results_battery_grid.Hide() 2585 else: 2586 pnl = self._PRW_panel.GetData(as_instance = True) 2587 self._TCTRL_panel_comment.SetValue(gmTools.coalesce ( 2588 pnl['comment'], 2589 '' 2590 )) 2591 self._GRID_results_battery.panel_to_show = pnl 2592 #self._GRID_results_battery.Show() 2593 self._PNL_results_battery_grid.Show() 2594 self._GRID_results_battery.Fit() 2595 self._GRID_results_all.Fit() 2596 self.Layout()
2597 #--------------------------------------------------------
2599 wx.CallAfter(self.__on_panel_selection_modified)
2600 #--------------------------------------------------------
2602 self._TCTRL_panel_comment.SetValue('') 2603 if self._PRW_panel.GetValue().strip() == '': 2604 self._GRID_results_battery.panel_to_show = None 2605 #self._GRID_results_battery.Hide() 2606 self._PNL_results_battery_grid.Hide() 2607 self.Layout()
2608 #-------------------------------------------------------- 2609 # internal API 2610 #--------------------------------------------------------
2611 - def __init_ui(self):
2612 self.SetMinSize((10, 10)) 2613 2614 self.__action_button_popup = wx.Menu(title = _('Perform on selected results:')) 2615 2616 item = self.__action_button_popup.Append(-1, _('Review and &sign')) 2617 self.Bind(wx.EVT_MENU, self.__on_sign_current_selection, item) 2618 2619 item = self.__action_button_popup.Append(-1, _('Plot')) 2620 self.Bind(wx.EVT_MENU, self.__on_plot_current_selection, item) 2621 2622 item = self.__action_button_popup.Append(-1, _('Export to &file')) 2623 self.Bind(wx.EVT_MENU, self._GRID_results_all.current_selection_to_file, item) 2624 self.__action_button_popup.Enable(id = menu_id, enable = False) 2625 2626 item = self.__action_button_popup.Append(-1, _('Export to &clipboard')) 2627 self.Bind(wx.EVT_MENU, self._GRID_results_all.current_selection_to_clipboard, item) 2628 self.__action_button_popup.Enable(id = menu_id, enable = False) 2629 2630 item = self.__action_button_popup.Append(-1, _('&Delete')) 2631 self.Bind(wx.EVT_MENU, self.__on_delete_current_selection, item) 2632 2633 # FIXME: create inbox message to staff to phone patient to come in 2634 # FIXME: generate and let edit a SOAP narrative and include the values 2635 2636 self._PRW_panel.add_callback_on_selection(callback = self._on_panel_selected) 2637 self._PRW_panel.add_callback_on_modified(callback = self._on_panel_selection_modified) 2638 2639 self._GRID_results_battery.show_by_panel = True 2640 self._GRID_results_battery.panel_to_show = None 2641 #self._GRID_results_battery.Hide() 2642 self._PNL_results_battery_grid.Hide() 2643 self._BTN_display_mode.SetLabel(_('All: by &Day')) 2644 #self._GRID_results_all.Show() 2645 self._PNL_results_all_grid.Show() 2646 self._PNL_results_all_listed.Hide() 2647 self.Layout() 2648 2649 self._PRW_panel.SetFocus()
2650 #-------------------------------------------------------- 2651 # reget mixin API 2652 #--------------------------------------------------------
2653 - def _populate_with_data(self):
2654 pat = gmPerson.gmCurrentPatient() 2655 if pat.connected: 2656 self._GRID_results_battery.patient = pat 2657 if self.__display_mode == 'grid': 2658 self._GRID_results_all.patient = pat 2659 self._PNL_results_all_listed.patient = None 2660 else: 2661 self._GRID_results_all.patient = None 2662 self._PNL_results_all_listed.patient = pat 2663 else: 2664 self._GRID_results_battery.patient = None 2665 self._GRID_results_all.patient = None 2666 self._PNL_results_all_listed.patient = None 2667 return True
2668 2669 #================================================================ 2670 # editing widgets 2671 #================================================================
2672 -def review_tests(parent=None, tests=None):
2673 2674 if tests is None: 2675 return True 2676 2677 if len(tests) == 0: 2678 return True 2679 2680 if parent is None: 2681 parent = wx.GetApp().GetTopWindow() 2682 2683 if len(tests) > 10: 2684 test_count = len(tests) 2685 tests2show = None 2686 else: 2687 test_count = None 2688 tests2show = tests 2689 if len(tests) == 0: 2690 return True 2691 2692 dlg = cMeasurementsReviewDlg(parent, -1, tests = tests, test_count = test_count) 2693 decision = dlg.ShowModal() 2694 if decision != wx.ID_APPLY: 2695 return True 2696 2697 wx.BeginBusyCursor() 2698 if dlg._RBTN_confirm_abnormal.GetValue(): 2699 abnormal = None 2700 elif dlg._RBTN_results_normal.GetValue(): 2701 abnormal = False 2702 else: 2703 abnormal = True 2704 2705 if dlg._RBTN_confirm_relevance.GetValue(): 2706 relevant = None 2707 elif dlg._RBTN_results_not_relevant.GetValue(): 2708 relevant = False 2709 else: 2710 relevant = True 2711 2712 comment = None 2713 if len(tests) == 1: 2714 comment = dlg._TCTRL_comment.GetValue() 2715 2716 make_responsible = dlg._CHBOX_responsible.IsChecked() 2717 dlg.DestroyLater() 2718 2719 for test in tests: 2720 test.set_review ( 2721 technically_abnormal = abnormal, 2722 clinically_relevant = relevant, 2723 comment = comment, 2724 make_me_responsible = make_responsible 2725 ) 2726 wx.EndBusyCursor() 2727 2728 return True
2729 2730 #---------------------------------------------------------------- 2731 from Gnumed.wxGladeWidgets import wxgMeasurementsReviewDlg 2732
2733 -class cMeasurementsReviewDlg(wxgMeasurementsReviewDlg.wxgMeasurementsReviewDlg):
2734
2735 - def __init__(self, *args, **kwargs):
2736 2737 try: 2738 tests = kwargs['tests'] 2739 del kwargs['tests'] 2740 test_count = len(tests) 2741 try: del kwargs['test_count'] 2742 except KeyError: pass 2743 except KeyError: 2744 tests = None 2745 test_count = kwargs['test_count'] 2746 del kwargs['test_count'] 2747 2748 wxgMeasurementsReviewDlg.wxgMeasurementsReviewDlg.__init__(self, *args, **kwargs) 2749 2750 if tests is None: 2751 msg = _('%s results selected. Too many to list individually.') % test_count 2752 else: 2753 msg = '\n'.join ( 2754 [ '%s: %s %s (%s)' % ( 2755 t['unified_abbrev'], 2756 t['unified_val'], 2757 t['val_unit'], 2758 gmDateTime.pydt_strftime(t['clin_when'], '%Y %b %d') 2759 ) for t in tests 2760 ] 2761 ) 2762 2763 self._LBL_tests.SetLabel(msg) 2764 2765 if test_count == 1: 2766 self._TCTRL_comment.Enable(True) 2767 self._TCTRL_comment.SetValue(gmTools.coalesce(tests[0]['review_comment'], '')) 2768 if tests[0]['you_are_responsible']: 2769 self._CHBOX_responsible.Enable(False) 2770 2771 self.Fit()
2772 #-------------------------------------------------------- 2773 # event handling 2774 #--------------------------------------------------------
2775 - def _on_signoff_button_pressed(self, evt):
2776 if self.IsModal(): 2777 self.EndModal(wx.ID_APPLY) 2778 else: 2779 self.Close()
2780 2781 #================================================================ 2782 from Gnumed.wxGladeWidgets import wxgMeasurementEditAreaPnl 2783
2784 -class cMeasurementEditAreaPnl(wxgMeasurementEditAreaPnl.wxgMeasurementEditAreaPnl, gmEditArea.cGenericEditAreaMixin):
2785 """This edit area saves *new* measurements into the active patient only.""" 2786
2787 - def __init__(self, *args, **kwargs):
2788 2789 try: 2790 self.__default_date = kwargs['date'] 2791 del kwargs['date'] 2792 except KeyError: 2793 self.__default_date = None 2794 2795 wxgMeasurementEditAreaPnl.wxgMeasurementEditAreaPnl.__init__(self, *args, **kwargs) 2796 gmEditArea.cGenericEditAreaMixin.__init__(self) 2797 2798 self.__register_interests() 2799 2800 self.successful_save_msg = _('Successfully saved measurement.') 2801 2802 self._DPRW_evaluated.display_accuracy = gmDateTime.acc_minutes
2803 2804 #-------------------------------------------------------- 2805 # generic edit area mixin API 2806 #----------------------------------------------------------------
2807 - def set_fields(self, fields):
2808 try: 2809 self._PRW_test.SetData(data = fields['pk_test_type']['data']) 2810 except KeyError: 2811 pass 2812 try: 2813 self._DPRW_evaluated.SetData(data = fields['clin_when']['data']) 2814 except KeyError: 2815 pass 2816 try: 2817 self._PRW_problem.SetData(data = fields['pk_episode']['data']) 2818 except KeyError: 2819 pass 2820 try: 2821 self._PRW_units.SetText(fields['val_unit']['data'], fields['val_unit']['data'], True) 2822 except KeyError: 2823 pass 2824 try: 2825 self._TCTRL_normal_min.SetValue(fields['val_normal_min']['data']) 2826 except KeyError: 2827 pass 2828 try: 2829 self._TCTRL_normal_max.SetValue(fields['val_normal_max']['data']) 2830 except KeyError: 2831 pass 2832 try: 2833 self._TCTRL_normal_range.SetValue(fields['val_normal_range']['data']) 2834 except KeyError: 2835 pass 2836 try: 2837 self._TCTRL_target_min.SetValue(fields['val_target_min']['data']) 2838 except KeyError: 2839 pass 2840 try: 2841 self._TCTRL_target_max.SetValue(fields['val_target_max']['data']) 2842 except KeyError: 2843 pass 2844 try: 2845 self._TCTRL_target_range.SetValue(fields['val_target_range']['data']) 2846 except KeyError: 2847 pass 2848 2849 self._TCTRL_result.SetFocus()
2850 2851 #--------------------------------------------------------
2852 - def _refresh_as_new(self):
2853 self._PRW_test.SetText('', None, True) 2854 self.__refresh_loinc_info() 2855 self.__refresh_previous_value() 2856 self.__update_units_context() 2857 self._TCTRL_result.SetValue('') 2858 self._PRW_units.SetText('', None, True) 2859 self._PRW_abnormality_indicator.SetText('', None, True) 2860 if self.__default_date is None: 2861 self._DPRW_evaluated.SetData(data = pyDT.datetime.now(tz = gmDateTime.gmCurrentLocalTimezone)) 2862 else: 2863 self._DPRW_evaluated.SetData(data = None) 2864 self._TCTRL_note_test_org.SetValue('') 2865 self._PRW_intended_reviewer.SetData(gmStaff.gmCurrentProvider()['pk_staff']) 2866 self._PRW_problem.SetData() 2867 self._TCTRL_narrative.SetValue('') 2868 self._CHBOX_review.SetValue(False) 2869 self._CHBOX_abnormal.SetValue(False) 2870 self._CHBOX_relevant.SetValue(False) 2871 self._CHBOX_abnormal.Enable(False) 2872 self._CHBOX_relevant.Enable(False) 2873 self._TCTRL_review_comment.SetValue('') 2874 self._TCTRL_normal_min.SetValue('') 2875 self._TCTRL_normal_max.SetValue('') 2876 self._TCTRL_normal_range.SetValue('') 2877 self._TCTRL_target_min.SetValue('') 2878 self._TCTRL_target_max.SetValue('') 2879 self._TCTRL_target_range.SetValue('') 2880 self._TCTRL_norm_ref_group.SetValue('') 2881 2882 self._PRW_test.SetFocus()
2883 #--------------------------------------------------------
2884 - def _refresh_from_existing(self):
2885 self._PRW_test.SetData(data = self.data['pk_test_type']) 2886 self.__refresh_loinc_info() 2887 self.__refresh_previous_value() 2888 self.__update_units_context() 2889 self._TCTRL_result.SetValue(self.data['unified_val']) 2890 self._PRW_units.SetText(self.data['val_unit'], self.data['val_unit'], True) 2891 self._PRW_abnormality_indicator.SetText ( 2892 gmTools.coalesce(self.data['abnormality_indicator'], ''), 2893 gmTools.coalesce(self.data['abnormality_indicator'], ''), 2894 True 2895 ) 2896 self._DPRW_evaluated.SetData(data = self.data['clin_when']) 2897 self._TCTRL_note_test_org.SetValue(gmTools.coalesce(self.data['note_test_org'], '')) 2898 self._PRW_intended_reviewer.SetData(self.data['pk_intended_reviewer']) 2899 self._PRW_problem.SetData(self.data['pk_episode']) 2900 self._TCTRL_narrative.SetValue(gmTools.coalesce(self.data['comment'], '')) 2901 self._CHBOX_review.SetValue(False) 2902 self._CHBOX_abnormal.SetValue(gmTools.coalesce(self.data['is_technically_abnormal'], False)) 2903 self._CHBOX_relevant.SetValue(gmTools.coalesce(self.data['is_clinically_relevant'], False)) 2904 self._CHBOX_abnormal.Enable(False) 2905 self._CHBOX_relevant.Enable(False) 2906 self._TCTRL_review_comment.SetValue(gmTools.coalesce(self.data['review_comment'], '')) 2907 self._TCTRL_normal_min.SetValue(str(gmTools.coalesce(self.data['val_normal_min'], ''))) 2908 self._TCTRL_normal_max.SetValue(str(gmTools.coalesce(self.data['val_normal_max'], ''))) 2909 self._TCTRL_normal_range.SetValue(gmTools.coalesce(self.data['val_normal_range'], '')) 2910 self._TCTRL_target_min.SetValue(str(gmTools.coalesce(self.data['val_target_min'], ''))) 2911 self._TCTRL_target_max.SetValue(str(gmTools.coalesce(self.data['val_target_max'], ''))) 2912 self._TCTRL_target_range.SetValue(gmTools.coalesce(self.data['val_target_range'], '')) 2913 self._TCTRL_norm_ref_group.SetValue(gmTools.coalesce(self.data['norm_ref_group'], '')) 2914 2915 self._TCTRL_result.SetFocus()
2916 #--------------------------------------------------------
2918 self._PRW_test.SetText('', None, True) 2919 self.__refresh_loinc_info() 2920 self.__refresh_previous_value() 2921 self.__update_units_context() 2922 self._TCTRL_result.SetValue('') 2923 self._PRW_units.SetText('', None, True) 2924 self._PRW_abnormality_indicator.SetText('', None, True) 2925 self._DPRW_evaluated.SetData(data = self.data['clin_when']) 2926 self._TCTRL_note_test_org.SetValue('') 2927 self._PRW_intended_reviewer.SetData(self.data['pk_intended_reviewer']) 2928 self._PRW_problem.SetData(self.data['pk_episode']) 2929 self._TCTRL_narrative.SetValue('') 2930 self._CHBOX_review.SetValue(False) 2931 self._CHBOX_abnormal.SetValue(False) 2932 self._CHBOX_relevant.SetValue(False) 2933 self._CHBOX_abnormal.Enable(False) 2934 self._CHBOX_relevant.Enable(False) 2935 self._TCTRL_review_comment.SetValue('') 2936 self._TCTRL_normal_min.SetValue('') 2937 self._TCTRL_normal_max.SetValue('') 2938 self._TCTRL_normal_range.SetValue('') 2939 self._TCTRL_target_min.SetValue('') 2940 self._TCTRL_target_max.SetValue('') 2941 self._TCTRL_target_range.SetValue('') 2942 self._TCTRL_norm_ref_group.SetValue('') 2943 2944 self._PRW_test.SetFocus()
2945 #--------------------------------------------------------
2946 - def _valid_for_save(self):
2947 2948 validity = True 2949 2950 if not self._DPRW_evaluated.is_valid_timestamp(): 2951 self._DPRW_evaluated.display_as_valid(False) 2952 validity = False 2953 else: 2954 self._DPRW_evaluated.display_as_valid(True) 2955 2956 val = self._TCTRL_result.GetValue().strip() 2957 if val == '': 2958 validity = False 2959 self.display_ctrl_as_valid(self._TCTRL_result, False) 2960 else: 2961 self.display_ctrl_as_valid(self._TCTRL_result, True) 2962 numeric, val = gmTools.input2decimal(val) 2963 if numeric: 2964 if self._PRW_units.GetValue().strip() == '': 2965 self._PRW_units.display_as_valid(False) 2966 validity = False 2967 else: 2968 self._PRW_units.display_as_valid(True) 2969 else: 2970 self._PRW_units.display_as_valid(True) 2971 2972 if self._PRW_problem.GetValue().strip() == '': 2973 self._PRW_problem.display_as_valid(False) 2974 validity = False 2975 else: 2976 self._PRW_problem.display_as_valid(True) 2977 2978 if self._PRW_test.GetValue().strip() == '': 2979 self._PRW_test.display_as_valid(False) 2980 validity = False 2981 else: 2982 self._PRW_test.display_as_valid(True) 2983 2984 if self._PRW_intended_reviewer.GetData() is None: 2985 self._PRW_intended_reviewer.display_as_valid(False) 2986 validity = False 2987 else: 2988 self._PRW_intended_reviewer.display_as_valid(True) 2989 2990 ctrls = [self._TCTRL_normal_min, self._TCTRL_normal_max, self._TCTRL_target_min, self._TCTRL_target_max] 2991 for widget in ctrls: 2992 val = widget.GetValue().strip() 2993 if val == '': 2994 continue 2995 try: 2996 decimal.Decimal(val.replace(',', '.', 1)) 2997 self.display_ctrl_as_valid(widget, True) 2998 except: 2999 validity = False 3000 self.display_ctrl_as_valid(widget, False) 3001 3002 if validity is False: 3003 self.StatusText = _('Cannot save result. Invalid or missing essential input.') 3004 3005 return validity
3006 #--------------------------------------------------------
3007 - def _save_as_new(self):
3008 3009 emr = gmPerson.gmCurrentPatient().emr 3010 3011 success, result = gmTools.input2decimal(self._TCTRL_result.GetValue()) 3012 if success: 3013 v_num = result 3014 v_al = None 3015 else: 3016 v_al = self._TCTRL_result.GetValue().strip() 3017 v_num = None 3018 3019 pk_type = self._PRW_test.GetData() 3020 if pk_type is None: 3021 abbrev = self._PRW_test.GetValue().strip() 3022 name = self._PRW_test.GetValue().strip() 3023 unit = gmTools.coalesce(self._PRW_units.GetData(), self._PRW_units.GetValue()).strip() 3024 lab = manage_measurement_orgs ( 3025 parent = self, 3026 msg = _('Please select (or create) a lab for the new test type [%s in %s]') % (name, unit) 3027 ) 3028 if lab is not None: 3029 lab = lab['pk_test_org'] 3030 tt = gmPathLab.create_measurement_type ( 3031 lab = lab, 3032 abbrev = abbrev, 3033 name = name, 3034 unit = unit 3035 ) 3036 pk_type = tt['pk_test_type'] 3037 3038 tr = emr.add_test_result ( 3039 episode = self._PRW_problem.GetData(can_create=True, is_open=False), 3040 type = pk_type, 3041 intended_reviewer = self._PRW_intended_reviewer.GetData(), 3042 val_num = v_num, 3043 val_alpha = v_al, 3044 unit = self._PRW_units.GetValue() 3045 ) 3046 3047 tr['clin_when'] = self._DPRW_evaluated.GetData().get_pydt() 3048 3049 ctrls = [ 3050 ('abnormality_indicator', self._PRW_abnormality_indicator), 3051 ('note_test_org', self._TCTRL_note_test_org), 3052 ('comment', self._TCTRL_narrative), 3053 ('val_normal_range', self._TCTRL_normal_range), 3054 ('val_target_range', self._TCTRL_target_range), 3055 ('norm_ref_group', self._TCTRL_norm_ref_group) 3056 ] 3057 for field, widget in ctrls: 3058 tr[field] = widget.GetValue().strip() 3059 3060 ctrls = [ 3061 ('val_normal_min', self._TCTRL_normal_min), 3062 ('val_normal_max', self._TCTRL_normal_max), 3063 ('val_target_min', self._TCTRL_target_min), 3064 ('val_target_max', self._TCTRL_target_max) 3065 ] 3066 for field, widget in ctrls: 3067 val = widget.GetValue().strip() 3068 if val == '': 3069 tr[field] = None 3070 else: 3071 tr[field] = decimal.Decimal(val.replace(',', '.', 1)) 3072 3073 tr.save_payload() 3074 3075 if self._CHBOX_review.GetValue() is True: 3076 tr.set_review ( 3077 technically_abnormal = self._CHBOX_abnormal.GetValue(), 3078 clinically_relevant = self._CHBOX_relevant.GetValue(), 3079 comment = gmTools.none_if(self._TCTRL_review_comment.GetValue().strip(), ''), 3080 make_me_responsible = False 3081 ) 3082 3083 self.data = tr 3084 3085 # wx.CallAfter ( 3086 # plot_adjacent_measurements, 3087 # test = self.data, 3088 # plot_singular_result = False, 3089 # use_default_template = True 3090 # ) 3091 3092 return True
3093 #--------------------------------------------------------
3094 - def _save_as_update(self):
3095 3096 success, result = gmTools.input2decimal(self._TCTRL_result.GetValue()) 3097 if success: 3098 v_num = result 3099 v_al = None 3100 else: 3101 v_num = None 3102 v_al = self._TCTRL_result.GetValue().strip() 3103 3104 pk_type = self._PRW_test.GetData() 3105 if pk_type is None: 3106 abbrev = self._PRW_test.GetValue().strip() 3107 name = self._PRW_test.GetValue().strip() 3108 unit = gmTools.coalesce(self._PRW_units.GetData(), self._PRW_units.GetValue()).strip() 3109 lab = manage_measurement_orgs ( 3110 parent = self, 3111 msg = _('Please select (or create) a lab for the new test type [%s in %s]') % (name, unit) 3112 ) 3113 if lab is not None: 3114 lab = lab['pk_test_org'] 3115 tt = gmPathLab.create_measurement_type ( 3116 lab = None, 3117 abbrev = abbrev, 3118 name = name, 3119 unit = unit 3120 ) 3121 pk_type = tt['pk_test_type'] 3122 3123 tr = self.data 3124 3125 tr['pk_episode'] = self._PRW_problem.GetData(can_create=True, is_open=False) 3126 tr['pk_test_type'] = pk_type 3127 tr['pk_intended_reviewer'] = self._PRW_intended_reviewer.GetData() 3128 tr['val_num'] = v_num 3129 tr['val_alpha'] = v_al 3130 tr['val_unit'] = gmTools.coalesce(self._PRW_units.GetData(), self._PRW_units.GetValue()).strip() 3131 tr['clin_when'] = self._DPRW_evaluated.GetData().get_pydt() 3132 3133 ctrls = [ 3134 ('abnormality_indicator', self._PRW_abnormality_indicator), 3135 ('note_test_org', self._TCTRL_note_test_org), 3136 ('comment', self._TCTRL_narrative), 3137 ('val_normal_range', self._TCTRL_normal_range), 3138 ('val_target_range', self._TCTRL_target_range), 3139 ('norm_ref_group', self._TCTRL_norm_ref_group) 3140 ] 3141 for field, widget in ctrls: 3142 tr[field] = widget.GetValue().strip() 3143 3144 ctrls = [ 3145 ('val_normal_min', self._TCTRL_normal_min), 3146 ('val_normal_max', self._TCTRL_normal_max), 3147 ('val_target_min', self._TCTRL_target_min), 3148 ('val_target_max', self._TCTRL_target_max) 3149 ] 3150 for field, widget in ctrls: 3151 val = widget.GetValue().strip() 3152 if val == '': 3153 tr[field] = None 3154 else: 3155 tr[field] = decimal.Decimal(val.replace(',', '.', 1)) 3156 3157 tr.save_payload() 3158 3159 if self._CHBOX_review.GetValue() is True: 3160 tr.set_review ( 3161 technically_abnormal = self._CHBOX_abnormal.GetValue(), 3162 clinically_relevant = self._CHBOX_relevant.GetValue(), 3163 comment = gmTools.none_if(self._TCTRL_review_comment.GetValue().strip(), ''), 3164 make_me_responsible = False 3165 ) 3166 3167 # wx.CallAfter ( 3168 # plot_adjacent_measurements, 3169 # test = self.data, 3170 # plot_singular_result = False, 3171 # use_default_template = True 3172 # ) 3173 3174 return True
3175 #-------------------------------------------------------- 3176 # event handling 3177 #--------------------------------------------------------
3178 - def __register_interests(self):
3179 self._PRW_test.add_callback_on_lose_focus(self._on_leave_test_prw) 3180 self._PRW_abnormality_indicator.add_callback_on_lose_focus(self._on_leave_indicator_prw) 3181 self._PRW_units.add_callback_on_lose_focus(self._on_leave_unit_prw)
3182 #--------------------------------------------------------
3183 - def _on_leave_test_prw(self):
3184 self.__refresh_loinc_info() 3185 self.__refresh_previous_value() 3186 self.__update_units_context() 3187 # only works if we've got a unit set 3188 self.__update_normal_range() 3189 self.__update_clinical_range()
3190 #--------------------------------------------------------
3191 - def _on_leave_unit_prw(self):
3192 # maybe we've got a unit now ? 3193 self.__update_normal_range() 3194 self.__update_clinical_range()
3195 #--------------------------------------------------------
3196 - def _on_leave_indicator_prw(self):
3197 # if the user hasn't explicitly enabled reviewing 3198 if not self._CHBOX_review.GetValue(): 3199 self._CHBOX_abnormal.SetValue(self._PRW_abnormality_indicator.GetValue().strip() != '')
3200 #--------------------------------------------------------
3201 - def _on_review_box_checked(self, evt):
3202 self._CHBOX_abnormal.Enable(self._CHBOX_review.GetValue()) 3203 self._CHBOX_relevant.Enable(self._CHBOX_review.GetValue()) 3204 self._TCTRL_review_comment.Enable(self._CHBOX_review.GetValue())
3205 #--------------------------------------------------------
3206 - def _on_test_info_button_pressed(self, event):
3207 pk = self._PRW_test.GetData() 3208 if pk is not None: 3209 tt = gmPathLab.cMeasurementType(aPK_obj = pk) 3210 search_term = '%s %s %s' % ( 3211 tt['name'], 3212 tt['abbrev'], 3213 gmTools.coalesce(tt['loinc'], '') 3214 ) 3215 else: 3216 search_term = self._PRW_test.GetValue() 3217 3218 search_term = search_term.replace(' ', '+') 3219 3220 call_browser_on_measurement_type(measurement_type = search_term)
3221 #--------------------------------------------------------
3222 - def _on_manage_episodes_button_pressed(self, event):
3225 #-------------------------------------------------------- 3226 # internal helpers 3227 #--------------------------------------------------------
3228 - def __update_units_context(self):
3229 3230 if self._PRW_test.GetData() is None: 3231 self._PRW_units.unset_context(context = 'pk_type') 3232 self._PRW_units.unset_context(context = 'loinc') 3233 if self._PRW_test.GetValue().strip() == '': 3234 self._PRW_units.unset_context(context = 'test_name') 3235 else: 3236 self._PRW_units.set_context(context = 'test_name', val = self._PRW_test.GetValue().strip()) 3237 return 3238 3239 tt = self._PRW_test.GetData(as_instance = True) 3240 3241 self._PRW_units.set_context(context = 'pk_type', val = tt['pk_test_type']) 3242 self._PRW_units.set_context(context = 'test_name', val = tt['name']) 3243 3244 if tt['loinc'] is not None: 3245 self._PRW_units.set_context(context = 'loinc', val = tt['loinc']) 3246 3247 # closest unit 3248 if self._PRW_units.GetValue().strip() == '': 3249 clin_when = self._DPRW_evaluated.GetData() 3250 if clin_when is None: 3251 unit = tt.temporally_closest_unit 3252 else: 3253 clin_when = clin_when.get_pydt() 3254 unit = tt.get_temporally_closest_unit(timestamp = clin_when) 3255 if unit is None: 3256 self._PRW_units.SetText('', unit, True) 3257 else: 3258 self._PRW_units.SetText(unit, unit, True)
3259 3260 #--------------------------------------------------------
3261 - def __update_normal_range(self):
3262 unit = self._PRW_units.GetValue().strip() 3263 if unit == '': 3264 return 3265 if self._PRW_test.GetData() is None: 3266 return 3267 for ctrl in [self._TCTRL_normal_min, self._TCTRL_normal_max, self._TCTRL_normal_range, self._TCTRL_norm_ref_group]: 3268 if ctrl.GetValue().strip() != '': 3269 return 3270 tt = self._PRW_test.GetData(as_instance = True) 3271 test_w_range = tt.get_temporally_closest_normal_range ( 3272 unit, 3273 timestamp = self._DPRW_evaluated.GetData().get_pydt() 3274 ) 3275 if test_w_range is None: 3276 return 3277 self._TCTRL_normal_min.SetValue(str(gmTools.coalesce(test_w_range['val_normal_min'], ''))) 3278 self._TCTRL_normal_max.SetValue(str(gmTools.coalesce(test_w_range['val_normal_max'], ''))) 3279 self._TCTRL_normal_range.SetValue(gmTools.coalesce(test_w_range['val_normal_range'], '')) 3280 self._TCTRL_norm_ref_group.SetValue(gmTools.coalesce(test_w_range['norm_ref_group'], ''))
3281 3282 #--------------------------------------------------------
3283 - def __update_clinical_range(self):
3284 unit = self._PRW_units.GetValue().strip() 3285 if unit == '': 3286 return 3287 if self._PRW_test.GetData() is None: 3288 return 3289 for ctrl in [self._TCTRL_target_min, self._TCTRL_target_max, self._TCTRL_target_range]: 3290 if ctrl.GetValue().strip() != '': 3291 return 3292 tt = self._PRW_test.GetData(as_instance = True) 3293 test_w_range = tt.get_temporally_closest_target_range ( 3294 unit, 3295 gmPerson.gmCurrentPatient().ID, 3296 timestamp = self._DPRW_evaluated.GetData().get_pydt() 3297 ) 3298 if test_w_range is None: 3299 return 3300 self._TCTRL_target_min.SetValue(str(gmTools.coalesce(test_w_range['val_target_min'], ''))) 3301 self._TCTRL_target_max.SetValue(str(gmTools.coalesce(test_w_range['val_target_max'], ''))) 3302 self._TCTRL_target_range.SetValue(gmTools.coalesce(test_w_range['val_target_range'], ''))
3303 3304 #--------------------------------------------------------
3305 - def __refresh_loinc_info(self):
3306 3307 self._TCTRL_loinc.SetValue('') 3308 3309 if self._PRW_test.GetData() is None: 3310 return 3311 3312 tt = self._PRW_test.GetData(as_instance = True) 3313 3314 if tt['loinc'] is None: 3315 return 3316 3317 info = gmLOINC.loinc2term(loinc = tt['loinc']) 3318 if len(info) == 0: 3319 self._TCTRL_loinc.SetValue('') 3320 return 3321 3322 self._TCTRL_loinc.SetValue('%s: %s' % (tt['loinc'], info[0]))
3323 #--------------------------------------------------------
3324 - def __refresh_previous_value(self):
3325 self._TCTRL_previous_value.SetValue('') 3326 # it doesn't make much sense to show the most 3327 # recent value when editing an existing one 3328 if self.data is not None: 3329 return 3330 if self._PRW_test.GetData() is None: 3331 return 3332 tt = self._PRW_test.GetData(as_instance = True) 3333 most_recent = tt.get_most_recent_results ( 3334 no_of_results = 1, 3335 patient = gmPerson.gmCurrentPatient().ID 3336 ) 3337 if most_recent is None: 3338 return 3339 self._TCTRL_previous_value.SetValue(_('%s ago: %s%s%s - %s%s') % ( 3340 gmDateTime.format_interval_medically(gmDateTime.pydt_now_here() - most_recent['clin_when']), 3341 most_recent['unified_val'], 3342 most_recent['val_unit'], 3343 gmTools.coalesce(most_recent['abnormality_indicator'], '', ' (%s)'), 3344 most_recent['abbrev_tt'], 3345 gmTools.coalesce(most_recent.formatted_range, '', ' [%s]') 3346 )) 3347 self._TCTRL_previous_value.SetToolTip(most_recent.format ( 3348 with_review = True, 3349 with_evaluation = False, 3350 with_ranges = True, 3351 with_episode = True, 3352 with_type_details=True 3353 ))
3354 3355 #================================================================ 3356 # measurement type handling 3357 #================================================================
3358 -def pick_measurement_types(parent=None, msg=None, right_column=None, picks=None):
3359 3360 if parent is None: 3361 parent = wx.GetApp().GetTopWindow() 3362 3363 if msg is None: 3364 msg = _('Pick the relevant measurement types.') 3365 3366 if right_column is None: 3367 right_columns = [_('Picked')] 3368 else: 3369 right_columns = [right_column] 3370 3371 picker = gmListWidgets.cItemPickerDlg(parent, -1, msg = msg) 3372 picker.set_columns(columns = [_('Known measurement types')], columns_right = right_columns) 3373 types = gmPathLab.get_measurement_types(order_by = 'unified_abbrev') 3374 picker.set_choices ( 3375 choices = [ 3376 '%s: %s%s' % ( 3377 t['unified_abbrev'], 3378 t['unified_name'], 3379 gmTools.coalesce(t['name_org'], '', ' (%s)') 3380 ) 3381 for t in types 3382 ], 3383 data = types 3384 ) 3385 if picks is not None: 3386 picker.set_picks ( 3387 picks = [ 3388 '%s: %s%s' % ( 3389 p['unified_abbrev'], 3390 p['unified_name'], 3391 gmTools.coalesce(p['name_org'], '', ' (%s)') 3392 ) 3393 for p in picks 3394 ], 3395 data = picks 3396 ) 3397 result = picker.ShowModal() 3398 3399 if result == wx.ID_CANCEL: 3400 picker.DestroyLater() 3401 return None 3402 3403 picks = picker.picks 3404 picker.DestroyLater() 3405 return picks
3406 3407 #----------------------------------------------------------------
3408 -def manage_measurement_types(parent=None):
3409 3410 if parent is None: 3411 parent = wx.GetApp().GetTopWindow() 3412 3413 #------------------------------------------------------------ 3414 def edit(test_type=None): 3415 ea = cMeasurementTypeEAPnl(parent, -1, type = test_type) 3416 dlg = gmEditArea.cGenericEditAreaDlg2 ( 3417 parent = parent, 3418 id = -1, 3419 edit_area = ea, 3420 single_entry = gmTools.bool2subst((test_type is None), False, True) 3421 ) 3422 dlg.SetTitle(gmTools.coalesce(test_type, _('Adding measurement type'), _('Editing measurement type'))) 3423 3424 if dlg.ShowModal() == wx.ID_OK: 3425 dlg.DestroyLater() 3426 return True 3427 3428 dlg.DestroyLater() 3429 return False
3430 3431 #------------------------------------------------------------ 3432 def delete(measurement_type): 3433 if measurement_type.in_use: 3434 gmDispatcher.send ( 3435 signal = 'statustext', 3436 beep = True, 3437 msg = _('Cannot delete measurement type [%s (%s)] because it is in use.') % (measurement_type['name'], measurement_type['abbrev']) 3438 ) 3439 return False 3440 gmPathLab.delete_measurement_type(measurement_type = measurement_type['pk_test_type']) 3441 return True 3442 3443 #------------------------------------------------------------ 3444 def get_tooltip(test_type): 3445 return test_type.format() 3446 3447 #------------------------------------------------------------ 3448 def manage_aggregates(test_type): 3449 manage_meta_test_types(parent = parent) 3450 return False 3451 3452 #------------------------------------------------------------ 3453 def manage_panels_of_type(test_type): 3454 if test_type['loinc'] is None: 3455 return False 3456 all_panels = gmPathLab.get_test_panels(order_by = 'description') 3457 curr_panels = test_type.test_panels 3458 if curr_panels is None: 3459 curr_panels = [] 3460 panel_candidates = [ p for p in all_panels if p['pk_test_panel'] not in [ 3461 c_pnl['pk_test_panel'] for c_pnl in curr_panels 3462 ] ] 3463 picker = gmListWidgets.cItemPickerDlg(parent, -1, title = 'Panels with [%s]' % test_type['abbrev']) 3464 picker.set_columns(['Panels available'], ['Panels [%s] is to be on' % test_type['abbrev']]) 3465 picker.set_choices ( 3466 choices = [ u'%s (%s)' % (c['description'], gmTools.coalesce(c['comment'], '')) for c in panel_candidates ], 3467 data = panel_candidates 3468 ) 3469 picker.set_picks ( 3470 picks = [ u'%s (%s)' % (c['description'], gmTools.coalesce(c['comment'], '')) for c in curr_panels ], 3471 data = curr_panels 3472 ) 3473 exit_type = picker.ShowModal() 3474 if exit_type == wx.ID_CANCEL: 3475 return False 3476 3477 # add picked panels which aren't currently in the panel list 3478 panels2add = [ p for p in picker.picks if p['pk_test_panel'] not in [ 3479 c_pnl['pk_test_panel'] for c_pnl in curr_panels 3480 ] ] 3481 # remove unpicked panels off the current panel list 3482 panels2remove = [ p for p in curr_panels if p['pk_test_panel'] not in [ 3483 picked_pnl['pk_test_panel'] for picked_pnl in picker.picks 3484 ] ] 3485 for new_panel in panels2add: 3486 new_panel.add_loinc(test_type['loinc']) 3487 for stale_panel in panels2remove: 3488 stale_panel.remove_loinc(test_type['loinc']) 3489 3490 return True 3491 3492 #------------------------------------------------------------ 3493 def refresh(lctrl): 3494 mtypes = gmPathLab.get_measurement_types(order_by = 'name, abbrev') 3495 items = [ [ 3496 m['abbrev'], 3497 m['name'], 3498 gmTools.coalesce(m['reference_unit'], ''), 3499 gmTools.coalesce(m['loinc'], ''), 3500 gmTools.coalesce(m['comment_type'], ''), 3501 gmTools.coalesce(m['name_org'], '?'), 3502 gmTools.coalesce(m['comment_org'], ''), 3503 m['pk_test_type'] 3504 ] for m in mtypes ] 3505 lctrl.set_string_items(items) 3506 lctrl.set_data(mtypes) 3507 3508 #------------------------------------------------------------ 3509 gmListWidgets.get_choices_from_list ( 3510 parent = parent, 3511 caption = _('Measurement types.'), 3512 columns = [ _('Abbrev'), _('Name'), _('Unit'), _('LOINC'), _('Comment'), _('Org'), _('Comment'), '#' ], 3513 single_selection = True, 3514 refresh_callback = refresh, 3515 edit_callback = edit, 3516 new_callback = edit, 3517 delete_callback = delete, 3518 list_tooltip_callback = get_tooltip, 3519 left_extra_button = (_('%s &Aggregate') % gmTools.u_sum, _('Manage aggregations (%s) of tests into groups.') % gmTools.u_sum, manage_aggregates), 3520 middle_extra_button = (_('Select panels'), _('Select panels the focussed test type is to belong to.'), manage_panels_of_type) 3521 ) 3522 3523 #----------------------------------------------------------------
3524 -class cMeasurementTypePhraseWheel(gmPhraseWheel.cPhraseWheel):
3525
3526 - def __init__(self, *args, **kwargs):
3527 3528 query = """ 3529 SELECT DISTINCT ON (field_label) 3530 pk_test_type AS data, 3531 name 3532 || ' (' 3533 || coalesce ( 3534 (SELECT unit || ' @ ' || organization FROM clin.v_test_orgs c_vto WHERE c_vto.pk_test_org = c_vtt.pk_test_org), 3535 '%(in_house)s' 3536 ) 3537 || ')' 3538 AS field_label, 3539 name 3540 || ' (' 3541 || abbrev || ', ' 3542 || coalesce(abbrev_meta || ': ' || name_meta || ', ', '') 3543 || coalesce ( 3544 (SELECT unit || ' @ ' || organization FROM clin.v_test_orgs c_vto WHERE c_vto.pk_test_org = c_vtt.pk_test_org), 3545 '%(in_house)s' 3546 ) 3547 || ')' 3548 AS list_label 3549 FROM 3550 clin.v_test_types c_vtt 3551 WHERE 3552 abbrev_meta %%(fragment_condition)s 3553 OR 3554 name_meta %%(fragment_condition)s 3555 OR 3556 abbrev %%(fragment_condition)s 3557 OR 3558 name %%(fragment_condition)s 3559 ORDER BY field_label 3560 LIMIT 50""" % {'in_house': _('generic / in house lab')} 3561 3562 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 3563 mp.setThresholds(1, 2, 4) 3564 mp.word_separators = '[ \t:@]+' 3565 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 3566 self.matcher = mp 3567 self.SetToolTip(_('Select the type of measurement.')) 3568 self.selection_only = False
3569 3570 #------------------------------------------------------------
3571 - def _data2instance(self):
3572 if self.GetData() is None: 3573 return None 3574 3575 return gmPathLab.cMeasurementType(aPK_obj = self.GetData())
3576 3577 #---------------------------------------------------------------- 3578 from Gnumed.wxGladeWidgets import wxgMeasurementTypeEAPnl 3579
3580 -class cMeasurementTypeEAPnl(wxgMeasurementTypeEAPnl.wxgMeasurementTypeEAPnl, gmEditArea.cGenericEditAreaMixin):
3581
3582 - def __init__(self, *args, **kwargs):
3583 3584 try: 3585 data = kwargs['type'] 3586 del kwargs['type'] 3587 except KeyError: 3588 data = None 3589 3590 wxgMeasurementTypeEAPnl.wxgMeasurementTypeEAPnl.__init__(self, *args, **kwargs) 3591 gmEditArea.cGenericEditAreaMixin.__init__(self) 3592 self.mode = 'new' 3593 self.data = data 3594 if data is not None: 3595 self.mode = 'edit' 3596 3597 self.__init_ui()
3598 3599 #----------------------------------------------------------------
3600 - def __init_ui(self):
3601 3602 # name phraseweel 3603 query = """ 3604 select distinct on (name) 3605 pk, 3606 name 3607 from clin.test_type 3608 where 3609 name %(fragment_condition)s 3610 order by name 3611 limit 50""" 3612 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 3613 mp.setThresholds(1, 2, 4) 3614 self._PRW_name.matcher = mp 3615 self._PRW_name.selection_only = False 3616 self._PRW_name.add_callback_on_lose_focus(callback = self._on_name_lost_focus) 3617 3618 # abbreviation 3619 query = """ 3620 select distinct on (abbrev) 3621 pk, 3622 abbrev 3623 from clin.test_type 3624 where 3625 abbrev %(fragment_condition)s 3626 order by abbrev 3627 limit 50""" 3628 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 3629 mp.setThresholds(1, 2, 3) 3630 self._PRW_abbrev.matcher = mp 3631 self._PRW_abbrev.selection_only = False 3632 3633 # unit 3634 self._PRW_reference_unit.selection_only = False 3635 3636 # loinc 3637 mp = gmLOINC.cLOINCMatchProvider() 3638 mp.setThresholds(1, 2, 4) 3639 #mp.print_queries = True 3640 #mp.word_separators = '[ \t:@]+' 3641 self._PRW_loinc.matcher = mp 3642 self._PRW_loinc.selection_only = False 3643 self._PRW_loinc.add_callback_on_lose_focus(callback = self._on_loinc_lost_focus)
3644 3645 #----------------------------------------------------------------
3646 - def _on_name_lost_focus(self):
3647 3648 test = self._PRW_name.GetValue().strip() 3649 3650 if test == '': 3651 self._PRW_reference_unit.unset_context(context = 'test_name') 3652 return 3653 3654 self._PRW_reference_unit.set_context(context = 'test_name', val = test)
3655 3656 #----------------------------------------------------------------
3657 - def _on_loinc_lost_focus(self):
3658 loinc = self._PRW_loinc.GetData() 3659 3660 if loinc is None: 3661 self._TCTRL_loinc_info.SetValue('') 3662 self._PRW_reference_unit.unset_context(context = 'loinc') 3663 return 3664 3665 self._PRW_reference_unit.set_context(context = 'loinc', val = loinc) 3666 3667 info = gmLOINC.loinc2term(loinc = loinc) 3668 if len(info) == 0: 3669 self._TCTRL_loinc_info.SetValue('') 3670 return 3671 3672 self._TCTRL_loinc_info.SetValue(info[0])
3673 3674 #---------------------------------------------------------------- 3675 # generic Edit Area mixin API 3676 #----------------------------------------------------------------
3677 - def _valid_for_save(self):
3678 3679 has_errors = False 3680 for field in [self._PRW_name, self._PRW_abbrev, self._PRW_reference_unit]: 3681 if field.GetValue().strip() in ['', None]: 3682 has_errors = True 3683 field.display_as_valid(valid = False) 3684 else: 3685 field.display_as_valid(valid = True) 3686 field.Refresh() 3687 3688 return (not has_errors)
3689 3690 #----------------------------------------------------------------
3691 - def _save_as_new(self):
3692 3693 pk_org = self._PRW_test_org.GetData() 3694 if pk_org is None: 3695 pk_org = gmPathLab.create_test_org ( 3696 name = gmTools.none_if(self._PRW_test_org.GetValue().strip(), '') 3697 )['pk_test_org'] 3698 3699 tt = gmPathLab.create_measurement_type ( 3700 lab = pk_org, 3701 abbrev = self._PRW_abbrev.GetValue().strip(), 3702 name = self._PRW_name.GetValue().strip(), 3703 unit = gmTools.coalesce ( 3704 self._PRW_reference_unit.GetData(), 3705 self._PRW_reference_unit.GetValue() 3706 ).strip() 3707 ) 3708 if self._PRW_loinc.GetData() is not None: 3709 tt['loinc'] = gmTools.none_if(self._PRW_loinc.GetData().strip(), '') 3710 else: 3711 tt['loinc'] = gmTools.none_if(self._PRW_loinc.GetValue().strip(), '') 3712 tt['comment_type'] = gmTools.none_if(self._TCTRL_comment_type.GetValue().strip(), '') 3713 tt['pk_meta_test_type'] = self._PRW_meta_type.GetData() 3714 3715 tt.save() 3716 3717 self.data = tt 3718 3719 return True
3720 #----------------------------------------------------------------
3721 - def _save_as_update(self):
3722 3723 pk_org = self._PRW_test_org.GetData() 3724 if pk_org is None: 3725 pk_org = gmPathLab.create_test_org ( 3726 name = gmTools.none_if(self._PRW_test_org.GetValue().strip(), '') 3727 )['pk_test_org'] 3728 3729 self.data['pk_test_org'] = pk_org 3730 self.data['abbrev'] = self._PRW_abbrev.GetValue().strip() 3731 self.data['name'] = self._PRW_name.GetValue().strip() 3732 self.data['reference_unit'] = gmTools.coalesce ( 3733 self._PRW_reference_unit.GetData(), 3734 self._PRW_reference_unit.GetValue() 3735 ).strip() 3736 old_loinc = self.data['loinc'] 3737 if self._PRW_loinc.GetData() is not None: 3738 self.data['loinc'] = gmTools.none_if(self._PRW_loinc.GetData().strip(), '') 3739 else: 3740 self.data['loinc'] = gmTools.none_if(self._PRW_loinc.GetValue().strip(), '') 3741 new_loinc = self.data['loinc'] 3742 self.data['comment_type'] = gmTools.none_if(self._TCTRL_comment_type.GetValue().strip(), '') 3743 self.data['pk_meta_test_type'] = self._PRW_meta_type.GetData() 3744 self.data.save() 3745 3746 # was it, AND can it be, on any panel ? 3747 if None not in [old_loinc, new_loinc]: 3748 # would it risk being dropped from any panel ? 3749 if new_loinc != old_loinc: 3750 for panel in gmPathLab.get_test_panels(loincs = [old_loinc]): 3751 pnl_loincs = panel.included_loincs 3752 if new_loinc not in pnl_loincs: 3753 pnl_loincs.append(new_loinc) 3754 panel.included_loincs = pnl_loincs 3755 # do not remove old_loinc as it may sit on another 3756 # test type which we haven't removed it from yet 3757 3758 return True
3759 3760 #----------------------------------------------------------------
3761 - def _refresh_as_new(self):
3762 self._PRW_name.SetText('', None, True) 3763 self._on_name_lost_focus() 3764 self._PRW_abbrev.SetText('', None, True) 3765 self._PRW_reference_unit.SetText('', None, True) 3766 self._PRW_loinc.SetText('', None, True) 3767 self._on_loinc_lost_focus() 3768 self._TCTRL_comment_type.SetValue('') 3769 self._PRW_test_org.SetText('', None, True) 3770 self._PRW_meta_type.SetText('', None, True) 3771 3772 self._PRW_name.SetFocus()
3773 #----------------------------------------------------------------
3774 - def _refresh_from_existing(self):
3775 self._PRW_name.SetText(self.data['name'], self.data['name'], True) 3776 self._on_name_lost_focus() 3777 self._PRW_abbrev.SetText(self.data['abbrev'], self.data['abbrev'], True) 3778 self._PRW_reference_unit.SetText ( 3779 gmTools.coalesce(self.data['reference_unit'], ''), 3780 self.data['reference_unit'], 3781 True 3782 ) 3783 self._PRW_loinc.SetText ( 3784 gmTools.coalesce(self.data['loinc'], ''), 3785 self.data['loinc'], 3786 True 3787 ) 3788 self._on_loinc_lost_focus() 3789 self._TCTRL_comment_type.SetValue(gmTools.coalesce(self.data['comment_type'], '')) 3790 self._PRW_test_org.SetText ( 3791 gmTools.coalesce(self.data['pk_test_org'], '', self.data['name_org']), 3792 self.data['pk_test_org'], 3793 True 3794 ) 3795 if self.data['pk_meta_test_type'] is None: 3796 self._PRW_meta_type.SetText('', None, True) 3797 else: 3798 self._PRW_meta_type.SetText('%s: %s' % (self.data['abbrev_meta'], self.data['name_meta']), self.data['pk_meta_test_type'], True) 3799 3800 self._PRW_name.SetFocus()
3801 #----------------------------------------------------------------
3803 self._refresh_as_new() 3804 self._PRW_test_org.SetText ( 3805 gmTools.coalesce(self.data['pk_test_org'], '', self.data['name_org']), 3806 self.data['pk_test_org'], 3807 True 3808 ) 3809 self._PRW_name.SetFocus()
3810 3811 #================================================================ 3812 _SQL_units_from_test_results = """ 3813 -- via clin.v_test_results.pk_type (for types already used in results) 3814 SELECT 3815 val_unit AS data, 3816 val_unit AS field_label, 3817 val_unit || ' (' || name_tt || ')' AS list_label, 3818 1 AS rank 3819 FROM 3820 clin.v_test_results 3821 WHERE 3822 ( 3823 val_unit %(fragment_condition)s 3824 OR 3825 reference_unit %(fragment_condition)s 3826 ) 3827 %(ctxt_type_pk)s 3828 %(ctxt_test_name)s 3829 """ 3830 3831 _SQL_units_from_test_types = """ 3832 -- via clin.test_type (for types not yet used in results) 3833 SELECT 3834 reference_unit AS data, 3835 reference_unit AS field_label, 3836 reference_unit || ' (' || name || ')' AS list_label, 3837 2 AS rank 3838 FROM 3839 clin.test_type 3840 WHERE 3841 reference_unit %(fragment_condition)s 3842 %(ctxt_ctt)s 3843 """ 3844 3845 _SQL_units_from_loinc_ipcc = """ 3846 -- via ref.loinc.ipcc_units 3847 SELECT 3848 ipcc_units AS data, 3849 ipcc_units AS field_label, 3850 ipcc_units || ' (LOINC.ipcc: ' || term || ')' AS list_label, 3851 3 AS rank 3852 FROM 3853 ref.loinc 3854 WHERE 3855 ipcc_units %(fragment_condition)s 3856 %(ctxt_loinc)s 3857 %(ctxt_loinc_term)s 3858 """ 3859 3860 _SQL_units_from_loinc_submitted = """ 3861 -- via ref.loinc.submitted_units 3862 SELECT 3863 submitted_units AS data, 3864 submitted_units AS field_label, 3865 submitted_units || ' (LOINC.submitted:' || term || ')' AS list_label, 3866 3 AS rank 3867 FROM 3868 ref.loinc 3869 WHERE 3870 submitted_units %(fragment_condition)s 3871 %(ctxt_loinc)s 3872 %(ctxt_loinc_term)s 3873 """ 3874 3875 _SQL_units_from_loinc_example = """ 3876 -- via ref.loinc.example_units 3877 SELECT 3878 example_units AS data, 3879 example_units AS field_label, 3880 example_units || ' (LOINC.example: ' || term || ')' AS list_label, 3881 3 AS rank 3882 FROM 3883 ref.loinc 3884 WHERE 3885 example_units %(fragment_condition)s 3886 %(ctxt_loinc)s 3887 %(ctxt_loinc_term)s 3888 """ 3889 3890 _SQL_units_from_substance_doses = """ 3891 -- via ref.v_substance_doses.unit 3892 SELECT 3893 unit AS data, 3894 unit AS field_label, 3895 unit || ' (' || substance || ')' AS list_label, 3896 2 AS rank 3897 FROM 3898 ref.v_substance_doses 3899 WHERE 3900 unit %(fragment_condition)s 3901 %(ctxt_substance)s 3902 """ 3903 3904 _SQL_units_from_substance_doses2 = """ 3905 -- via ref.v_substance_doses.dose_unit 3906 SELECT 3907 dose_unit AS data, 3908 dose_unit AS field_label, 3909 dose_unit || ' (' || substance || ')' AS list_label, 3910 2 AS rank 3911 FROM 3912 ref.v_substance_doses 3913 WHERE 3914 dose_unit %(fragment_condition)s 3915 %(ctxt_substance)s 3916 """ 3917 3918 #----------------------------------------------------------------
3919 -class cUnitPhraseWheel(gmPhraseWheel.cPhraseWheel):
3920
3921 - def __init__(self, *args, **kwargs):
3922 3923 query = """ 3924 SELECT DISTINCT ON (data) 3925 data, 3926 field_label, 3927 list_label 3928 FROM ( 3929 3930 SELECT 3931 data, 3932 field_label, 3933 list_label, 3934 rank 3935 FROM ( 3936 (%s) UNION ALL 3937 (%s) UNION ALL 3938 (%s) UNION ALL 3939 (%s) UNION ALL 3940 (%s) UNION ALL 3941 (%s) UNION ALL 3942 (%s) 3943 ) AS all_matching_units 3944 WHERE data IS NOT NULL 3945 ORDER BY rank, list_label 3946 3947 ) AS ranked_matching_units 3948 LIMIT 50""" % ( 3949 _SQL_units_from_test_results, 3950 _SQL_units_from_test_types, 3951 _SQL_units_from_loinc_ipcc, 3952 _SQL_units_from_loinc_submitted, 3953 _SQL_units_from_loinc_example, 3954 _SQL_units_from_substance_doses, 3955 _SQL_units_from_substance_doses2 3956 ) 3957 3958 ctxt = { 3959 'ctxt_type_pk': { 3960 'where_part': 'AND pk_test_type = %(pk_type)s', 3961 'placeholder': 'pk_type' 3962 }, 3963 'ctxt_test_name': { 3964 'where_part': 'AND %(test_name)s IN (name_tt, name_meta, abbrev_meta)', 3965 'placeholder': 'test_name' 3966 }, 3967 'ctxt_ctt': { 3968 'where_part': 'AND %(test_name)s IN (name, abbrev)', 3969 'placeholder': 'test_name' 3970 }, 3971 'ctxt_loinc': { 3972 'where_part': 'AND code = %(loinc)s', 3973 'placeholder': 'loinc' 3974 }, 3975 'ctxt_loinc_term': { 3976 'where_part': 'AND term ~* %(test_name)s', 3977 'placeholder': 'test_name' 3978 }, 3979 'ctxt_substance': { 3980 'where_part': 'AND description ~* %(substance)s', 3981 'placeholder': 'substance' 3982 } 3983 } 3984 3985 mp = gmMatchProvider.cMatchProvider_SQL2(queries = query, context = ctxt) 3986 mp.setThresholds(1, 2, 4) 3987 #mp.print_queries = True 3988 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 3989 self.matcher = mp 3990 self.SetToolTip(_('Select the desired unit for the amount or measurement.')) 3991 self.selection_only = False 3992 self.phrase_separators = '[;|]+'
3993 3994 #================================================================ 3995 3996 #================================================================
3997 -class cTestResultIndicatorPhraseWheel(gmPhraseWheel.cPhraseWheel):
3998
3999 - def __init__(self, *args, **kwargs):
4000 4001 query = """ 4002 select distinct abnormality_indicator, 4003 abnormality_indicator, abnormality_indicator 4004 from clin.v_test_results 4005 where 4006 abnormality_indicator %(fragment_condition)s 4007 order by abnormality_indicator 4008 limit 25""" 4009 4010 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 4011 mp.setThresholds(1, 1, 2) 4012 mp.ignored_chars = "[.'\\\[\]#$%_]+" + '"' 4013 mp.word_separators = '[ \t&:]+' 4014 gmPhraseWheel.cPhraseWheel.__init__ ( 4015 self, 4016 *args, 4017 **kwargs 4018 ) 4019 self.matcher = mp 4020 self.SetToolTip(_('Select an indicator for the level of abnormality.')) 4021 self.selection_only = False
4022 4023 #================================================================ 4024 # measurement org widgets / functions 4025 #----------------------------------------------------------------
4026 -def edit_measurement_org(parent=None, org=None):
4027 ea = cMeasurementOrgEAPnl(parent, -1) 4028 ea.data = org 4029 ea.mode = gmTools.coalesce(org, 'new', 'edit') 4030 dlg = gmEditArea.cGenericEditAreaDlg2(parent, -1, edit_area = ea) 4031 dlg.SetTitle(gmTools.coalesce(org, _('Adding new diagnostic org'), _('Editing diagnostic org'))) 4032 if dlg.ShowModal() == wx.ID_OK: 4033 dlg.DestroyLater() 4034 return True 4035 dlg.DestroyLater() 4036 return False
4037 #----------------------------------------------------------------
4038 -def manage_measurement_orgs(parent=None, msg=None):
4039 4040 if parent is None: 4041 parent = wx.GetApp().GetTopWindow() 4042 4043 #------------------------------------------------------------ 4044 def edit(org=None): 4045 return edit_measurement_org(parent = parent, org = org)
4046 #------------------------------------------------------------ 4047 def refresh(lctrl): 4048 orgs = gmPathLab.get_test_orgs() 4049 lctrl.set_string_items ([ 4050 (o['unit'], o['organization'], gmTools.coalesce(o['test_org_contact'], ''), gmTools.coalesce(o['comment'], ''), o['pk_test_org']) 4051 for o in orgs 4052 ]) 4053 lctrl.set_data(orgs) 4054 #------------------------------------------------------------ 4055 def delete(test_org): 4056 gmPathLab.delete_test_org(test_org = test_org['pk_test_org']) 4057 return True 4058 #------------------------------------------------------------ 4059 if msg is None: 4060 msg = _('\nThese are the diagnostic orgs (path labs etc) currently defined in GNUmed.\n\n') 4061 4062 return gmListWidgets.get_choices_from_list ( 4063 parent = parent, 4064 msg = msg, 4065 caption = _('Showing diagnostic orgs.'), 4066 columns = [_('Name'), _('Organization'), _('Contact'), _('Comment'), '#'], 4067 single_selection = True, 4068 refresh_callback = refresh, 4069 edit_callback = edit, 4070 new_callback = edit, 4071 delete_callback = delete 4072 ) 4073 4074 #---------------------------------------------------------------- 4075 from Gnumed.wxGladeWidgets import wxgMeasurementOrgEAPnl 4076
4077 -class cMeasurementOrgEAPnl(wxgMeasurementOrgEAPnl.wxgMeasurementOrgEAPnl, gmEditArea.cGenericEditAreaMixin):
4078
4079 - def __init__(self, *args, **kwargs):
4080 4081 try: 4082 data = kwargs['org'] 4083 del kwargs['org'] 4084 except KeyError: 4085 data = None 4086 4087 wxgMeasurementOrgEAPnl.wxgMeasurementOrgEAPnl.__init__(self, *args, **kwargs) 4088 gmEditArea.cGenericEditAreaMixin.__init__(self) 4089 4090 self.mode = 'new' 4091 self.data = data 4092 if data is not None: 4093 self.mode = 'edit'
4094 4095 #self.__init_ui() 4096 #---------------------------------------------------------------- 4097 # def __init_ui(self): 4098 # # adjust phrasewheels etc 4099 #---------------------------------------------------------------- 4100 # generic Edit Area mixin API 4101 #----------------------------------------------------------------
4102 - def _valid_for_save(self):
4103 has_errors = False 4104 if self._PRW_org_unit.GetData() is None: 4105 if self._PRW_org_unit.GetValue().strip() == '': 4106 has_errors = True 4107 self._PRW_org_unit.display_as_valid(valid = False) 4108 else: 4109 self._PRW_org_unit.display_as_valid(valid = True) 4110 else: 4111 self._PRW_org_unit.display_as_valid(valid = True) 4112 4113 return (not has_errors)
4114 #----------------------------------------------------------------
4115 - def _save_as_new(self):
4116 data = gmPathLab.create_test_org ( 4117 name = self._PRW_org_unit.GetValue().strip(), 4118 comment = self._TCTRL_comment.GetValue().strip(), 4119 pk_org_unit = self._PRW_org_unit.GetData() 4120 ) 4121 data['test_org_contact'] = self._TCTRL_contact.GetValue().strip() 4122 data.save() 4123 self.data = data 4124 return True
4125 #----------------------------------------------------------------
4126 - def _save_as_update(self):
4127 # get or create the org unit 4128 name = self._PRW_org_unit.GetValue().strip() 4129 org = gmOrganization.org_exists(organization = name) 4130 if org is None: 4131 org = gmOrganization.create_org ( 4132 organization = name, 4133 category = 'Laboratory' 4134 ) 4135 org_unit = gmOrganization.create_org_unit ( 4136 pk_organization = org['pk_org'], 4137 unit = name 4138 ) 4139 # update test_org fields 4140 self.data['pk_org_unit'] = org_unit['pk_org_unit'] 4141 self.data['test_org_contact'] = self._TCTRL_contact.GetValue().strip() 4142 self.data['comment'] = self._TCTRL_comment.GetValue().strip() 4143 self.data.save() 4144 return True
4145 #----------------------------------------------------------------
4146 - def _refresh_as_new(self):
4147 self._PRW_org_unit.SetText(value = '', data = None) 4148 self._TCTRL_contact.SetValue('') 4149 self._TCTRL_comment.SetValue('')
4150 #----------------------------------------------------------------
4151 - def _refresh_from_existing(self):
4152 self._PRW_org_unit.SetText(value = self.data['unit'], data = self.data['pk_org_unit']) 4153 self._TCTRL_contact.SetValue(gmTools.coalesce(self.data['test_org_contact'], '')) 4154 self._TCTRL_comment.SetValue(gmTools.coalesce(self.data['comment'], ''))
4155 #----------------------------------------------------------------
4157 self._refresh_as_new()
4158 #----------------------------------------------------------------
4159 - def _on_manage_orgs_button_pressed(self, event):
4161 4162 #----------------------------------------------------------------
4163 -class cMeasurementOrgPhraseWheel(gmPhraseWheel.cPhraseWheel):
4164
4165 - def __init__(self, *args, **kwargs):
4166 4167 query = """ 4168 SELECT DISTINCT ON (list_label) 4169 pk_test_org AS data, 4170 unit || ' (' || organization || ')' AS field_label, 4171 unit || ' @ ' || organization AS list_label 4172 FROM clin.v_test_orgs 4173 WHERE 4174 unit %(fragment_condition)s 4175 OR 4176 organization %(fragment_condition)s 4177 ORDER BY list_label 4178 LIMIT 50""" 4179 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 4180 mp.setThresholds(1, 2, 4) 4181 #mp.word_separators = '[ \t:@]+' 4182 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 4183 self.matcher = mp 4184 self.SetToolTip(_('The name of the path lab/diagnostic organisation.')) 4185 self.selection_only = False
4186 #------------------------------------------------------------
4187 - def _create_data(self):
4188 if self.GetData() is not None: 4189 _log.debug('data already set, not creating') 4190 return 4191 4192 if self.GetValue().strip() == '': 4193 _log.debug('cannot create new lab, missing name') 4194 return 4195 4196 lab = gmPathLab.create_test_org(name = self.GetValue().strip()) 4197 self.SetText(value = lab['unit'], data = lab['pk_test_org']) 4198 return
4199 #------------------------------------------------------------
4200 - def _data2instance(self):
4201 return gmPathLab.cTestOrg(aPK_obj = self.GetData())
4202 4203 #================================================================ 4204 # Meta test type widgets 4205 #----------------------------------------------------------------
4206 -def edit_meta_test_type(parent=None, meta_test_type=None):
4207 ea = cMetaTestTypeEAPnl(parent, -1) 4208 ea.data = meta_test_type 4209 ea.mode = gmTools.coalesce(meta_test_type, 'new', 'edit') 4210 dlg = gmEditArea.cGenericEditAreaDlg2 ( 4211 parent = parent, 4212 id = -1, 4213 edit_area = ea, 4214 single_entry = gmTools.bool2subst((meta_test_type is None), False, True) 4215 ) 4216 dlg.SetTitle(gmTools.coalesce(meta_test_type, _('Adding new meta test type'), _('Editing meta test type'))) 4217 if dlg.ShowModal() == wx.ID_OK: 4218 dlg.DestroyLater() 4219 return True 4220 dlg.DestroyLater() 4221 return False
4222 4223 #----------------------------------------------------------------
4224 -def manage_meta_test_types(parent=None):
4225 4226 if parent is None: 4227 parent = wx.GetApp().GetTopWindow() 4228 4229 #------------------------------------------------------------ 4230 def edit(meta_test_type=None): 4231 return edit_meta_test_type(parent = parent, meta_test_type = meta_test_type)
4232 #------------------------------------------------------------ 4233 def delete(meta_test_type): 4234 gmPathLab.delete_meta_type(meta_type = meta_test_type['pk']) 4235 return True 4236 #---------------------------------------- 4237 def get_tooltip(data): 4238 if data is None: 4239 return None 4240 return data.format(with_tests = True) 4241 #------------------------------------------------------------ 4242 def refresh(lctrl): 4243 mtts = gmPathLab.get_meta_test_types() 4244 items = [ [ 4245 m['abbrev'], 4246 m['name'], 4247 gmTools.coalesce(m['loinc'], ''), 4248 gmTools.coalesce(m['comment'], ''), 4249 m['pk'] 4250 ] for m in mtts ] 4251 lctrl.set_string_items(items) 4252 lctrl.set_data(mtts) 4253 #---------------------------------------- 4254 4255 msg = _( 4256 '\n' 4257 'These are the meta test types currently defined in GNUmed.\n' 4258 '\n' 4259 'Meta test types allow you to aggregate several actual test types used\n' 4260 'by pathology labs into one logical type.\n' 4261 '\n' 4262 'This is useful for grouping together results of tests which come under\n' 4263 'different names but really are the same thing. This often happens when\n' 4264 'you switch labs or the lab starts using another test method.\n' 4265 ) 4266 4267 gmListWidgets.get_choices_from_list ( 4268 parent = parent, 4269 msg = msg, 4270 caption = _('Showing meta test types.'), 4271 columns = [_('Abbrev'), _('Name'), _('LOINC'), _('Comment'), '#'], 4272 single_selection = True, 4273 list_tooltip_callback = get_tooltip, 4274 edit_callback = edit, 4275 new_callback = edit, 4276 delete_callback = delete, 4277 refresh_callback = refresh 4278 ) 4279 4280 #----------------------------------------------------------------
4281 -class cMetaTestTypePRW(gmPhraseWheel.cPhraseWheel):
4282
4283 - def __init__(self, *args, **kwargs):
4284 4285 query = """ 4286 SELECT DISTINCT ON (field_label) 4287 c_mtt.pk 4288 AS data, 4289 c_mtt.abbrev || ': ' || name 4290 AS field_label, 4291 c_mtt.abbrev || ': ' || name 4292 || coalesce ( 4293 ' (' || c_mtt.comment || ')', 4294 '' 4295 ) 4296 || coalesce ( 4297 ', LOINC: ' || c_mtt.loinc, 4298 '' 4299 ) 4300 AS list_label 4301 FROM 4302 clin.meta_test_type c_mtt 4303 WHERE 4304 abbrev %(fragment_condition)s 4305 OR 4306 name %(fragment_condition)s 4307 OR 4308 loinc %(fragment_condition)s 4309 ORDER BY field_label 4310 LIMIT 50""" 4311 4312 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 4313 mp.setThresholds(1, 2, 4) 4314 mp.word_separators = '[ \t:@]+' 4315 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 4316 self.matcher = mp 4317 self.SetToolTip(_('Select the meta test type.')) 4318 self.selection_only = True
4319 #------------------------------------------------------------
4320 - def _data2instance(self):
4321 if self.GetData() is None: 4322 return None 4323 4324 return gmPathLab.cMetaTestType(aPK_obj = self.GetData())
4325 4326 #---------------------------------------------------------------- 4327 from Gnumed.wxGladeWidgets import wxgMetaTestTypeEAPnl 4328
4329 -class cMetaTestTypeEAPnl(wxgMetaTestTypeEAPnl.wxgMetaTestTypeEAPnl, gmEditArea.cGenericEditAreaMixin):
4330
4331 - def __init__(self, *args, **kwargs):
4332 4333 try: 4334 data = kwargs['meta_test_type'] 4335 del kwargs['meta_test_type'] 4336 except KeyError: 4337 data = None 4338 4339 wxgMetaTestTypeEAPnl.wxgMetaTestTypeEAPnl.__init__(self, *args, **kwargs) 4340 gmEditArea.cGenericEditAreaMixin.__init__(self) 4341 4342 # Code using this mixin should set mode and data 4343 # after instantiating the class: 4344 self.mode = 'new' 4345 self.data = data 4346 if data is not None: 4347 self.mode = 'edit' 4348 4349 self.__init_ui()
4350 #----------------------------------------------------------------
4351 - def __init_ui(self):
4352 # loinc 4353 mp = gmLOINC.cLOINCMatchProvider() 4354 mp.setThresholds(1, 2, 4) 4355 #mp.print_queries = True 4356 #mp.word_separators = '[ \t:@]+' 4357 self._PRW_loinc.matcher = mp 4358 self._PRW_loinc.selection_only = False 4359 self._PRW_loinc.add_callback_on_lose_focus(callback = self._on_loinc_lost_focus)
4360 4361 #---------------------------------------------------------------- 4362 # generic Edit Area mixin API 4363 #----------------------------------------------------------------
4364 - def _valid_for_save(self):
4365 4366 validity = True 4367 4368 if self._PRW_abbreviation.GetValue().strip() == '': 4369 validity = False 4370 self._PRW_abbreviation.display_as_valid(False) 4371 self.StatusText = _('Missing abbreviation for meta test type.') 4372 self._PRW_abbreviation.SetFocus() 4373 else: 4374 self._PRW_abbreviation.display_as_valid(True) 4375 4376 if self._PRW_name.GetValue().strip() == '': 4377 validity = False 4378 self._PRW_name.display_as_valid(False) 4379 self.StatusText = _('Missing name for meta test type.') 4380 self._PRW_name.SetFocus() 4381 else: 4382 self._PRW_name.display_as_valid(True) 4383 4384 return validity
4385 #----------------------------------------------------------------
4386 - def _save_as_new(self):
4387 4388 # save the data as a new instance 4389 data = gmPathLab.create_meta_type ( 4390 name = self._PRW_name.GetValue().strip(), 4391 abbreviation = self._PRW_abbreviation.GetValue().strip(), 4392 return_existing = False 4393 ) 4394 if data is None: 4395 self.StatusText = _('This meta test type already exists.') 4396 return False 4397 data['loinc'] = self._PRW_loinc.GetData() 4398 data['comment'] = self._TCTRL_comment.GetValue().strip() 4399 data.save() 4400 self.data = data 4401 return True
4402 #----------------------------------------------------------------
4403 - def _save_as_update(self):
4404 self.data['name'] = self._PRW_name.GetValue().strip() 4405 self.data['abbrev'] = self._PRW_abbreviation.GetValue().strip() 4406 self.data['loinc'] = self._PRW_loinc.GetData() 4407 self.data['comment'] = self._TCTRL_comment.GetValue().strip() 4408 self.data.save() 4409 return True
4410 #----------------------------------------------------------------
4411 - def _refresh_as_new(self):
4412 self._PRW_name.SetText('', None) 4413 self._PRW_abbreviation.SetText('', None) 4414 self._PRW_loinc.SetText('', None) 4415 self._TCTRL_loinc_info.SetValue('') 4416 self._TCTRL_comment.SetValue('') 4417 self._LBL_member_detail.SetLabel('') 4418 4419 self._PRW_name.SetFocus()
4420 #----------------------------------------------------------------
4422 self._refresh_as_new()
4423 #----------------------------------------------------------------
4424 - def _refresh_from_existing(self):
4425 self._PRW_name.SetText(self.data['name'], self.data['pk']) 4426 self._PRW_abbreviation.SetText(self.data['abbrev'], self.data['abbrev']) 4427 self._PRW_loinc.SetText(gmTools.coalesce(self.data['loinc'], ''), self.data['loinc']) 4428 self.__refresh_loinc_info() 4429 self._TCTRL_comment.SetValue(gmTools.coalesce(self.data['comment'], '')) 4430 self.__refresh_members() 4431 4432 self._PRW_name.SetFocus()
4433 #---------------------------------------------------------------- 4434 # event handlers 4435 #----------------------------------------------------------------
4436 - def _on_loinc_lost_focus(self):
4437 self.__refresh_loinc_info()
4438 #---------------------------------------------------------------- 4439 # internal helpers 4440 #----------------------------------------------------------------
4441 - def __refresh_loinc_info(self):
4442 loinc = self._PRW_loinc.GetData() 4443 4444 if loinc is None: 4445 self._TCTRL_loinc_info.SetValue('') 4446 return 4447 4448 info = gmLOINC.loinc2term(loinc = loinc) 4449 if len(info) == 0: 4450 self._TCTRL_loinc_info.SetValue('') 4451 return 4452 4453 self._TCTRL_loinc_info.SetValue(info[0])
4454 #----------------------------------------------------------------
4455 - def __refresh_members(self):
4456 if self.data is None: 4457 self._LBL_member_detail.SetLabel('') 4458 return 4459 4460 types = self.data.included_test_types 4461 if len(types) == 0: 4462 self._LBL_member_detail.SetLabel('') 4463 return 4464 4465 lines = [] 4466 for tt in types: 4467 lines.append('%s (%s%s) [#%s] @ %s' % ( 4468 tt['name'], 4469 tt['abbrev'], 4470 gmTools.coalesce(tt['loinc'], '', ', LOINC: %s'), 4471 tt['pk_test_type'], 4472 tt['name_org'] 4473 )) 4474 self._LBL_member_detail.SetLabel('\n'.join(lines))
4475 4476 #================================================================ 4477 # test panel handling 4478 #================================================================
4479 -def edit_test_panel(parent=None, test_panel=None):
4480 ea = cTestPanelEAPnl(parent, -1) 4481 ea.data = test_panel 4482 ea.mode = gmTools.coalesce(test_panel, 'new', 'edit') 4483 dlg = gmEditArea.cGenericEditAreaDlg2 ( 4484 parent = parent, 4485 id = -1, 4486 edit_area = ea, 4487 single_entry = gmTools.bool2subst((test_panel is None), False, True) 4488 ) 4489 dlg.SetTitle(gmTools.coalesce(test_panel, _('Adding new test panel'), _('Editing test panel'))) 4490 if dlg.ShowModal() == wx.ID_OK: 4491 dlg.DestroyLater() 4492 return True 4493 dlg.DestroyLater() 4494 return False
4495 4496 #----------------------------------------------------------------
4497 -def manage_test_panels(parent=None):
4498 4499 if parent is None: 4500 parent = wx.GetApp().GetTopWindow() 4501 4502 #------------------------------------------------------------ 4503 def edit(test_panel=None): 4504 return edit_test_panel(parent = parent, test_panel = test_panel)
4505 #------------------------------------------------------------ 4506 def delete(test_panel): 4507 gmPathLab.delete_test_panel(pk = test_panel['pk_test_panel']) 4508 return True 4509 #------------------------------------------------------------ 4510 def get_tooltip(test_panel): 4511 return test_panel.format() 4512 #------------------------------------------------------------ 4513 def refresh(lctrl): 4514 panels = gmPathLab.get_test_panels(order_by = 'description') 4515 items = [ [ 4516 p['description'], 4517 gmTools.coalesce(p['comment'], ''), 4518 p['pk_test_panel'] 4519 ] for p in panels ] 4520 lctrl.set_string_items(items) 4521 lctrl.set_data(panels) 4522 #------------------------------------------------------------ 4523 gmListWidgets.get_choices_from_list ( 4524 parent = parent, 4525 caption = 'GNUmed: ' + _('Test panels list'), 4526 columns = [ _('Name'), _('Comment'), '#' ], 4527 single_selection = True, 4528 refresh_callback = refresh, 4529 edit_callback = edit, 4530 new_callback = edit, 4531 delete_callback = delete, 4532 list_tooltip_callback = get_tooltip 4533 ) 4534 4535 #----------------------------------------------------------------
4536 -class cTestPanelPRW(gmPhraseWheel.cPhraseWheel):
4537
4538 - def __init__(self, *args, **kwargs):
4539 query = """ 4540 SELECT 4541 pk_test_panel 4542 AS data, 4543 description 4544 AS field_label, 4545 description 4546 AS list_label 4547 FROM 4548 clin.v_test_panels 4549 WHERE 4550 description %(fragment_condition)s 4551 ORDER BY field_label 4552 LIMIT 30""" 4553 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 4554 mp.setThresholds(1, 2, 4) 4555 #mp.word_separators = '[ \t:@]+' 4556 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 4557 self.matcher = mp 4558 self.SetToolTip(_('Select a test panel.')) 4559 self.selection_only = True
4560 #------------------------------------------------------------
4561 - def _data2instance(self):
4562 if self.GetData() is None: 4563 return None 4564 return gmPathLab.cTestPanel(aPK_obj = self.GetData())
4565 #------------------------------------------------------------
4566 - def _get_data_tooltip(self):
4567 if self.GetData() is None: 4568 return None 4569 return gmPathLab.cTestPanel(aPK_obj = self.GetData()).format()
4570 4571 #==================================================================== 4572 from Gnumed.wxGladeWidgets import wxgTestPanelEAPnl 4573
4574 -class cTestPanelEAPnl(wxgTestPanelEAPnl.wxgTestPanelEAPnl, gmEditArea.cGenericEditAreaMixin):
4575
4576 - def __init__(self, *args, **kwargs):
4577 4578 try: 4579 data = kwargs['panel'] 4580 del kwargs['panel'] 4581 except KeyError: 4582 data = None 4583 4584 wxgTestPanelEAPnl.wxgTestPanelEAPnl.__init__(self, *args, **kwargs) 4585 gmEditArea.cGenericEditAreaMixin.__init__(self) 4586 4587 self.__loincs = None 4588 4589 self.mode = 'new' 4590 self.data = data 4591 if data is not None: 4592 self.mode = 'edit' 4593 4594 self.__init_ui()
4595 4596 #----------------------------------------------------------------
4597 - def __init_ui(self):
4598 self._LCTRL_loincs.set_columns([_('LOINC'), _('Term'), _('Units')]) 4599 self._LCTRL_loincs.set_column_widths(widths = [wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE]) 4600 #self._LCTRL_loincs.set_resize_column(column = 2) 4601 self._LCTRL_loincs.delete_callback = self._remove_loincs_from_list 4602 self.__refresh_loinc_list() 4603 4604 self._PRW_loinc.final_regex = r'.*' 4605 self._PRW_loinc.add_callback_on_selection(callback = self._on_loinc_selected)
4606 4607 #----------------------------------------------------------------
4608 - def __refresh_loinc_list(self):
4609 self._LCTRL_loincs.remove_items_safely() 4610 if self.__loincs is None: 4611 if self.data is None: 4612 return 4613 self.__loincs = self.data['loincs'] 4614 4615 items = [] 4616 for loinc in self.__loincs: 4617 loinc_detail = gmLOINC.loinc2data(loinc = loinc) 4618 if loinc_detail is None: 4619 # check for test type with this pseudo loinc 4620 ttypes = gmPathLab.get_measurement_types(loincs = [loinc]) 4621 if len(ttypes) == 0: 4622 items.append([loinc, _('LOINC not found'), '']) 4623 else: 4624 for tt in ttypes: 4625 items.append([loinc, _('not a LOINC') + u'; %(name)s @ %(name_org)s [#%(pk_test_type)s]' % tt, '']) 4626 continue 4627 items.append ([ 4628 loinc, 4629 loinc_detail['term'], 4630 gmTools.coalesce(loinc_detail['example_units'], '', '%s') 4631 ]) 4632 4633 self._LCTRL_loincs.set_string_items(items) 4634 self._LCTRL_loincs.set_column_widths()
4635 4636 #---------------------------------------------------------------- 4637 # generic Edit Area mixin API 4638 #----------------------------------------------------------------
4639 - def _valid_for_save(self):
4640 validity = True 4641 4642 if self.__loincs is None: 4643 if self.data is not None: 4644 self.__loincs = self.data['loincs'] 4645 4646 if self.__loincs is None: 4647 # not fatal despite panel being useless 4648 self.StatusText = _('No LOINC codes selected.') 4649 self._PRW_loinc.SetFocus() 4650 4651 if self._TCTRL_description.GetValue().strip() == '': 4652 validity = False 4653 self.display_tctrl_as_valid(tctrl = self._TCTRL_description, valid = False) 4654 self._TCTRL_description.SetFocus() 4655 else: 4656 self.display_tctrl_as_valid(tctrl = self._TCTRL_description, valid = True) 4657 4658 return validity
4659 4660 #----------------------------------------------------------------
4661 - def _save_as_new(self):
4662 data = gmPathLab.create_test_panel(description = self._TCTRL_description.GetValue().strip()) 4663 data['comment'] = self._TCTRL_comment.GetValue().strip() 4664 data.save() 4665 if self.__loincs is not None: 4666 data.included_loincs = self.__loincs 4667 self.data = data 4668 return True
4669 4670 #----------------------------------------------------------------
4671 - def _save_as_update(self):
4672 self.data['description'] = self._TCTRL_description.GetValue().strip() 4673 self.data['comment'] = self._TCTRL_comment.GetValue().strip() 4674 self.data.save() 4675 if self.__loincs is not None: 4676 self.data.included_loincs = self.__loincs 4677 return True
4678 4679 #----------------------------------------------------------------
4680 - def _refresh_as_new(self):
4681 self._TCTRL_description.SetValue('') 4682 self._TCTRL_comment.SetValue('') 4683 self._PRW_loinc.SetText('', None) 4684 self._LBL_loinc.SetLabel('') 4685 self.__loincs = None 4686 self.__refresh_loinc_list() 4687 4688 self._TCTRL_description.SetFocus()
4689 4690 #----------------------------------------------------------------
4692 self._refresh_as_new()
4693 4694 #----------------------------------------------------------------
4695 - def _refresh_from_existing(self):
4696 self._TCTRL_description.SetValue(self.data['description']) 4697 self._TCTRL_comment.SetValue(gmTools.coalesce(self.data['comment'], '')) 4698 self._PRW_loinc.SetText('', None) 4699 self._LBL_loinc.SetLabel('') 4700 self.__loincs = self.data['loincs'] 4701 self.__refresh_loinc_list() 4702 4703 self._PRW_loinc.SetFocus()
4704 4705 #---------------------------------------------------------------- 4706 # event handlers 4707 #----------------------------------------------------------------
4708 - def _on_loinc_selected(self, loinc):
4709 loinc = self._PRW_loinc.GetData() 4710 if loinc is None: 4711 self._LBL_loinc.SetLabel('') 4712 return 4713 loinc_detail = gmLOINC.loinc2data(loinc = loinc) 4714 if loinc_detail is None: 4715 loinc_str = _('no LOINC details found') 4716 else: 4717 loinc_str = '%s: %s%s' % ( 4718 loinc, 4719 loinc_detail['term'], 4720 gmTools.coalesce(loinc_detail['example_units'], '', ' (%s)') 4721 ) 4722 self._LBL_loinc.SetLabel(loinc_str)
4723 4724 #----------------------------------------------------------------
4725 - def _on_add_loinc_button_pressed(self, event):
4726 event.Skip() 4727 4728 loinc = self._PRW_loinc.GetData() 4729 if loinc is None: 4730 loinc = self._PRW_loinc.GetValue().strip() 4731 if loinc.strip() == '': 4732 return 4733 4734 if self.__loincs is None: 4735 self.__loincs = [loinc] 4736 else: 4737 if loinc in self.__loincs: 4738 return 4739 self.__loincs.append(loinc) 4740 4741 self.__refresh_loinc_list() 4742 self._PRW_loinc.SetText('', None) 4743 self._LBL_loinc.SetLabel('') 4744 4745 self._PRW_loinc.SetFocus()
4746 4747 #----------------------------------------------------------------
4748 - def _on_remove_loinc_button_pressed(self, event):
4749 event.Skip() 4750 self._remove_loincs_from_list()
4751 4752 #----------------------------------------------------------------
4753 - def _remove_loincs_from_list(self):
4754 loincs2remove = self._LCTRL_loincs.selected_item_data 4755 if loincs2remove is None: 4756 return 4757 for loinc in loincs2remove: 4758 try: 4759 while True: 4760 self.__loincs.remove(loinc[0]) 4761 except ValueError: 4762 pass 4763 self.__refresh_loinc_list()
4764 4765 #================================================================ 4766 # main 4767 #---------------------------------------------------------------- 4768 if __name__ == '__main__': 4769 4770 from Gnumed.pycommon import gmLog2 4771 from Gnumed.wxpython import gmPatSearchWidgets 4772 4773 gmI18N.activate_locale() 4774 gmI18N.install_domain() 4775 gmDateTime.init() 4776 4777 #------------------------------------------------------------
4778 - def test_grid():
4779 pat = gmPersonSearch.ask_for_patient() 4780 app = wx.PyWidgetTester(size = (500, 300)) 4781 lab_grid = cMeasurementsGrid(app.frame, -1) 4782 lab_grid.patient = pat 4783 app.frame.Show() 4784 app.MainLoop()
4785 #------------------------------------------------------------
4786 - def test_test_ea_pnl():
4787 pat = gmPersonSearch.ask_for_patient() 4788 gmPatSearchWidgets.set_active_patient(patient=pat) 4789 app = wx.PyWidgetTester(size = (500, 300)) 4790 ea = cMeasurementEditAreaPnl(app.frame, -1) 4791 app.frame.Show() 4792 app.MainLoop()
4793 #------------------------------------------------------------ 4794 # def test_primary_care_vitals_pnl(): 4795 # app = wx.PyWidgetTester(size = (500, 300)) 4796 # pnl = wxgPrimaryCareVitalsInputPnl.wxgPrimaryCareVitalsInputPnl(app.frame, -1) 4797 # app.frame.Show() 4798 # app.MainLoop() 4799 #------------------------------------------------------------ 4800 if (len(sys.argv) > 1) and (sys.argv[1] == 'test'): 4801 #test_grid() 4802 test_test_ea_pnl() 4803 #test_primary_care_vitals_pnl() 4804 4805 #================================================================ 4806