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.Destroy() 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.Destroy() 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.Destroy() 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, fields=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 fields is not None: 352 ea.set_fields(fields) 353 if dlg.ShowModal() == wx.ID_OK: 354 dlg.Destroy() 355 return True 356 dlg.Destroy() 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 most recent measurement results, possibly filtered by panel/battery. 1327 1328 - operates on a cPatient instance handed to it and NOT on the currently active patient 1329 """
1330 - def __init__(self, *args, **kwargs):
1331 wxgMeasurementsAsMostRecentListPnl.wxgMeasurementsAsMostRecentListPnl.__init__(self, *args, **kwargs) 1332 1333 gmRegetMixin.cRegetOnPaintMixin.__init__(self) 1334 1335 self.__patient = None 1336 1337 self.__init_ui() 1338 self.__register_events()
1339 1340 #------------------------------------------------------------ 1341 # internal helpers 1342 #------------------------------------------------------------
1343 - def __init_ui(self):
1344 self._LCTRL_results.set_columns([_('Test'), _('Result'), _('When'), _('Range')]) 1345 self._CHBOX_show_missing.Disable() 1346 self._PNL_related_documents.lab_reference = None
1347 1348 #------------------------------------------------------------
1349 - def __register_events(self):
1350 gmDispatcher.connect(signal = 'gm_table_mod', receiver = self._on_database_signal) 1351 1352 self._PRW_panel.add_callback_on_selection(callback = self._on_panel_selected) 1353 self._PRW_panel.add_callback_on_modified(callback = self._on_panel_selection_modified) 1354 1355 self._LCTRL_results.select_callback = self._on_result_selected 1356 self._LCTRL_results.edit_callback = self._on_edit
1357 1358 #------------------------------------------------------------
1359 - def __repopulate_ui(self):
1360 1361 self._TCTRL_details.SetValue('') 1362 self._PNL_related_documents.lab_reference = None 1363 1364 pnl = self._PRW_panel.GetData(as_instance = True) 1365 if pnl is None: 1366 results = gmPathLab.get_most_recent_result_for_test_types(pk_patient = self.__patient.ID) 1367 else: 1368 results = pnl.get_most_recent_results ( 1369 pk_patient = self.__patient.ID, 1370 #order_by = , 1371 group_by_meta_type = True, 1372 include_missing = self._CHBOX_show_missing.IsChecked() 1373 ) 1374 items = [] 1375 data = [] 1376 for r in results: 1377 if isinstance(r, gmPathLab.cTestResult): 1378 result_type = r['abbrev_tt'] 1379 review = gmTools.bool2subst ( 1380 r['reviewed'], 1381 '', 1382 ' ' + gmTools.u_writing_hand, 1383 ' ' + gmTools.u_writing_hand 1384 ) 1385 result_val = '%s%s%s%s' % ( 1386 gmTools.strip_empty_lines(text = r['unified_val'])[0], 1387 gmTools.coalesce(r['val_unit'], '', ' %s'), 1388 gmTools.coalesce(r['abnormality_indicator'], '', ' %s'), 1389 review 1390 ) 1391 result_when = _('%s ago (%s)') % ( 1392 gmDateTime.format_interval_medically(interval = gmDateTime.pydt_now_here() - r['clin_when']), 1393 gmDateTime.pydt_strftime(r['clin_when'], '%Y %b %d %H:%M', accuracy = gmDateTime.acc_minutes) 1394 ) 1395 range_info = gmTools.coalesce ( 1396 r.formatted_clinical_range, 1397 r.formatted_normal_range 1398 ) 1399 tt = r.format(with_source_data = True) 1400 else: 1401 result_type = r 1402 result_val = _('missing') 1403 loinc_data = gmLOINC.loinc2data(r) 1404 if loinc_data is None: 1405 result_when = _('LOINC not found') 1406 tt = u'' 1407 else: 1408 result_when = loinc_data['term'] 1409 tt = gmLOINC.format_loinc(r) 1410 range_info = None 1411 items.append([result_type, result_val, result_when, gmTools.coalesce(range_info, '')]) 1412 data.append({'data': r, 'formatted': tt}) 1413 1414 self._LCTRL_results.set_string_items(items) 1415 self._LCTRL_results.set_column_widths([wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE]) 1416 self._LCTRL_results.set_data(data) 1417 1418 if len(items) > 0: 1419 self._LCTRL_results.Select(idx = 0, on = 1) 1420 self._LCTRL_results.SetFocus() 1421 1422 return True
1423 1424 #--------------------------------------------------------
1425 - def __on_panel_selected(self, panel):
1426 if panel is None: 1427 self._TCTRL_panel_comment.SetValue('') 1428 self._CHBOX_show_missing.Disable() 1429 else: 1430 pnl = self._PRW_panel.GetData(as_instance = True) 1431 self._TCTRL_panel_comment.SetValue(gmTools.coalesce(pnl['comment'], '')) 1432 self.__repopulate_ui() 1433 self._CHBOX_show_missing.Enable()
1434 1435 #--------------------------------------------------------
1437 self._TCTRL_panel_comment.SetValue('') 1438 if self._PRW_panel.Value.strip() == u'': 1439 self.__repopulate_ui() 1440 self._CHBOX_show_missing.Disable()
1441 1442 #------------------------------------------------------------ 1443 # event handlers 1444 #------------------------------------------------------------
1445 - def _on_database_signal(self, **kwds):
1446 if self.__patient is None: 1447 return True 1448 1449 if kwds['pk_identity'] is not None: # review table doesn't have pk_identity yet 1450 if kwds['pk_identity'] != self.__patient.ID: 1451 return True 1452 1453 if kwds['table'] not in ['clin.test_result', 'clin.reviewed_test_results', 'clin.test_panel']: 1454 return True 1455 1456 self._schedule_data_reget() 1457 return True
1458 1459 #------------------------------------------------------------
1460 - def _on_manage_panels_button_pressed(self, event):
1462 1463 #--------------------------------------------------------
1464 - def _on_panel_selected(self, panel):
1465 wx.CallAfter(self.__on_panel_selected, panel = panel)
1466 1467 #--------------------------------------------------------
1469 wx.CallAfter(self.__on_panel_selection_modified)
1470 1471 #------------------------------------------------------------
1472 - def _on_result_selected(self, event):
1473 event.Skip() 1474 item_data = self._LCTRL_results.get_item_data(item_idx = event.Index) 1475 self._TCTRL_details.SetValue(item_data['formatted']) 1476 if isinstance(item_data['data'], gmPathLab.cTestResult): 1477 self._PNL_related_documents.lab_reference = item_data['data'] 1478 else: 1479 self._PNL_related_documents.lab_reference = None
1480 1481 #------------------------------------------------------------
1482 - def _on_edit(self):
1483 item_data = self._LCTRL_results.get_selected_item_data(only_one = True) 1484 if item_data is None: 1485 return 1486 if isinstance(item_data['data'], gmPathLab.cTestResult): 1487 if edit_measurement(parent = self, measurement = item_data['data'], single_entry = True): 1488 self.__repopulate_ui()
1489 1490 #------------------------------------------------------------
1491 - def _on_show_missing_toggled(self, event):
1492 event.Skip() 1493 # should not happen 1494 if self._PRW_panel.GetData(as_instance = False) is None: 1495 return 1496 self.__repopulate_ui()
1497 1498 #------------------------------------------------------------ 1499 # reget mixin API 1500 #------------------------------------------------------------
1501 - def _populate_with_data(self):
1502 self.__repopulate_ui() 1503 return True
1504 1505 #------------------------------------------------------------ 1506 # properties 1507 #------------------------------------------------------------
1508 - def _get_patient(self):
1509 return self.__patient
1510
1511 - def _set_patient(self, patient):
1512 if (self.__patient is None) and (patient is None): 1513 return 1514 if (self.__patient is None) or (patient is None): 1515 self.__patient = patient 1516 self._schedule_data_reget() 1517 return 1518 if self.__patient.ID == patient.ID: 1519 return 1520 self.__patient = patient 1521 self._schedule_data_reget()
1522 1523 patient = property(_get_patient, _set_patient)
1524 1525 #================================================================ 1526 from Gnumed.wxGladeWidgets import wxgMeasurementsAsTablePnl 1527
1528 -class cMeasurementsAsTablePnl(wxgMeasurementsAsTablePnl.wxgMeasurementsAsTablePnl, gmRegetMixin.cRegetOnPaintMixin):
1529 """A panel for holding a grid displaying all measurement results. 1530 1531 - operates on a cPatient instance handed to it and NOT on the currently active patient 1532 """
1533 - def __init__(self, *args, **kwargs):
1534 wxgMeasurementsAsTablePnl.wxgMeasurementsAsTablePnl.__init__(self, *args, **kwargs) 1535 1536 gmRegetMixin.cRegetOnPaintMixin.__init__(self) 1537 1538 self.__patient = None 1539 1540 self.__init_ui() 1541 self.__register_events()
1542 1543 #------------------------------------------------------------ 1544 # internal helpers 1545 #------------------------------------------------------------
1546 - def __init_ui(self):
1547 self.__action_button_popup = wx.Menu(title = _('Perform on selected results:')) 1548 1549 item = self.__action_button_popup.Append(-1, _('Review and &sign')) 1550 self.Bind(wx.EVT_MENU, self.__on_sign_current_selection, item) 1551 1552 item = self.__action_button_popup.Append(-1, _('Plot')) 1553 self.Bind(wx.EVT_MENU, self.__on_plot_current_selection, item) 1554 1555 #item = self.__action_button_popup.Append(-1, _('Export to &file')) 1556 #self.Bind(wx.EVT_MENU, self._GRID_results_all.current_selection_to_file, item) 1557 #self.__action_button_popup.Enable(id = item.Id, enable = False) 1558 1559 #item = self.__action_button_popup.Append(-1, _('Export to &clipboard')) 1560 #self.Bind(wx.EVT_MENU, self._GRID_results_all.current_selection_to_clipboard, item) 1561 #self.__action_button_popup.Enable(id = item.Id, enable = False) 1562 1563 item = self.__action_button_popup.Append(-1, _('&Delete')) 1564 self.Bind(wx.EVT_MENU, self.__on_delete_current_selection, item) 1565 1566 # FIXME: create inbox message to staff to phone patient to come in 1567 # FIXME: generate and let edit a SOAP narrative and include the values 1568 1569 self._GRID_results_all.show_by_panel = False
1570 1571 #------------------------------------------------------------
1572 - def __register_events(self):
1573 gmDispatcher.connect(signal = 'gm_table_mod', receiver = self._on_database_signal)
1574 1575 #------------------------------------------------------------
1576 - def __repopulate_ui(self):
1577 self._GRID_results_all.patient = self.__patient 1578 #self._GRID_results_battery.Fit() 1579 self.Layout() 1580 return True
1581 1582 #------------------------------------------------------------
1583 - def __on_sign_current_selection(self, evt):
1584 self._GRID_results_all.sign_current_selection()
1585 1586 #------------------------------------------------------------
1587 - def __on_plot_current_selection(self, evt):
1588 self._GRID_results_all.plot_current_selection()
1589 1590 #------------------------------------------------------------
1591 - def __on_delete_current_selection(self, evt):
1592 self._GRID_results_all.delete_current_selection()
1593 1594 #------------------------------------------------------------ 1595 # event handlers 1596 #------------------------------------------------------------
1597 - def _on_database_signal(self, **kwds):
1598 if self.__patient is None: 1599 return True 1600 1601 if kwds['pk_identity'] is not None: # review table doesn't have pk_identity yet 1602 if kwds['pk_identity'] != self.__patient.ID: 1603 return True 1604 1605 if kwds['table'] not in ['clin.test_result', 'clin.reviewed_test_results']: 1606 return True 1607 1608 self._schedule_data_reget() 1609 return True
1610 1611 #--------------------------------------------------------
1612 - def _on_add_button_pressed(self, event):
1613 edit_measurement(parent = self, measurement = None)
1614 1615 #--------------------------------------------------------
1616 - def _on_manage_types_button_pressed(self, event):
1617 event.Skip() 1618 manage_measurement_types(parent = self)
1619 1620 #--------------------------------------------------------
1621 - def _on_review_button_pressed(self, evt):
1622 self.PopupMenu(self.__action_button_popup)
1623 1624 #--------------------------------------------------------
1625 - def _on_select_button_pressed(self, evt):
1626 if self._RBTN_my_unsigned.GetValue() is True: 1627 self._GRID_results_all.select_cells(unsigned_only = True, accountables_only = True, keep_preselections = False) 1628 elif self._RBTN_all_unsigned.GetValue() is True: 1629 self._GRID_results_all.select_cells(unsigned_only = True, accountables_only = False, keep_preselections = False)
1630 1631 #------------------------------------------------------------ 1632 # reget mixin API 1633 #------------------------------------------------------------
1634 - def _populate_with_data(self):
1635 self.__repopulate_ui() 1636 return True
1637 1638 #------------------------------------------------------------ 1639 # properties 1640 #------------------------------------------------------------
1641 - def _get_patient(self):
1642 return self.__patient
1643
1644 - def _set_patient(self, patient):
1645 if (self.__patient is None) and (patient is None): 1646 return 1647 if (self.__patient is None) or (patient is None): 1648 self.__patient = patient 1649 self._schedule_data_reget() 1650 return 1651 if self.__patient.ID == patient.ID: 1652 return 1653 self.__patient = patient 1654 self._schedule_data_reget()
1655 1656 patient = property(_get_patient, _set_patient)
1657 1658 #================================================================ 1659 # notebook based measurements plugin 1660 #================================================================
1661 -class cMeasurementsNb(wx.Notebook, gmPlugin.cPatientChange_PluginMixin):
1662 """Notebook displaying measurements pages: 1663 1664 - by test battery 1665 - by day 1666 - by issue/episode 1667 - most-recent list, perhaps by panel 1668 - full grid 1669 - full list 1670 1671 Used as a main notebook plugin page. 1672 1673 Operates on the active patient. 1674 """ 1675 #--------------------------------------------------------
1676 - def __init__(self, parent, id):
1677 1678 wx.Notebook.__init__ ( 1679 self, 1680 parent = parent, 1681 id = id, 1682 style = wx.NB_TOP | wx.NB_MULTILINE | wx.NO_BORDER, 1683 name = self.__class__.__name__ 1684 ) 1685 _log.debug('created wx.Notebook: %s with ID %s', self.__class__.__name__, self.Id) 1686 gmPlugin.cPatientChange_PluginMixin.__init__(self) 1687 self.__patient = gmPerson.gmCurrentPatient() 1688 self.__init_ui() 1689 self.SetSelection(0)
1690 1691 #-------------------------------------------------------- 1692 # patient change plugin API 1693 #--------------------------------------------------------
1694 - def _on_current_patient_unset(self, **kwds):
1695 for page_idx in range(self.GetPageCount()): 1696 page = self.GetPage(page_idx) 1697 page.patient = None
1698 1699 #--------------------------------------------------------
1700 - def _post_patient_selection(self, **kwds):
1701 for page_idx in range(self.GetPageCount()): 1702 page = self.GetPage(page_idx) 1703 page.patient = self.__patient.patient
1704 1705 #-------------------------------------------------------- 1706 # notebook plugin API 1707 #--------------------------------------------------------
1708 - def repopulate_ui(self):
1709 if self.__patient.connected: 1710 pat = self.__patient.patient 1711 else: 1712 pat = None 1713 for page_idx in range(self.GetPageCount()): 1714 page = self.GetPage(page_idx) 1715 page.patient = pat 1716 1717 return True
1718 1719 #-------------------------------------------------------- 1720 # internal API 1721 #--------------------------------------------------------
1722 - def __init_ui(self):
1723 1724 # by day 1725 new_page = cMeasurementsByDayPnl(self, -1) 1726 new_page.patient = None 1727 self.AddPage ( 1728 page = new_page, 1729 text = _('Days'), 1730 select = True 1731 ) 1732 1733 # by issue 1734 new_page = cMeasurementsByIssuePnl(self, -1) 1735 new_page.patient = None 1736 self.AddPage ( 1737 page = new_page, 1738 text = _('Problems'), 1739 select = False 1740 ) 1741 1742 # by test panel 1743 new_page = cMeasurementsByBatteryPnl(self, -1) 1744 new_page.patient = None 1745 self.AddPage ( 1746 page = new_page, 1747 text = _('Panels'), 1748 select = False 1749 ) 1750 1751 # most-recent, by panel 1752 new_page = cMeasurementsAsMostRecentListPnl(self, -1) 1753 new_page.patient = None 1754 self.AddPage ( 1755 page = new_page, 1756 text = _('Most recent'), 1757 select = False 1758 ) 1759 1760 # full grid 1761 new_page = cMeasurementsAsTablePnl(self, -1) 1762 new_page.patient = None 1763 self.AddPage ( 1764 page = new_page, 1765 text = _('Table'), 1766 select = False 1767 ) 1768 1769 # full list 1770 new_page = cMeasurementsAsListPnl(self, -1) 1771 new_page.patient = None 1772 self.AddPage ( 1773 page = new_page, 1774 text = _('List'), 1775 select = False 1776 )
1777 1778 #-------------------------------------------------------- 1779 # properties 1780 #--------------------------------------------------------
1781 - def _get_patient(self):
1782 return self.__patient
1783
1784 - def _set_patient(self, patient):
1785 self.__patient = patient 1786 if self.__patient.connected: 1787 pat = self.__patient.patient 1788 else: 1789 pat = None 1790 for page_idx in range(self.GetPageCount()): 1791 page = self.GetPage(page_idx) 1792 page.patient = pat
1793 1794 patient = property(_get_patient, _set_patient)
1795 1796 #================================================================
1797 -class cMeasurementsGrid(wx.grid.Grid):
1798 """A grid class for displaying measurement results. 1799 1800 - operates on a cPatient instance handed to it 1801 - does NOT listen to the currently active patient 1802 - thereby it can display any patient at any time 1803 """ 1804 # FIXME: sort-by-battery 1805 # FIXME: filter out empty 1806 # FIXME: filter by tests of a selected date 1807 # FIXME: dates DESC/ASC by cfg 1808 # FIXME: mouse over column header: display date info
1809 - def __init__(self, *args, **kwargs):
1810 1811 wx.grid.Grid.__init__(self, *args, **kwargs) 1812 1813 self.__patient = None 1814 self.__panel_to_show = None 1815 self.__show_by_panel = False 1816 self.__cell_data = {} 1817 self.__row_label_data = [] 1818 self.__col_label_data = [] 1819 1820 self.__prev_row = None 1821 self.__prev_col = None 1822 self.__prev_label_row = None 1823 self.__date_format = str((_('lab_grid_date_format::%Y\n%b %d')).lstrip('lab_grid_date_format::')) 1824 1825 self.__init_ui() 1826 self.__register_events()
1827 1828 #------------------------------------------------------------ 1829 # external API 1830 #------------------------------------------------------------
1831 - def delete_current_selection(self):
1832 if not self.IsSelection(): 1833 gmDispatcher.send(signal = 'statustext', msg = _('No results selected for deletion.')) 1834 return True 1835 1836 selected_cells = self.get_selected_cells() 1837 if len(selected_cells) > 20: 1838 results = None 1839 msg = _( 1840 'There are %s results marked for deletion.\n' 1841 '\n' 1842 'Are you sure you want to delete these results ?' 1843 ) % len(selected_cells) 1844 else: 1845 results = self.__cells_to_data(cells = selected_cells, exclude_multi_cells = False) 1846 txt = '\n'.join([ '%s %s (%s): %s %s%s' % ( 1847 r['clin_when'].strftime('%x %H:%M'), 1848 r['unified_abbrev'], 1849 r['unified_name'], 1850 r['unified_val'], 1851 r['val_unit'], 1852 gmTools.coalesce(r['abnormality_indicator'], '', ' (%s)') 1853 ) for r in results 1854 ]) 1855 msg = _( 1856 'The following results are marked for deletion:\n' 1857 '\n' 1858 '%s\n' 1859 '\n' 1860 'Are you sure you want to delete these results ?' 1861 ) % txt 1862 1863 dlg = gmGuiHelpers.c2ButtonQuestionDlg ( 1864 self, 1865 -1, 1866 caption = _('Deleting test results'), 1867 question = msg, 1868 button_defs = [ 1869 {'label': _('Delete'), 'tooltip': _('Yes, delete all the results.'), 'default': False}, 1870 {'label': _('Cancel'), 'tooltip': _('No, do NOT delete any results.'), 'default': True} 1871 ] 1872 ) 1873 decision = dlg.ShowModal() 1874 1875 if decision == wx.ID_YES: 1876 if results is None: 1877 results = self.__cells_to_data(cells = selected_cells, exclude_multi_cells = False) 1878 for result in results: 1879 gmPathLab.delete_test_result(result)
1880 1881 #------------------------------------------------------------
1882 - def sign_current_selection(self):
1883 if not self.IsSelection(): 1884 gmDispatcher.send(signal = 'statustext', msg = _('Cannot sign results. No results selected.')) 1885 return True 1886 1887 selected_cells = self.get_selected_cells() 1888 tests = self.__cells_to_data(cells = selected_cells, exclude_multi_cells = False) 1889 1890 return review_tests(parent = self, tests = tests)
1891 1892 #------------------------------------------------------------
1893 - def plot_current_selection(self):
1894 1895 if not self.IsSelection(): 1896 gmDispatcher.send(signal = 'statustext', msg = _('Cannot plot results. No results selected.')) 1897 return True 1898 1899 tests = self.__cells_to_data ( 1900 cells = self.get_selected_cells(), 1901 exclude_multi_cells = False, 1902 auto_include_multi_cells = True 1903 ) 1904 1905 plot_measurements(parent = self, tests = tests)
1906 #------------------------------------------------------------
1907 - def get_selected_cells(self):
1908 1909 sel_block_top_left = self.GetSelectionBlockTopLeft() 1910 sel_block_bottom_right = self.GetSelectionBlockBottomRight() 1911 sel_cols = self.GetSelectedCols() 1912 sel_rows = self.GetSelectedRows() 1913 1914 selected_cells = [] 1915 1916 # individually selected cells (ctrl-click) 1917 selected_cells += self.GetSelectedCells() 1918 1919 # selected rows 1920 selected_cells += list ( 1921 (row, col) 1922 for row in sel_rows 1923 for col in range(self.GetNumberCols()) 1924 ) 1925 1926 # selected columns 1927 selected_cells += list ( 1928 (row, col) 1929 for row in range(self.GetNumberRows()) 1930 for col in sel_cols 1931 ) 1932 1933 # selection blocks 1934 for top_left, bottom_right in zip(self.GetSelectionBlockTopLeft(), self.GetSelectionBlockBottomRight()): 1935 selected_cells += [ 1936 (row, col) 1937 for row in range(top_left[0], bottom_right[0] + 1) 1938 for col in range(top_left[1], bottom_right[1] + 1) 1939 ] 1940 1941 return set(selected_cells)
1942 #------------------------------------------------------------
1943 - def select_cells(self, unsigned_only=False, accountables_only=False, keep_preselections=False):
1944 """Select a range of cells according to criteria. 1945 1946 unsigned_only: include only those which are not signed at all yet 1947 accountable_only: include only those for which the current user is responsible 1948 keep_preselections: broaden (rather than replace) the range of selected cells 1949 1950 Combinations are powerful ! 1951 """ 1952 wx.BeginBusyCursor() 1953 self.BeginBatch() 1954 1955 if not keep_preselections: 1956 self.ClearSelection() 1957 1958 for col_idx in self.__cell_data.keys(): 1959 for row_idx in self.__cell_data[col_idx].keys(): 1960 # loop over results in cell and only include 1961 # those multi-value cells that are not ambiguous 1962 do_not_include = False 1963 for result in self.__cell_data[col_idx][row_idx]: 1964 if unsigned_only: 1965 if result['reviewed']: 1966 do_not_include = True 1967 break 1968 if accountables_only: 1969 if not result['you_are_responsible']: 1970 do_not_include = True 1971 break 1972 if do_not_include: 1973 continue 1974 1975 self.SelectBlock(row_idx, col_idx, row_idx, col_idx, addToSelected = True) 1976 1977 self.EndBatch() 1978 wx.EndBusyCursor()
1979 1980 #------------------------------------------------------------
1981 - def repopulate_grid(self):
1982 self.empty_grid() 1983 if self.__patient is None: 1984 return 1985 1986 if self.__show_by_panel: 1987 if self.__panel_to_show is None: 1988 return 1989 tests = self.__panel_to_show.get_test_types_for_results ( 1990 self.__patient.ID, 1991 order_by = 'unified_abbrev', 1992 unique_meta_types = True 1993 ) 1994 self.__repopulate_grid ( 1995 tests4rows = tests, 1996 test_pks2show = [ tt['pk_test_type'] for tt in self.__panel_to_show['test_types'] ] 1997 ) 1998 return 1999 2000 emr = self.__patient.emr 2001 tests = emr.get_test_types_for_results(order_by = 'unified_abbrev', unique_meta_types = True) 2002 self.__repopulate_grid(tests4rows = tests)
2003 2004 #------------------------------------------------------------
2005 - def __repopulate_grid(self, tests4rows=None, test_pks2show=None):
2006 2007 if len(tests4rows) == 0: 2008 return 2009 2010 emr = self.__patient.emr 2011 2012 self.__row_label_data = tests4rows 2013 row_labels = [ '%s%s' % ( 2014 gmTools.bool2subst(test_type['is_fake_meta_type'], '', gmTools.u_sum, ''), 2015 test_type['unified_abbrev'] 2016 ) for test_type in self.__row_label_data 2017 ] 2018 2019 self.__col_label_data = [ d['clin_when_day'] for d in emr.get_dates_for_results ( 2020 tests = test_pks2show, 2021 reverse_chronological = True 2022 )] 2023 col_labels = [ gmDateTime.pydt_strftime(date, self.__date_format, accuracy = gmDateTime.acc_days) for date in self.__col_label_data ] 2024 2025 results = emr.get_test_results_by_date ( 2026 tests = test_pks2show, 2027 reverse_chronological = True 2028 ) 2029 2030 self.BeginBatch() 2031 2032 # rows 2033 self.AppendRows(numRows = len(row_labels)) 2034 for row_idx in range(len(row_labels)): 2035 self.SetRowLabelValue(row_idx, row_labels[row_idx]) 2036 2037 # columns 2038 self.AppendCols(numCols = len(col_labels)) 2039 for col_idx in range(len(col_labels)): 2040 self.SetColLabelValue(col_idx, col_labels[col_idx]) 2041 2042 # cell values (list of test results) 2043 for result in results: 2044 row_idx = row_labels.index('%s%s' % ( 2045 gmTools.bool2subst(result['is_fake_meta_type'], '', gmTools.u_sum, ''), 2046 result['unified_abbrev'] 2047 )) 2048 col_idx = col_labels.index(gmDateTime.pydt_strftime(result['clin_when'], self.__date_format, accuracy = gmDateTime.acc_days)) 2049 2050 try: 2051 self.__cell_data[col_idx] 2052 except KeyError: 2053 self.__cell_data[col_idx] = {} 2054 2055 # the tooltip always shows the youngest sub result details 2056 if row_idx in self.__cell_data[col_idx]: 2057 self.__cell_data[col_idx][row_idx].append(result) 2058 self.__cell_data[col_idx][row_idx].sort(key = lambda x: x['clin_when'], reverse = True) 2059 else: 2060 self.__cell_data[col_idx][row_idx] = [result] 2061 2062 # rebuild cell display string 2063 vals2display = [] 2064 cell_has_out_of_bounds_value = False 2065 for sub_result in self.__cell_data[col_idx][row_idx]: 2066 2067 if sub_result.is_considered_abnormal: 2068 cell_has_out_of_bounds_value = True 2069 2070 abnormality_indicator = sub_result.formatted_abnormality_indicator 2071 if abnormality_indicator is None: 2072 abnormality_indicator = '' 2073 if abnormality_indicator != '': 2074 abnormality_indicator = ' (%s)' % abnormality_indicator[:3] 2075 2076 missing_review = False 2077 # warn on missing review if 2078 # a) no review at all exists or 2079 if not sub_result['reviewed']: 2080 missing_review = True 2081 # b) there is a review but 2082 else: 2083 # current user is reviewer and hasn't reviewed 2084 if sub_result['you_are_responsible'] and not sub_result['review_by_you']: 2085 missing_review = True 2086 2087 needs_superscript = False 2088 2089 # can we display the full sub_result length ? 2090 if sub_result.is_long_text: 2091 lines = gmTools.strip_empty_lines ( 2092 text = sub_result['unified_val'], 2093 eol = '\n', 2094 return_list = True 2095 ) 2096 needs_superscript = True 2097 tmp = lines[0][:7] 2098 else: 2099 val = gmTools.strip_empty_lines ( 2100 text = sub_result['unified_val'], 2101 eol = '\n', 2102 return_list = False 2103 ).replace('\n', '//') 2104 if len(val) > 8: 2105 needs_superscript = True 2106 tmp = val[:7] 2107 else: 2108 tmp = '%.8s' % val[:8] 2109 2110 # abnormal ? 2111 tmp = '%s%.6s' % (tmp, abnormality_indicator) 2112 2113 # is there a comment ? 2114 has_sub_result_comment = gmTools.coalesce ( 2115 gmTools.coalesce(sub_result['note_test_org'], sub_result['comment']), 2116 '' 2117 ).strip() != '' 2118 if has_sub_result_comment: 2119 needs_superscript = True 2120 2121 if needs_superscript: 2122 tmp = '%s%s' % (tmp, gmTools.u_superscript_one) 2123 2124 # lacking a review ? 2125 if missing_review: 2126 tmp = '%s %s' % (tmp, gmTools.u_writing_hand) 2127 else: 2128 if sub_result['is_clinically_relevant']: 2129 tmp += ' !' 2130 2131 # part of a multi-result cell ? 2132 if len(self.__cell_data[col_idx][row_idx]) > 1: 2133 tmp = '%s %s' % (sub_result['clin_when'].strftime('%H:%M'), tmp) 2134 2135 vals2display.append(tmp) 2136 2137 self.SetCellValue(row_idx, col_idx, '\n'.join(vals2display)) 2138 self.SetCellAlignment(row_idx, col_idx, horiz = wx.ALIGN_RIGHT, vert = wx.ALIGN_CENTRE) 2139 # We used to color text in cells holding abnormals 2140 # in firebrick red but that would color ALL text (including 2141 # normals) and not only the abnormals within that 2142 # cell. Shading, however, only says that *something* 2143 # inside that cell is worthy of attention. 2144 #if sub_result_relevant: 2145 # font = self.GetCellFont(row_idx, col_idx) 2146 # self.SetCellTextColour(row_idx, col_idx, 'firebrick') 2147 # font.SetWeight(wx.FONTWEIGHT_BOLD) 2148 # self.SetCellFont(row_idx, col_idx, font) 2149 if cell_has_out_of_bounds_value: 2150 #self.SetCellBackgroundColour(row_idx, col_idx, 'cornflower blue') 2151 self.SetCellBackgroundColour(row_idx, col_idx, 'PALE TURQUOISE') 2152 2153 self.EndBatch() 2154 2155 self.AutoSize() 2156 self.AdjustScrollbars() 2157 self.ForceRefresh() 2158 2159 #self.Fit() 2160 2161 return
2162 2163 #------------------------------------------------------------
2164 - def empty_grid(self):
2165 self.BeginBatch() 2166 self.ClearGrid() 2167 # Windows cannot do nothing, it rather decides to assert() 2168 # on thinking it is supposed to do nothing 2169 if self.GetNumberRows() > 0: 2170 self.DeleteRows(pos = 0, numRows = self.GetNumberRows()) 2171 if self.GetNumberCols() > 0: 2172 self.DeleteCols(pos = 0, numCols = self.GetNumberCols()) 2173 self.EndBatch() 2174 self.__cell_data = {} 2175 self.__row_label_data = [] 2176 self.__col_label_data = []
2177 2178 #------------------------------------------------------------
2179 - def get_row_tooltip(self, row=None):
2180 # include details about test types included ? 2181 2182 # sometimes, for some reason, there is no row and 2183 # wxPython still tries to find a tooltip for it 2184 try: 2185 tt = self.__row_label_data[row] 2186 except IndexError: 2187 return ' ' 2188 2189 if tt['is_fake_meta_type']: 2190 return tt.format(patient = self.__patient.ID) 2191 2192 meta_tt = tt.meta_test_type 2193 txt = meta_tt.format(with_tests = True, patient = self.__patient.ID) 2194 2195 return txt
2196 2197 #------------------------------------------------------------
2198 - def get_cell_tooltip(self, col=None, row=None):
2199 try: 2200 cell_results = self.__cell_data[col][row] 2201 except KeyError: 2202 # FIXME: maybe display the most recent or when the most recent was ? 2203 cell_results = None 2204 2205 if cell_results is None: 2206 return ' ' 2207 2208 is_multi_cell = False 2209 if len(cell_results) > 1: 2210 is_multi_cell = True 2211 result = cell_results[0] 2212 2213 tt = '' 2214 # header 2215 if is_multi_cell: 2216 tt += _('Details of most recent (topmost) result ! \n') 2217 if result.is_long_text: 2218 tt += gmTools.strip_empty_lines(text = result['val_alpha'], eol = '\n', return_list = False) 2219 return tt 2220 2221 tt += result.format(with_review = True, with_evaluation = True, with_ranges = True) 2222 return tt
2223 2224 #------------------------------------------------------------ 2225 # internal helpers 2226 #------------------------------------------------------------
2227 - def __init_ui(self):
2228 #self.SetMinSize(wx.DefaultSize) 2229 self.SetMinSize((10, 10)) 2230 2231 self.CreateGrid(0, 1) 2232 self.EnableEditing(0) 2233 self.EnableDragGridSize(1) 2234 2235 # column labels 2236 # setting this screws up the labels: they are cut off and displaced 2237 #self.SetColLabelAlignment(wx.ALIGN_CENTER, wx.ALIGN_BOTTOM) 2238 2239 # row labels 2240 self.SetRowLabelSize(wx.grid.GRID_AUTOSIZE) # starting with 2.8.8 2241 #self.SetRowLabelSize(150) 2242 self.SetRowLabelAlignment(horiz = wx.ALIGN_LEFT, vert = wx.ALIGN_CENTRE) 2243 font = self.GetLabelFont() 2244 font.SetWeight(wx.FONTWEIGHT_LIGHT) 2245 self.SetLabelFont(font) 2246 2247 # add link to left upper corner 2248 dbcfg = gmCfg.cCfgSQL() 2249 url = dbcfg.get2 ( 2250 option = 'external.urls.measurements_encyclopedia', 2251 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace, 2252 bias = 'user', 2253 default = gmPathLab.URL_test_result_information 2254 ) 2255 2256 self.__WIN_corner = self.GetGridCornerLabelWindow() # a wx.Window instance 2257 2258 LNK_lab = wxh.HyperlinkCtrl ( 2259 self.__WIN_corner, 2260 -1, 2261 label = _('Tests'), 2262 style = wxh.HL_DEFAULT_STYLE # wx.TE_READONLY|wx.TE_CENTRE| wx.NO_BORDER | 2263 ) 2264 LNK_lab.SetURL(url) 2265 LNK_lab.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BACKGROUND)) 2266 LNK_lab.SetToolTip(_( 2267 'Navigate to an encyclopedia of measurements\n' 2268 'and test methods on the web.\n' 2269 '\n' 2270 ' <%s>' 2271 ) % url) 2272 2273 SZR_inner = wx.BoxSizer(wx.HORIZONTAL) 2274 SZR_inner.Add((20, 20), 1, wx.EXPAND, 0) # spacer 2275 SZR_inner.Add(LNK_lab, 0, wx.ALIGN_CENTER_VERTICAL, 0) #wx.ALIGN_CENTER wx.EXPAND 2276 SZR_inner.Add((20, 20), 1, wx.EXPAND, 0) # spacer 2277 2278 SZR_corner = wx.BoxSizer(wx.VERTICAL) 2279 SZR_corner.Add((20, 20), 1, wx.EXPAND, 0) # spacer 2280 SZR_corner.Add(SZR_inner, 0, wx.EXPAND) # inner sizer with centered hyperlink 2281 SZR_corner.Add((20, 20), 1, wx.EXPAND, 0) # spacer 2282 2283 self.__WIN_corner.SetSizer(SZR_corner) 2284 SZR_corner.Fit(self.__WIN_corner)
2285 2286 #------------------------------------------------------------
2287 - def __resize_corner_window(self, evt):
2288 self.__WIN_corner.Layout()
2289 2290 #------------------------------------------------------------
2291 - def __cells_to_data(self, cells=None, exclude_multi_cells=False, auto_include_multi_cells=False):
2292 """List of <cells> must be in row / col order.""" 2293 data = [] 2294 for row, col in cells: 2295 try: 2296 # cell data is stored col / row 2297 data_list = self.__cell_data[col][row] 2298 except KeyError: 2299 continue 2300 2301 if len(data_list) == 1: 2302 data.append(data_list[0]) 2303 continue 2304 2305 if exclude_multi_cells: 2306 gmDispatcher.send(signal = 'statustext', msg = _('Excluding multi-result field from further processing.')) 2307 continue 2308 2309 if auto_include_multi_cells: 2310 data.extend(data_list) 2311 continue 2312 2313 data_to_include = self.__get_choices_from_multi_cell(cell_data = data_list) 2314 if data_to_include is None: 2315 continue 2316 data.extend(data_to_include) 2317 2318 return data
2319 2320 #------------------------------------------------------------
2321 - def __get_choices_from_multi_cell(self, cell_data=None, single_selection=False):
2322 data = gmListWidgets.get_choices_from_list ( 2323 parent = self, 2324 msg = _( 2325 'Your selection includes a field with multiple results.\n' 2326 '\n' 2327 'Please select the individual results you want to work on:' 2328 ), 2329 caption = _('Selecting test results'), 2330 choices = [ [d['clin_when'], '%s: %s' % (d['abbrev_tt'], d['name_tt']), d['unified_val']] for d in cell_data ], 2331 columns = [ _('Date / Time'), _('Test'), _('Result') ], 2332 data = cell_data, 2333 single_selection = single_selection 2334 ) 2335 return data
2336 2337 #------------------------------------------------------------ 2338 # event handling 2339 #------------------------------------------------------------
2340 - def __register_events(self):
2341 # dynamic tooltips: GridWindow, GridRowLabelWindow, GridColLabelWindow, GridCornerLabelWindow 2342 self.GetGridWindow().Bind(wx.EVT_MOTION, self.__on_mouse_over_cells) 2343 self.GetGridRowLabelWindow().Bind(wx.EVT_MOTION, self.__on_mouse_over_row_labels) 2344 #self.GetGridColLabelWindow().Bind(wx.EVT_MOTION, self.__on_mouse_over_col_labels) 2345 2346 # sizing left upper corner window 2347 self.Bind(wx.EVT_SIZE, self.__resize_corner_window) 2348 2349 # editing cells 2350 self.Bind(wx.grid.EVT_GRID_CELL_LEFT_DCLICK, self.__on_cell_left_dclicked)
2351 2352 #------------------------------------------------------------
2353 - def __on_cell_left_dclicked(self, evt):
2354 col = evt.GetCol() 2355 row = evt.GetRow() 2356 2357 try: 2358 self.__cell_data[col][row] 2359 except KeyError: # empty cell 2360 fields = {} 2361 col_date = self.__col_label_data[col] 2362 fields['clin_when'] = {'data': col_date} 2363 test_type = self.__row_label_data[row] 2364 temporally_closest_result_of_row_type = test_type.meta_test_type.get_temporally_closest_result(col_date, self.__patient.ID) 2365 if temporally_closest_result_of_row_type is not None: 2366 fields['pk_test_type'] = {'data': temporally_closest_result_of_row_type['pk_test_type']} 2367 same_day_results = gmPathLab.get_results_for_day ( 2368 timestamp = col_date, 2369 patient = self.__patient.ID, 2370 order_by = None 2371 ) 2372 if len(same_day_results) > 0: 2373 fields['pk_episode'] = {'data': same_day_results[0]['pk_episode']} 2374 # maybe ['comment'] as in "medical context" ? - not thought through yet 2375 # no need to set because because setting pk_test_type will do so: 2376 # fields['val_unit'] 2377 # fields['val_normal_min'] 2378 # fields['val_normal_max'] 2379 # fields['val_normal_range'] 2380 # fields['val_target_min'] 2381 # fields['val_target_max'] 2382 # fields['val_target_range'] 2383 edit_measurement ( 2384 parent = self, 2385 measurement = None, 2386 single_entry = True, 2387 fields = fields 2388 ) 2389 return 2390 2391 if len(self.__cell_data[col][row]) > 1: 2392 data = self.__get_choices_from_multi_cell(cell_data = self.__cell_data[col][row], single_selection = True) 2393 else: 2394 data = self.__cell_data[col][row][0] 2395 2396 if data is None: 2397 return 2398 2399 edit_measurement(parent = self, measurement = data, single_entry = True)
2400 2401 #------------------------------------------------------------ 2402 # def OnMouseMotionRowLabel(self, evt): 2403 # x, y = self.CalcUnscrolledPosition(evt.GetPosition()) 2404 # row = self.YToRow(y) 2405 # label = self.table().GetRowHelpValue(row) 2406 # self.GetGridRowLabelWindow().SetToolTip(label or "") 2407 # evt.Skip()
2408 - def __on_mouse_over_row_labels(self, evt):
2409 2410 # Use CalcUnscrolledPosition() to get the mouse position within the 2411 # entire grid including what's offscreen 2412 x, y = self.CalcUnscrolledPosition(evt.GetX(), evt.GetY()) 2413 2414 row = self.YToRow(y) 2415 2416 if self.__prev_label_row == row: 2417 return 2418 2419 self.__prev_label_row == row 2420 2421 evt.GetEventObject().SetToolTip(self.get_row_tooltip(row = row))
2422 #------------------------------------------------------------ 2423 # def OnMouseMotionColLabel(self, evt): 2424 # x, y = self.CalcUnscrolledPosition(evt.GetPosition()) 2425 # col = self.XToCol(x) 2426 # label = self.table().GetColHelpValue(col) 2427 # self.GetGridColLabelWindow().SetToolTip(label or "") 2428 # evt.Skip() 2429 #------------------------------------------------------------
2430 - def __on_mouse_over_cells(self, evt):
2431 """Calculate where the mouse is and set the tooltip dynamically.""" 2432 2433 # Use CalcUnscrolledPosition() to get the mouse position within the 2434 # entire grid including what's offscreen 2435 x, y = self.CalcUnscrolledPosition(evt.GetX(), evt.GetY()) 2436 2437 # use this logic to prevent tooltips outside the actual cells 2438 # apply to GetRowSize, too 2439 # tot = 0 2440 # for col in range(self.NumberCols): 2441 # tot += self.GetColSize(col) 2442 # if xpos <= tot: 2443 # self.tool_tip.Tip = 'Tool tip for Column %s' % ( 2444 # self.GetColLabelValue(col)) 2445 # break 2446 # else: # mouse is in label area beyond the right-most column 2447 # self.tool_tip.Tip = '' 2448 2449 row, col = self.XYToCell(x, y) 2450 2451 if (row == self.__prev_row) and (col == self.__prev_col): 2452 return 2453 2454 self.__prev_row = row 2455 self.__prev_col = col 2456 2457 evt.GetEventObject().SetToolTip(self.get_cell_tooltip(col=col, row=row))
2458 2459 #------------------------------------------------------------ 2460 # properties 2461 #------------------------------------------------------------
2462 - def _get_patient(self):
2463 return self.__patient
2464
2465 - def _set_patient(self, patient):
2466 self.__patient = patient 2467 self.repopulate_grid()
2468 2469 patient = property(_get_patient, _set_patient) 2470 #------------------------------------------------------------
2471 - def _set_panel_to_show(self, panel):
2472 self.__panel_to_show = panel 2473 self.repopulate_grid()
2474 2475 panel_to_show = property(lambda x:x, _set_panel_to_show) 2476 #------------------------------------------------------------
2477 - def _set_show_by_panel(self, show_by_panel):
2478 self.__show_by_panel = show_by_panel 2479 self.repopulate_grid()
2480 2481 show_by_panel = property(lambda x:x, _set_show_by_panel)
2482 2483 #================================================================ 2484 # integrated measurements plugin 2485 #================================================================ 2486 from Gnumed.wxGladeWidgets import wxgMeasurementsPnl 2487
2488 -class cMeasurementsPnl(wxgMeasurementsPnl.wxgMeasurementsPnl, gmRegetMixin.cRegetOnPaintMixin):
2489 """Panel holding a grid with lab data. Used as notebook page.""" 2490
2491 - def __init__(self, *args, **kwargs):
2492 2493 wxgMeasurementsPnl.wxgMeasurementsPnl.__init__(self, *args, **kwargs) 2494 gmRegetMixin.cRegetOnPaintMixin.__init__(self) 2495 self.__display_mode = 'grid' 2496 self.__init_ui() 2497 self.__register_interests()
2498 #-------------------------------------------------------- 2499 # event handling 2500 #--------------------------------------------------------
2501 - def __register_interests(self):
2502 gmDispatcher.connect(signal = 'pre_patient_unselection', receiver = self._on_pre_patient_unselection) 2503 gmDispatcher.connect(signal = 'post_patient_selection', receiver = self._on_post_patient_selection) 2504 gmDispatcher.connect(signal = 'clin.test_result_mod_db', receiver = self._schedule_data_reget) 2505 gmDispatcher.connect(signal = 'clin.reviewed_test_results_mod_db', receiver = self._schedule_data_reget)
2506 #--------------------------------------------------------
2507 - def _on_post_patient_selection(self):
2508 self._schedule_data_reget()
2509 #--------------------------------------------------------
2511 self._GRID_results_all.patient = None 2512 self._GRID_results_battery.patient = None
2513 #--------------------------------------------------------
2514 - def _on_add_button_pressed(self, event):
2515 edit_measurement(parent = self, measurement = None)
2516 #--------------------------------------------------------
2517 - def _on_manage_types_button_pressed(self, event):
2518 event.Skip() 2519 manage_measurement_types(parent = self)
2520 #--------------------------------------------------------
2521 - def _on_list_button_pressed(self, event):
2522 event.Skip() 2523 manage_measurements(parent = self, single_selection = True)#, emr = pat.emr)
2524 #--------------------------------------------------------
2525 - def _on_review_button_pressed(self, evt):
2526 self.PopupMenu(self.__action_button_popup)
2527 #--------------------------------------------------------
2528 - def _on_select_button_pressed(self, evt):
2529 if self._RBTN_my_unsigned.GetValue() is True: 2530 self._GRID_results_all.select_cells(unsigned_only = True, accountables_only = True, keep_preselections = False) 2531 elif self._RBTN_all_unsigned.GetValue() is True: 2532 self._GRID_results_all.select_cells(unsigned_only = True, accountables_only = False, keep_preselections = False)
2533 #--------------------------------------------------------
2534 - def _on_manage_panels_button_pressed(self, event):
2536 #--------------------------------------------------------
2537 - def _on_display_mode_button_pressed(self, event):
2538 event.Skip() 2539 if self.__display_mode == 'grid': 2540 self._BTN_display_mode.SetLabel(_('All: as &Grid')) 2541 self.__display_mode = 'day' 2542 #self._GRID_results_all.Hide() 2543 self._PNL_results_all_grid.Hide() 2544 if self._PNL_results_all_listed.patient is None: 2545 self._PNL_results_all_listed.patient = self._GRID_results_all.patient 2546 self._PNL_results_all_listed.Show() 2547 else: 2548 self._BTN_display_mode.SetLabel(_('All: by &Day')) 2549 self.__display_mode = 'grid' 2550 self._PNL_results_all_listed.Hide() 2551 if self._GRID_results_all.patient is None: 2552 self._GRID_results_all.patient = self._PNL_results_all_listed.patient 2553 #self._GRID_results_all.Show() 2554 self._PNL_results_all_grid.Show() 2555 self.Layout()
2556 #--------------------------------------------------------
2557 - def __on_sign_current_selection(self, evt):
2558 self._GRID_results_all.sign_current_selection()
2559 #--------------------------------------------------------
2560 - def __on_plot_current_selection(self, evt):
2561 self._GRID_results_all.plot_current_selection()
2562 #--------------------------------------------------------
2563 - def __on_delete_current_selection(self, evt):
2564 self._GRID_results_all.delete_current_selection()
2565 #--------------------------------------------------------
2566 - def _on_panel_selected(self, panel):
2567 wx.CallAfter(self.__on_panel_selected, panel=panel)
2568 #--------------------------------------------------------
2569 - def __on_panel_selected(self, panel):
2570 if panel is None: 2571 self._TCTRL_panel_comment.SetValue('') 2572 self._GRID_results_battery.panel_to_show = None 2573 #self._GRID_results_battery.Hide() 2574 self._PNL_results_battery_grid.Hide() 2575 else: 2576 pnl = self._PRW_panel.GetData(as_instance = True) 2577 self._TCTRL_panel_comment.SetValue(gmTools.coalesce ( 2578 pnl['comment'], 2579 '' 2580 )) 2581 self._GRID_results_battery.panel_to_show = pnl 2582 #self._GRID_results_battery.Show() 2583 self._PNL_results_battery_grid.Show() 2584 self._GRID_results_battery.Fit() 2585 self._GRID_results_all.Fit() 2586 self.Layout()
2587 #--------------------------------------------------------
2589 wx.CallAfter(self.__on_panel_selection_modified)
2590 #--------------------------------------------------------
2592 self._TCTRL_panel_comment.SetValue('') 2593 if self._PRW_panel.GetValue().strip() == '': 2594 self._GRID_results_battery.panel_to_show = None 2595 #self._GRID_results_battery.Hide() 2596 self._PNL_results_battery_grid.Hide() 2597 self.Layout()
2598 #-------------------------------------------------------- 2599 # internal API 2600 #--------------------------------------------------------
2601 - def __init_ui(self):
2602 self.SetMinSize((10, 10)) 2603 2604 self.__action_button_popup = wx.Menu(title = _('Perform on selected results:')) 2605 2606 item = self.__action_button_popup.Append(-1, _('Review and &sign')) 2607 self.Bind(wx.EVT_MENU, self.__on_sign_current_selection, item) 2608 2609 item = self.__action_button_popup.Append(-1, _('Plot')) 2610 self.Bind(wx.EVT_MENU, self.__on_plot_current_selection, item) 2611 2612 item = self.__action_button_popup.Append(-1, _('Export to &file')) 2613 self.Bind(wx.EVT_MENU, self._GRID_results_all.current_selection_to_file, item) 2614 self.__action_button_popup.Enable(id = menu_id, enable = False) 2615 2616 item = self.__action_button_popup.Append(-1, _('Export to &clipboard')) 2617 self.Bind(wx.EVT_MENU, self._GRID_results_all.current_selection_to_clipboard, item) 2618 self.__action_button_popup.Enable(id = menu_id, enable = False) 2619 2620 item = self.__action_button_popup.Append(-1, _('&Delete')) 2621 self.Bind(wx.EVT_MENU, self.__on_delete_current_selection, item) 2622 2623 # FIXME: create inbox message to staff to phone patient to come in 2624 # FIXME: generate and let edit a SOAP narrative and include the values 2625 2626 self._PRW_panel.add_callback_on_selection(callback = self._on_panel_selected) 2627 self._PRW_panel.add_callback_on_modified(callback = self._on_panel_selection_modified) 2628 2629 self._GRID_results_battery.show_by_panel = True 2630 self._GRID_results_battery.panel_to_show = None 2631 #self._GRID_results_battery.Hide() 2632 self._PNL_results_battery_grid.Hide() 2633 self._BTN_display_mode.SetLabel(_('All: by &Day')) 2634 #self._GRID_results_all.Show() 2635 self._PNL_results_all_grid.Show() 2636 self._PNL_results_all_listed.Hide() 2637 self.Layout() 2638 2639 self._PRW_panel.SetFocus()
2640 #-------------------------------------------------------- 2641 # reget mixin API 2642 #--------------------------------------------------------
2643 - def _populate_with_data(self):
2644 pat = gmPerson.gmCurrentPatient() 2645 if pat.connected: 2646 self._GRID_results_battery.patient = pat 2647 if self.__display_mode == 'grid': 2648 self._GRID_results_all.patient = pat 2649 self._PNL_results_all_listed.patient = None 2650 else: 2651 self._GRID_results_all.patient = None 2652 self._PNL_results_all_listed.patient = pat 2653 else: 2654 self._GRID_results_battery.patient = None 2655 self._GRID_results_all.patient = None 2656 self._PNL_results_all_listed.patient = None 2657 return True
2658 2659 #================================================================ 2660 # editing widgets 2661 #================================================================
2662 -def review_tests(parent=None, tests=None):
2663 2664 if tests is None: 2665 return True 2666 2667 if len(tests) == 0: 2668 return True 2669 2670 if parent is None: 2671 parent = wx.GetApp().GetTopWindow() 2672 2673 if len(tests) > 10: 2674 test_count = len(tests) 2675 tests2show = None 2676 else: 2677 test_count = None 2678 tests2show = tests 2679 if len(tests) == 0: 2680 return True 2681 2682 dlg = cMeasurementsReviewDlg(parent, -1, tests = tests, test_count = test_count) 2683 decision = dlg.ShowModal() 2684 if decision != wx.ID_APPLY: 2685 return True 2686 2687 wx.BeginBusyCursor() 2688 if dlg._RBTN_confirm_abnormal.GetValue(): 2689 abnormal = None 2690 elif dlg._RBTN_results_normal.GetValue(): 2691 abnormal = False 2692 else: 2693 abnormal = True 2694 2695 if dlg._RBTN_confirm_relevance.GetValue(): 2696 relevant = None 2697 elif dlg._RBTN_results_not_relevant.GetValue(): 2698 relevant = False 2699 else: 2700 relevant = True 2701 2702 comment = None 2703 if len(tests) == 1: 2704 comment = dlg._TCTRL_comment.GetValue() 2705 2706 make_responsible = dlg._CHBOX_responsible.IsChecked() 2707 dlg.Destroy() 2708 2709 for test in tests: 2710 test.set_review ( 2711 technically_abnormal = abnormal, 2712 clinically_relevant = relevant, 2713 comment = comment, 2714 make_me_responsible = make_responsible 2715 ) 2716 wx.EndBusyCursor() 2717 2718 return True
2719 2720 #---------------------------------------------------------------- 2721 from Gnumed.wxGladeWidgets import wxgMeasurementsReviewDlg 2722
2723 -class cMeasurementsReviewDlg(wxgMeasurementsReviewDlg.wxgMeasurementsReviewDlg):
2724
2725 - def __init__(self, *args, **kwargs):
2726 2727 try: 2728 tests = kwargs['tests'] 2729 del kwargs['tests'] 2730 test_count = len(tests) 2731 try: del kwargs['test_count'] 2732 except KeyError: pass 2733 except KeyError: 2734 tests = None 2735 test_count = kwargs['test_count'] 2736 del kwargs['test_count'] 2737 2738 wxgMeasurementsReviewDlg.wxgMeasurementsReviewDlg.__init__(self, *args, **kwargs) 2739 2740 if tests is None: 2741 msg = _('%s results selected. Too many to list individually.') % test_count 2742 else: 2743 msg = '\n'.join ( 2744 [ '%s: %s %s (%s)' % ( 2745 t['unified_abbrev'], 2746 t['unified_val'], 2747 t['val_unit'], 2748 gmDateTime.pydt_strftime(t['clin_when'], '%Y %b %d') 2749 ) for t in tests 2750 ] 2751 ) 2752 2753 self._LBL_tests.SetLabel(msg) 2754 2755 if test_count == 1: 2756 self._TCTRL_comment.Enable(True) 2757 self._TCTRL_comment.SetValue(gmTools.coalesce(tests[0]['review_comment'], '')) 2758 if tests[0]['you_are_responsible']: 2759 self._CHBOX_responsible.Enable(False) 2760 2761 self.Fit()
2762 #-------------------------------------------------------- 2763 # event handling 2764 #--------------------------------------------------------
2765 - def _on_signoff_button_pressed(self, evt):
2766 if self.IsModal(): 2767 self.EndModal(wx.ID_APPLY) 2768 else: 2769 self.Close()
2770 2771 #================================================================ 2772 from Gnumed.wxGladeWidgets import wxgMeasurementEditAreaPnl 2773
2774 -class cMeasurementEditAreaPnl(wxgMeasurementEditAreaPnl.wxgMeasurementEditAreaPnl, gmEditArea.cGenericEditAreaMixin):
2775 """This edit area saves *new* measurements into the active patient only.""" 2776
2777 - def __init__(self, *args, **kwargs):
2778 2779 try: 2780 self.__default_date = kwargs['date'] 2781 del kwargs['date'] 2782 except KeyError: 2783 self.__default_date = None 2784 2785 wxgMeasurementEditAreaPnl.wxgMeasurementEditAreaPnl.__init__(self, *args, **kwargs) 2786 gmEditArea.cGenericEditAreaMixin.__init__(self) 2787 2788 self.__register_interests() 2789 2790 self.successful_save_msg = _('Successfully saved measurement.') 2791 2792 self._DPRW_evaluated.display_accuracy = gmDateTime.acc_minutes
2793 2794 #-------------------------------------------------------- 2795 # generic edit area mixin API 2796 #----------------------------------------------------------------
2797 - def set_fields(self, fields):
2798 try: 2799 self._PRW_test.SetData(data = fields['pk_test_type']['data']) 2800 except KeyError: 2801 pass 2802 try: 2803 self._DPRW_evaluated.SetData(data = fields['clin_when']['data']) 2804 except KeyError: 2805 pass 2806 try: 2807 self._PRW_problem.SetData(data = fields['pk_episode']['data']) 2808 except KeyError: 2809 pass 2810 try: 2811 self._PRW_units.SetText(fields['val_unit']['data'], fields['val_unit']['data'], True) 2812 except KeyError: 2813 pass 2814 try: 2815 self._TCTRL_normal_min.SetValue(fields['val_normal_min']['data']) 2816 except KeyError: 2817 pass 2818 try: 2819 self._TCTRL_normal_max.SetValue(fields['val_normal_max']['data']) 2820 except KeyError: 2821 pass 2822 try: 2823 self._TCTRL_normal_range.SetValue(fields['val_normal_range']['data']) 2824 except KeyError: 2825 pass 2826 try: 2827 self._TCTRL_target_min.SetValue(fields['val_target_min']['data']) 2828 except KeyError: 2829 pass 2830 try: 2831 self._TCTRL_target_max.SetValue(fields['val_target_max']['data']) 2832 except KeyError: 2833 pass 2834 try: 2835 self._TCTRL_target_range.SetValue(fields['val_target_range']['data']) 2836 except KeyError: 2837 pass 2838 2839 self._TCTRL_result.SetFocus()
2840 2841 #--------------------------------------------------------
2842 - def _refresh_as_new(self):
2843 self._PRW_test.SetText('', None, True) 2844 self.__refresh_loinc_info() 2845 self.__refresh_previous_value() 2846 self.__update_units_context() 2847 self._TCTRL_result.SetValue('') 2848 self._PRW_units.SetText('', None, True) 2849 self._PRW_abnormality_indicator.SetText('', None, True) 2850 if self.__default_date is None: 2851 self._DPRW_evaluated.SetData(data = pyDT.datetime.now(tz = gmDateTime.gmCurrentLocalTimezone)) 2852 else: 2853 self._DPRW_evaluated.SetData(data = None) 2854 self._TCTRL_note_test_org.SetValue('') 2855 self._PRW_intended_reviewer.SetData(gmStaff.gmCurrentProvider()['pk_staff']) 2856 self._PRW_problem.SetData() 2857 self._TCTRL_narrative.SetValue('') 2858 self._CHBOX_review.SetValue(False) 2859 self._CHBOX_abnormal.SetValue(False) 2860 self._CHBOX_relevant.SetValue(False) 2861 self._CHBOX_abnormal.Enable(False) 2862 self._CHBOX_relevant.Enable(False) 2863 self._TCTRL_review_comment.SetValue('') 2864 self._TCTRL_normal_min.SetValue('') 2865 self._TCTRL_normal_max.SetValue('') 2866 self._TCTRL_normal_range.SetValue('') 2867 self._TCTRL_target_min.SetValue('') 2868 self._TCTRL_target_max.SetValue('') 2869 self._TCTRL_target_range.SetValue('') 2870 self._TCTRL_norm_ref_group.SetValue('') 2871 2872 self._PRW_test.SetFocus()
2873 #--------------------------------------------------------
2874 - def _refresh_from_existing(self):
2875 self._PRW_test.SetData(data = self.data['pk_test_type']) 2876 self.__refresh_loinc_info() 2877 self.__refresh_previous_value() 2878 self.__update_units_context() 2879 self._TCTRL_result.SetValue(self.data['unified_val']) 2880 self._PRW_units.SetText(self.data['val_unit'], self.data['val_unit'], True) 2881 self._PRW_abnormality_indicator.SetText ( 2882 gmTools.coalesce(self.data['abnormality_indicator'], ''), 2883 gmTools.coalesce(self.data['abnormality_indicator'], ''), 2884 True 2885 ) 2886 self._DPRW_evaluated.SetData(data = self.data['clin_when']) 2887 self._TCTRL_note_test_org.SetValue(gmTools.coalesce(self.data['note_test_org'], '')) 2888 self._PRW_intended_reviewer.SetData(self.data['pk_intended_reviewer']) 2889 self._PRW_problem.SetData(self.data['pk_episode']) 2890 self._TCTRL_narrative.SetValue(gmTools.coalesce(self.data['comment'], '')) 2891 self._CHBOX_review.SetValue(False) 2892 self._CHBOX_abnormal.SetValue(gmTools.coalesce(self.data['is_technically_abnormal'], False)) 2893 self._CHBOX_relevant.SetValue(gmTools.coalesce(self.data['is_clinically_relevant'], False)) 2894 self._CHBOX_abnormal.Enable(False) 2895 self._CHBOX_relevant.Enable(False) 2896 self._TCTRL_review_comment.SetValue(gmTools.coalesce(self.data['review_comment'], '')) 2897 self._TCTRL_normal_min.SetValue(str(gmTools.coalesce(self.data['val_normal_min'], ''))) 2898 self._TCTRL_normal_max.SetValue(str(gmTools.coalesce(self.data['val_normal_max'], ''))) 2899 self._TCTRL_normal_range.SetValue(gmTools.coalesce(self.data['val_normal_range'], '')) 2900 self._TCTRL_target_min.SetValue(str(gmTools.coalesce(self.data['val_target_min'], ''))) 2901 self._TCTRL_target_max.SetValue(str(gmTools.coalesce(self.data['val_target_max'], ''))) 2902 self._TCTRL_target_range.SetValue(gmTools.coalesce(self.data['val_target_range'], '')) 2903 self._TCTRL_norm_ref_group.SetValue(gmTools.coalesce(self.data['norm_ref_group'], '')) 2904 2905 self._TCTRL_result.SetFocus()
2906 #--------------------------------------------------------
2908 self._PRW_test.SetText('', None, True) 2909 self.__refresh_loinc_info() 2910 self.__refresh_previous_value() 2911 self.__update_units_context() 2912 self._TCTRL_result.SetValue('') 2913 self._PRW_units.SetText('', None, True) 2914 self._PRW_abnormality_indicator.SetText('', None, True) 2915 self._DPRW_evaluated.SetData(data = self.data['clin_when']) 2916 self._TCTRL_note_test_org.SetValue('') 2917 self._PRW_intended_reviewer.SetData(self.data['pk_intended_reviewer']) 2918 self._PRW_problem.SetData(self.data['pk_episode']) 2919 self._TCTRL_narrative.SetValue('') 2920 self._CHBOX_review.SetValue(False) 2921 self._CHBOX_abnormal.SetValue(False) 2922 self._CHBOX_relevant.SetValue(False) 2923 self._CHBOX_abnormal.Enable(False) 2924 self._CHBOX_relevant.Enable(False) 2925 self._TCTRL_review_comment.SetValue('') 2926 self._TCTRL_normal_min.SetValue('') 2927 self._TCTRL_normal_max.SetValue('') 2928 self._TCTRL_normal_range.SetValue('') 2929 self._TCTRL_target_min.SetValue('') 2930 self._TCTRL_target_max.SetValue('') 2931 self._TCTRL_target_range.SetValue('') 2932 self._TCTRL_norm_ref_group.SetValue('') 2933 2934 self._PRW_test.SetFocus()
2935 #--------------------------------------------------------
2936 - def _valid_for_save(self):
2937 2938 validity = True 2939 2940 if not self._DPRW_evaluated.is_valid_timestamp(): 2941 self._DPRW_evaluated.display_as_valid(False) 2942 validity = False 2943 else: 2944 self._DPRW_evaluated.display_as_valid(True) 2945 2946 val = self._TCTRL_result.GetValue().strip() 2947 if val == '': 2948 validity = False 2949 self.display_ctrl_as_valid(self._TCTRL_result, False) 2950 else: 2951 self.display_ctrl_as_valid(self._TCTRL_result, True) 2952 numeric, val = gmTools.input2decimal(val) 2953 if numeric: 2954 if self._PRW_units.GetValue().strip() == '': 2955 self._PRW_units.display_as_valid(False) 2956 validity = False 2957 else: 2958 self._PRW_units.display_as_valid(True) 2959 else: 2960 self._PRW_units.display_as_valid(True) 2961 2962 if self._PRW_problem.GetValue().strip() == '': 2963 self._PRW_problem.display_as_valid(False) 2964 validity = False 2965 else: 2966 self._PRW_problem.display_as_valid(True) 2967 2968 if self._PRW_test.GetValue().strip() == '': 2969 self._PRW_test.display_as_valid(False) 2970 validity = False 2971 else: 2972 self._PRW_test.display_as_valid(True) 2973 2974 if self._PRW_intended_reviewer.GetData() is None: 2975 self._PRW_intended_reviewer.display_as_valid(False) 2976 validity = False 2977 else: 2978 self._PRW_intended_reviewer.display_as_valid(True) 2979 2980 ctrls = [self._TCTRL_normal_min, self._TCTRL_normal_max, self._TCTRL_target_min, self._TCTRL_target_max] 2981 for widget in ctrls: 2982 val = widget.GetValue().strip() 2983 if val == '': 2984 continue 2985 try: 2986 decimal.Decimal(val.replace(',', '.', 1)) 2987 self.display_ctrl_as_valid(widget, True) 2988 except: 2989 validity = False 2990 self.display_ctrl_as_valid(widget, False) 2991 2992 if validity is False: 2993 gmDispatcher.send(signal = 'statustext', msg = _('Cannot save result. Invalid or missing essential input.')) 2994 2995 return validity
2996 #--------------------------------------------------------
2997 - def _save_as_new(self):
2998 2999 emr = gmPerson.gmCurrentPatient().emr 3000 3001 success, result = gmTools.input2decimal(self._TCTRL_result.GetValue()) 3002 if success: 3003 v_num = result 3004 v_al = None 3005 else: 3006 v_al = self._TCTRL_result.GetValue().strip() 3007 v_num = None 3008 3009 pk_type = self._PRW_test.GetData() 3010 if pk_type is None: 3011 abbrev = self._PRW_test.GetValue().strip() 3012 name = self._PRW_test.GetValue().strip() 3013 unit = gmTools.coalesce(self._PRW_units.GetData(), self._PRW_units.GetValue()).strip() 3014 lab = manage_measurement_orgs ( 3015 parent = self, 3016 msg = _('Please select (or create) a lab for the new test type [%s in %s]') % (name, unit) 3017 ) 3018 if lab is not None: 3019 lab = lab['pk_test_org'] 3020 tt = gmPathLab.create_measurement_type ( 3021 lab = lab, 3022 abbrev = abbrev, 3023 name = name, 3024 unit = unit 3025 ) 3026 pk_type = tt['pk_test_type'] 3027 3028 tr = emr.add_test_result ( 3029 episode = self._PRW_problem.GetData(can_create=True, is_open=False), 3030 type = pk_type, 3031 intended_reviewer = self._PRW_intended_reviewer.GetData(), 3032 val_num = v_num, 3033 val_alpha = v_al, 3034 unit = self._PRW_units.GetValue() 3035 ) 3036 3037 tr['clin_when'] = self._DPRW_evaluated.GetData().get_pydt() 3038 3039 ctrls = [ 3040 ('abnormality_indicator', self._PRW_abnormality_indicator), 3041 ('note_test_org', self._TCTRL_note_test_org), 3042 ('comment', self._TCTRL_narrative), 3043 ('val_normal_range', self._TCTRL_normal_range), 3044 ('val_target_range', self._TCTRL_target_range), 3045 ('norm_ref_group', self._TCTRL_norm_ref_group) 3046 ] 3047 for field, widget in ctrls: 3048 tr[field] = widget.GetValue().strip() 3049 3050 ctrls = [ 3051 ('val_normal_min', self._TCTRL_normal_min), 3052 ('val_normal_max', self._TCTRL_normal_max), 3053 ('val_target_min', self._TCTRL_target_min), 3054 ('val_target_max', self._TCTRL_target_max) 3055 ] 3056 for field, widget in ctrls: 3057 val = widget.GetValue().strip() 3058 if val == '': 3059 tr[field] = None 3060 else: 3061 tr[field] = decimal.Decimal(val.replace(',', '.', 1)) 3062 3063 tr.save_payload() 3064 3065 if self._CHBOX_review.GetValue() is True: 3066 tr.set_review ( 3067 technically_abnormal = self._CHBOX_abnormal.GetValue(), 3068 clinically_relevant = self._CHBOX_relevant.GetValue(), 3069 comment = gmTools.none_if(self._TCTRL_review_comment.GetValue().strip(), ''), 3070 make_me_responsible = False 3071 ) 3072 3073 self.data = tr 3074 3075 # wx.CallAfter ( 3076 # plot_adjacent_measurements, 3077 # test = self.data, 3078 # plot_singular_result = False, 3079 # use_default_template = True 3080 # ) 3081 3082 return True
3083 #--------------------------------------------------------
3084 - def _save_as_update(self):
3085 3086 success, result = gmTools.input2decimal(self._TCTRL_result.GetValue()) 3087 if success: 3088 v_num = result 3089 v_al = None 3090 else: 3091 v_num = None 3092 v_al = self._TCTRL_result.GetValue().strip() 3093 3094 pk_type = self._PRW_test.GetData() 3095 if pk_type is None: 3096 abbrev = self._PRW_test.GetValue().strip() 3097 name = self._PRW_test.GetValue().strip() 3098 unit = gmTools.coalesce(self._PRW_units.GetData(), self._PRW_units.GetValue()).strip() 3099 lab = manage_measurement_orgs ( 3100 parent = self, 3101 msg = _('Please select (or create) a lab for the new test type [%s in %s]') % (name, unit) 3102 ) 3103 if lab is not None: 3104 lab = lab['pk_test_org'] 3105 tt = gmPathLab.create_measurement_type ( 3106 lab = None, 3107 abbrev = abbrev, 3108 name = name, 3109 unit = unit 3110 ) 3111 pk_type = tt['pk_test_type'] 3112 3113 tr = self.data 3114 3115 tr['pk_episode'] = self._PRW_problem.GetData(can_create=True, is_open=False) 3116 tr['pk_test_type'] = pk_type 3117 tr['pk_intended_reviewer'] = self._PRW_intended_reviewer.GetData() 3118 tr['val_num'] = v_num 3119 tr['val_alpha'] = v_al 3120 tr['val_unit'] = gmTools.coalesce(self._PRW_units.GetData(), self._PRW_units.GetValue()).strip() 3121 tr['clin_when'] = self._DPRW_evaluated.GetData().get_pydt() 3122 3123 ctrls = [ 3124 ('abnormality_indicator', self._PRW_abnormality_indicator), 3125 ('note_test_org', self._TCTRL_note_test_org), 3126 ('comment', self._TCTRL_narrative), 3127 ('val_normal_range', self._TCTRL_normal_range), 3128 ('val_target_range', self._TCTRL_target_range), 3129 ('norm_ref_group', self._TCTRL_norm_ref_group) 3130 ] 3131 for field, widget in ctrls: 3132 tr[field] = widget.GetValue().strip() 3133 3134 ctrls = [ 3135 ('val_normal_min', self._TCTRL_normal_min), 3136 ('val_normal_max', self._TCTRL_normal_max), 3137 ('val_target_min', self._TCTRL_target_min), 3138 ('val_target_max', self._TCTRL_target_max) 3139 ] 3140 for field, widget in ctrls: 3141 val = widget.GetValue().strip() 3142 if val == '': 3143 tr[field] = None 3144 else: 3145 tr[field] = decimal.Decimal(val.replace(',', '.', 1)) 3146 3147 tr.save_payload() 3148 3149 if self._CHBOX_review.GetValue() is True: 3150 tr.set_review ( 3151 technically_abnormal = self._CHBOX_abnormal.GetValue(), 3152 clinically_relevant = self._CHBOX_relevant.GetValue(), 3153 comment = gmTools.none_if(self._TCTRL_review_comment.GetValue().strip(), ''), 3154 make_me_responsible = False 3155 ) 3156 3157 # wx.CallAfter ( 3158 # plot_adjacent_measurements, 3159 # test = self.data, 3160 # plot_singular_result = False, 3161 # use_default_template = True 3162 # ) 3163 3164 return True
3165 #-------------------------------------------------------- 3166 # event handling 3167 #--------------------------------------------------------
3168 - def __register_interests(self):
3169 self._PRW_test.add_callback_on_lose_focus(self._on_leave_test_prw) 3170 self._PRW_abnormality_indicator.add_callback_on_lose_focus(self._on_leave_indicator_prw) 3171 self._PRW_units.add_callback_on_lose_focus(self._on_leave_unit_prw)
3172 #--------------------------------------------------------
3173 - def _on_leave_test_prw(self):
3174 self.__refresh_loinc_info() 3175 self.__refresh_previous_value() 3176 self.__update_units_context() 3177 # only works if we've got a unit set 3178 self.__update_normal_range() 3179 self.__update_clinical_range()
3180 #--------------------------------------------------------
3181 - def _on_leave_unit_prw(self):
3182 # maybe we've got a unit now ? 3183 self.__update_normal_range() 3184 self.__update_clinical_range()
3185 #--------------------------------------------------------
3186 - def _on_leave_indicator_prw(self):
3187 # if the user hasn't explicitly enabled reviewing 3188 if not self._CHBOX_review.GetValue(): 3189 self._CHBOX_abnormal.SetValue(self._PRW_abnormality_indicator.GetValue().strip() != '')
3190 #--------------------------------------------------------
3191 - def _on_review_box_checked(self, evt):
3192 self._CHBOX_abnormal.Enable(self._CHBOX_review.GetValue()) 3193 self._CHBOX_relevant.Enable(self._CHBOX_review.GetValue()) 3194 self._TCTRL_review_comment.Enable(self._CHBOX_review.GetValue())
3195 #--------------------------------------------------------
3196 - def _on_test_info_button_pressed(self, event):
3197 pk = self._PRW_test.GetData() 3198 if pk is not None: 3199 tt = gmPathLab.cMeasurementType(aPK_obj = pk) 3200 search_term = '%s %s %s' % ( 3201 tt['name'], 3202 tt['abbrev'], 3203 gmTools.coalesce(tt['loinc'], '') 3204 ) 3205 else: 3206 search_term = self._PRW_test.GetValue() 3207 3208 search_term = search_term.replace(' ', '+') 3209 3210 call_browser_on_measurement_type(measurement_type = search_term)
3211 #--------------------------------------------------------
3212 - def _on_manage_episodes_button_pressed(self, event):
3215 #-------------------------------------------------------- 3216 # internal helpers 3217 #--------------------------------------------------------
3218 - def __update_units_context(self):
3219 3220 if self._PRW_test.GetData() is None: 3221 self._PRW_units.unset_context(context = 'pk_type') 3222 self._PRW_units.unset_context(context = 'loinc') 3223 if self._PRW_test.GetValue().strip() == '': 3224 self._PRW_units.unset_context(context = 'test_name') 3225 else: 3226 self._PRW_units.set_context(context = 'test_name', val = self._PRW_test.GetValue().strip()) 3227 return 3228 3229 tt = self._PRW_test.GetData(as_instance = True) 3230 3231 self._PRW_units.set_context(context = 'pk_type', val = tt['pk_test_type']) 3232 self._PRW_units.set_context(context = 'test_name', val = tt['name']) 3233 3234 if tt['loinc'] is not None: 3235 self._PRW_units.set_context(context = 'loinc', val = tt['loinc']) 3236 3237 # closest unit 3238 if self._PRW_units.GetValue().strip() == '': 3239 clin_when = self._DPRW_evaluated.GetData() 3240 if clin_when is None: 3241 unit = tt.temporally_closest_unit 3242 else: 3243 clin_when = clin_when.get_pydt() 3244 unit = tt.get_temporally_closest_unit(timestamp = clin_when) 3245 if unit is None: 3246 self._PRW_units.SetText('', unit, True) 3247 else: 3248 self._PRW_units.SetText(unit, unit, True)
3249 3250 #--------------------------------------------------------
3251 - def __update_normal_range(self):
3252 unit = self._PRW_units.GetValue().strip() 3253 if unit == '': 3254 return 3255 if self._PRW_test.GetData() is None: 3256 return 3257 for ctrl in [self._TCTRL_normal_min, self._TCTRL_normal_max, self._TCTRL_normal_range, self._TCTRL_norm_ref_group]: 3258 if ctrl.GetValue().strip() != '': 3259 return 3260 tt = self._PRW_test.GetData(as_instance = True) 3261 test_w_range = tt.get_temporally_closest_normal_range ( 3262 unit, 3263 timestamp = self._DPRW_evaluated.GetData().get_pydt() 3264 ) 3265 if test_w_range is None: 3266 return 3267 self._TCTRL_normal_min.SetValue(str(gmTools.coalesce(test_w_range['val_normal_min'], ''))) 3268 self._TCTRL_normal_max.SetValue(str(gmTools.coalesce(test_w_range['val_normal_max'], ''))) 3269 self._TCTRL_normal_range.SetValue(gmTools.coalesce(test_w_range['val_normal_range'], '')) 3270 self._TCTRL_norm_ref_group.SetValue(gmTools.coalesce(test_w_range['norm_ref_group'], ''))
3271 3272 #--------------------------------------------------------
3273 - def __update_clinical_range(self):
3274 unit = self._PRW_units.GetValue().strip() 3275 if unit == '': 3276 return 3277 if self._PRW_test.GetData() is None: 3278 return 3279 for ctrl in [self._TCTRL_target_min, self._TCTRL_target_max, self._TCTRL_target_range]: 3280 if ctrl.GetValue().strip() != '': 3281 return 3282 tt = self._PRW_test.GetData(as_instance = True) 3283 test_w_range = tt.get_temporally_closest_target_range ( 3284 unit, 3285 gmPerson.gmCurrentPatient().ID, 3286 timestamp = self._DPRW_evaluated.GetData().get_pydt() 3287 ) 3288 if test_w_range is None: 3289 return 3290 self._TCTRL_target_min.SetValue(str(gmTools.coalesce(test_w_range['val_target_min'], ''))) 3291 self._TCTRL_target_max.SetValue(str(gmTools.coalesce(test_w_range['val_target_max'], ''))) 3292 self._TCTRL_target_range.SetValue(gmTools.coalesce(test_w_range['val_target_range'], ''))
3293 3294 #--------------------------------------------------------
3295 - def __refresh_loinc_info(self):
3296 3297 self._TCTRL_loinc.SetValue('') 3298 3299 if self._PRW_test.GetData() is None: 3300 return 3301 3302 tt = self._PRW_test.GetData(as_instance = True) 3303 3304 if tt['loinc'] is None: 3305 return 3306 3307 info = gmLOINC.loinc2term(loinc = tt['loinc']) 3308 if len(info) == 0: 3309 self._TCTRL_loinc.SetValue('') 3310 return 3311 3312 self._TCTRL_loinc.SetValue('%s: %s' % (tt['loinc'], info[0]))
3313 #--------------------------------------------------------
3314 - def __refresh_previous_value(self):
3315 self._TCTRL_previous_value.SetValue('') 3316 # it doesn't make much sense to show the most 3317 # recent value when editing an existing one 3318 if self.data is not None: 3319 return 3320 if self._PRW_test.GetData() is None: 3321 return 3322 tt = self._PRW_test.GetData(as_instance = True) 3323 most_recent = tt.get_most_recent_results ( 3324 no_of_results = 1, 3325 patient = gmPerson.gmCurrentPatient().ID 3326 ) 3327 if most_recent is None: 3328 return 3329 self._TCTRL_previous_value.SetValue(_('%s ago: %s%s%s - %s%s') % ( 3330 gmDateTime.format_interval_medically(gmDateTime.pydt_now_here() - most_recent['clin_when']), 3331 most_recent['unified_val'], 3332 most_recent['val_unit'], 3333 gmTools.coalesce(most_recent['abnormality_indicator'], '', ' (%s)'), 3334 most_recent['abbrev_tt'], 3335 gmTools.coalesce(most_recent.formatted_range, '', ' [%s]') 3336 )) 3337 self._TCTRL_previous_value.SetToolTip(most_recent.format ( 3338 with_review = True, 3339 with_evaluation = False, 3340 with_ranges = True, 3341 with_episode = True, 3342 with_type_details=True 3343 ))
3344 3345 #================================================================ 3346 # measurement type handling 3347 #================================================================
3348 -def pick_measurement_types(parent=None, msg=None, right_column=None, picks=None):
3349 3350 if parent is None: 3351 parent = wx.GetApp().GetTopWindow() 3352 3353 if msg is None: 3354 msg = _('Pick the relevant measurement types.') 3355 3356 if right_column is None: 3357 right_columns = [_('Picked')] 3358 else: 3359 right_columns = [right_column] 3360 3361 picker = gmListWidgets.cItemPickerDlg(parent, -1, msg = msg) 3362 picker.set_columns(columns = [_('Known measurement types')], columns_right = right_columns) 3363 types = gmPathLab.get_measurement_types(order_by = 'unified_abbrev') 3364 picker.set_choices ( 3365 choices = [ 3366 '%s: %s%s' % ( 3367 t['unified_abbrev'], 3368 t['unified_name'], 3369 gmTools.coalesce(t['name_org'], '', ' (%s)') 3370 ) 3371 for t in types 3372 ], 3373 data = types 3374 ) 3375 if picks is not None: 3376 picker.set_picks ( 3377 picks = [ 3378 '%s: %s%s' % ( 3379 p['unified_abbrev'], 3380 p['unified_name'], 3381 gmTools.coalesce(p['name_org'], '', ' (%s)') 3382 ) 3383 for p in picks 3384 ], 3385 data = picks 3386 ) 3387 result = picker.ShowModal() 3388 3389 if result == wx.ID_CANCEL: 3390 picker.Destroy() 3391 return None 3392 3393 picks = picker.picks 3394 picker.Destroy() 3395 return picks
3396 3397 #----------------------------------------------------------------
3398 -def manage_measurement_types(parent=None):
3399 3400 if parent is None: 3401 parent = wx.GetApp().GetTopWindow() 3402 3403 #------------------------------------------------------------ 3404 def edit(test_type=None): 3405 ea = cMeasurementTypeEAPnl(parent, -1, type = test_type) 3406 dlg = gmEditArea.cGenericEditAreaDlg2 ( 3407 parent = parent, 3408 id = -1, 3409 edit_area = ea, 3410 single_entry = gmTools.bool2subst((test_type is None), False, True) 3411 ) 3412 dlg.SetTitle(gmTools.coalesce(test_type, _('Adding measurement type'), _('Editing measurement type'))) 3413 3414 if dlg.ShowModal() == wx.ID_OK: 3415 dlg.Destroy() 3416 return True 3417 3418 dlg.Destroy() 3419 return False
3420 3421 #------------------------------------------------------------ 3422 def delete(measurement_type): 3423 if measurement_type.in_use: 3424 gmDispatcher.send ( 3425 signal = 'statustext', 3426 beep = True, 3427 msg = _('Cannot delete measurement type [%s (%s)] because it is in use.') % (measurement_type['name'], measurement_type['abbrev']) 3428 ) 3429 return False 3430 gmPathLab.delete_measurement_type(measurement_type = measurement_type['pk_test_type']) 3431 return True 3432 3433 #------------------------------------------------------------ 3434 def get_tooltip(test_type): 3435 return test_type.format() 3436 3437 #------------------------------------------------------------ 3438 def manage_aggregates(test_type): 3439 manage_meta_test_types(parent = parent) 3440 return False 3441 3442 #------------------------------------------------------------ 3443 def manage_panels_of_type(test_type): 3444 if test_type['loinc'] is None: 3445 return False 3446 all_panels = gmPathLab.get_test_panels(order_by = 'description') 3447 curr_panels = test_type.test_panels 3448 if curr_panels is None: 3449 curr_panels = [] 3450 panel_candidates = [ p for p in all_panels if p['pk_test_panel'] not in [ 3451 c_pnl['pk_test_panel'] for c_pnl in curr_panels 3452 ] ] 3453 picker = gmListWidgets.cItemPickerDlg(parent, -1, title = 'Panels with [%s]' % test_type['abbrev']) 3454 picker.set_columns(['Panels available'], ['Panels [%s] is to be on' % test_type['abbrev']]) 3455 picker.set_choices ( 3456 choices = [ u'%s (%s)' % (c['description'], gmTools.coalesce(c['comment'], '')) for c in panel_candidates ], 3457 data = panel_candidates 3458 ) 3459 picker.set_picks ( 3460 picks = [ u'%s (%s)' % (c['description'], gmTools.coalesce(c['comment'], '')) for c in curr_panels ], 3461 data = curr_panels 3462 ) 3463 exit_type = picker.ShowModal() 3464 if exit_type == wx.ID_CANCEL: 3465 return False 3466 3467 # add picked panels which aren't currently in the panel list 3468 panels2add = [ p for p in picker.picks if p['pk_test_panel'] not in [ 3469 c_pnl['pk_test_panel'] for c_pnl in curr_panels 3470 ] ] 3471 # remove unpicked panels off the current panel list 3472 panels2remove = [ p for p in curr_panels if p['pk_test_panel'] not in [ 3473 picked_pnl['pk_test_panel'] for picked_pnl in picker.picks 3474 ] ] 3475 for new_panel in panels2add: 3476 new_panel.add_loinc(test_type['loinc']) 3477 for stale_panel in panels2remove: 3478 stale_panel.remove_loinc(test_type['loinc']) 3479 3480 return True 3481 3482 #------------------------------------------------------------ 3483 def refresh(lctrl): 3484 mtypes = gmPathLab.get_measurement_types(order_by = 'name, abbrev') 3485 items = [ [ 3486 m['abbrev'], 3487 m['name'], 3488 gmTools.coalesce(m['reference_unit'], ''), 3489 gmTools.coalesce(m['loinc'], ''), 3490 gmTools.coalesce(m['comment_type'], ''), 3491 gmTools.coalesce(m['name_org'], '?'), 3492 gmTools.coalesce(m['comment_org'], ''), 3493 m['pk_test_type'] 3494 ] for m in mtypes ] 3495 lctrl.set_string_items(items) 3496 lctrl.set_data(mtypes) 3497 3498 #------------------------------------------------------------ 3499 gmListWidgets.get_choices_from_list ( 3500 parent = parent, 3501 caption = _('Measurement types.'), 3502 columns = [ _('Abbrev'), _('Name'), _('Unit'), _('LOINC'), _('Comment'), _('Org'), _('Comment'), '#' ], 3503 single_selection = True, 3504 refresh_callback = refresh, 3505 edit_callback = edit, 3506 new_callback = edit, 3507 delete_callback = delete, 3508 list_tooltip_callback = get_tooltip, 3509 left_extra_button = (_('%s &Aggregate') % gmTools.u_sum, _('Manage aggregations (%s) of tests into groups.') % gmTools.u_sum, manage_aggregates), 3510 middle_extra_button = (_('Select panels'), _('Select panels the focussed test type is to belong to.'), manage_panels_of_type) 3511 ) 3512 3513 #----------------------------------------------------------------
3514 -class cMeasurementTypePhraseWheel(gmPhraseWheel.cPhraseWheel):
3515
3516 - def __init__(self, *args, **kwargs):
3517 3518 query = """ 3519 SELECT DISTINCT ON (field_label) 3520 pk_test_type AS data, 3521 name 3522 || ' (' 3523 || coalesce ( 3524 (SELECT unit || ' @ ' || organization FROM clin.v_test_orgs c_vto WHERE c_vto.pk_test_org = c_vtt.pk_test_org), 3525 '%(in_house)s' 3526 ) 3527 || ')' 3528 AS field_label, 3529 name 3530 || ' (' 3531 || abbrev || ', ' 3532 || coalesce(abbrev_meta || ': ' || name_meta || ', ', '') 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 list_label 3539 FROM 3540 clin.v_test_types c_vtt 3541 WHERE 3542 abbrev_meta %%(fragment_condition)s 3543 OR 3544 name_meta %%(fragment_condition)s 3545 OR 3546 abbrev %%(fragment_condition)s 3547 OR 3548 name %%(fragment_condition)s 3549 ORDER BY field_label 3550 LIMIT 50""" % {'in_house': _('generic / in house lab')} 3551 3552 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 3553 mp.setThresholds(1, 2, 4) 3554 mp.word_separators = '[ \t:@]+' 3555 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 3556 self.matcher = mp 3557 self.SetToolTip(_('Select the type of measurement.')) 3558 self.selection_only = False
3559 3560 #------------------------------------------------------------
3561 - def _data2instance(self):
3562 if self.GetData() is None: 3563 return None 3564 3565 return gmPathLab.cMeasurementType(aPK_obj = self.GetData())
3566 3567 #---------------------------------------------------------------- 3568 from Gnumed.wxGladeWidgets import wxgMeasurementTypeEAPnl 3569
3570 -class cMeasurementTypeEAPnl(wxgMeasurementTypeEAPnl.wxgMeasurementTypeEAPnl, gmEditArea.cGenericEditAreaMixin):
3571
3572 - def __init__(self, *args, **kwargs):
3573 3574 try: 3575 data = kwargs['type'] 3576 del kwargs['type'] 3577 except KeyError: 3578 data = None 3579 3580 wxgMeasurementTypeEAPnl.wxgMeasurementTypeEAPnl.__init__(self, *args, **kwargs) 3581 gmEditArea.cGenericEditAreaMixin.__init__(self) 3582 self.mode = 'new' 3583 self.data = data 3584 if data is not None: 3585 self.mode = 'edit' 3586 3587 self.__init_ui()
3588 3589 #----------------------------------------------------------------
3590 - def __init_ui(self):
3591 3592 # name phraseweel 3593 query = """ 3594 select distinct on (name) 3595 pk, 3596 name 3597 from clin.test_type 3598 where 3599 name %(fragment_condition)s 3600 order by name 3601 limit 50""" 3602 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 3603 mp.setThresholds(1, 2, 4) 3604 self._PRW_name.matcher = mp 3605 self._PRW_name.selection_only = False 3606 self._PRW_name.add_callback_on_lose_focus(callback = self._on_name_lost_focus) 3607 3608 # abbreviation 3609 query = """ 3610 select distinct on (abbrev) 3611 pk, 3612 abbrev 3613 from clin.test_type 3614 where 3615 abbrev %(fragment_condition)s 3616 order by abbrev 3617 limit 50""" 3618 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 3619 mp.setThresholds(1, 2, 3) 3620 self._PRW_abbrev.matcher = mp 3621 self._PRW_abbrev.selection_only = False 3622 3623 # unit 3624 self._PRW_reference_unit.selection_only = False 3625 3626 # loinc 3627 mp = gmLOINC.cLOINCMatchProvider() 3628 mp.setThresholds(1, 2, 4) 3629 #mp.print_queries = True 3630 #mp.word_separators = '[ \t:@]+' 3631 self._PRW_loinc.matcher = mp 3632 self._PRW_loinc.selection_only = False 3633 self._PRW_loinc.add_callback_on_lose_focus(callback = self._on_loinc_lost_focus)
3634 3635 #----------------------------------------------------------------
3636 - def _on_name_lost_focus(self):
3637 3638 test = self._PRW_name.GetValue().strip() 3639 3640 if test == '': 3641 self._PRW_reference_unit.unset_context(context = 'test_name') 3642 return 3643 3644 self._PRW_reference_unit.set_context(context = 'test_name', val = test)
3645 3646 #----------------------------------------------------------------
3647 - def _on_loinc_lost_focus(self):
3648 loinc = self._PRW_loinc.GetData() 3649 3650 if loinc is None: 3651 self._TCTRL_loinc_info.SetValue('') 3652 self._PRW_reference_unit.unset_context(context = 'loinc') 3653 return 3654 3655 self._PRW_reference_unit.set_context(context = 'loinc', val = loinc) 3656 3657 info = gmLOINC.loinc2term(loinc = loinc) 3658 if len(info) == 0: 3659 self._TCTRL_loinc_info.SetValue('') 3660 return 3661 3662 self._TCTRL_loinc_info.SetValue(info[0])
3663 3664 #---------------------------------------------------------------- 3665 # generic Edit Area mixin API 3666 #----------------------------------------------------------------
3667 - def _valid_for_save(self):
3668 3669 has_errors = False 3670 for field in [self._PRW_name, self._PRW_abbrev, self._PRW_reference_unit]: 3671 if field.GetValue().strip() in ['', None]: 3672 has_errors = True 3673 field.display_as_valid(valid = False) 3674 else: 3675 field.display_as_valid(valid = True) 3676 field.Refresh() 3677 3678 return (not has_errors)
3679 3680 #----------------------------------------------------------------
3681 - def _save_as_new(self):
3682 3683 pk_org = self._PRW_test_org.GetData() 3684 if pk_org is None: 3685 pk_org = gmPathLab.create_test_org ( 3686 name = gmTools.none_if(self._PRW_test_org.GetValue().strip(), '') 3687 )['pk_test_org'] 3688 3689 tt = gmPathLab.create_measurement_type ( 3690 lab = pk_org, 3691 abbrev = self._PRW_abbrev.GetValue().strip(), 3692 name = self._PRW_name.GetValue().strip(), 3693 unit = gmTools.coalesce ( 3694 self._PRW_reference_unit.GetData(), 3695 self._PRW_reference_unit.GetValue() 3696 ).strip() 3697 ) 3698 if self._PRW_loinc.GetData() is not None: 3699 tt['loinc'] = gmTools.none_if(self._PRW_loinc.GetData().strip(), '') 3700 else: 3701 tt['loinc'] = gmTools.none_if(self._PRW_loinc.GetValue().strip(), '') 3702 tt['comment_type'] = gmTools.none_if(self._TCTRL_comment_type.GetValue().strip(), '') 3703 tt['pk_meta_test_type'] = self._PRW_meta_type.GetData() 3704 3705 tt.save() 3706 3707 self.data = tt 3708 3709 return True
3710 #----------------------------------------------------------------
3711 - def _save_as_update(self):
3712 3713 pk_org = self._PRW_test_org.GetData() 3714 if pk_org is None: 3715 pk_org = gmPathLab.create_test_org ( 3716 name = gmTools.none_if(self._PRW_test_org.GetValue().strip(), '') 3717 )['pk_test_org'] 3718 3719 self.data['pk_test_org'] = pk_org 3720 self.data['abbrev'] = self._PRW_abbrev.GetValue().strip() 3721 self.data['name'] = self._PRW_name.GetValue().strip() 3722 self.data['reference_unit'] = gmTools.coalesce ( 3723 self._PRW_reference_unit.GetData(), 3724 self._PRW_reference_unit.GetValue() 3725 ).strip() 3726 old_loinc = self.data['loinc'] 3727 if self._PRW_loinc.GetData() is not None: 3728 self.data['loinc'] = gmTools.none_if(self._PRW_loinc.GetData().strip(), '') 3729 else: 3730 self.data['loinc'] = gmTools.none_if(self._PRW_loinc.GetValue().strip(), '') 3731 new_loinc = self.data['loinc'] 3732 self.data['comment_type'] = gmTools.none_if(self._TCTRL_comment_type.GetValue().strip(), '') 3733 self.data['pk_meta_test_type'] = self._PRW_meta_type.GetData() 3734 self.data.save() 3735 3736 # was it, AND can it be, on any panel ? 3737 if None not in [old_loinc, new_loinc]: 3738 # would it risk being dropped from any panel ? 3739 if new_loinc != old_loinc: 3740 for panel in gmPathLab.get_test_panels(loincs = [old_loinc]): 3741 pnl_loincs = panel.included_loincs 3742 if new_loinc not in pnl_loincs: 3743 pnl_loincs.append(new_loinc) 3744 panel.included_loincs = pnl_loincs 3745 # do not remove old_loinc as it may sit on another 3746 # test type which we haven't removed it from yet 3747 3748 return True
3749 3750 #----------------------------------------------------------------
3751 - def _refresh_as_new(self):
3752 self._PRW_name.SetText('', None, True) 3753 self._on_name_lost_focus() 3754 self._PRW_abbrev.SetText('', None, True) 3755 self._PRW_reference_unit.SetText('', None, True) 3756 self._PRW_loinc.SetText('', None, True) 3757 self._on_loinc_lost_focus() 3758 self._TCTRL_comment_type.SetValue('') 3759 self._PRW_test_org.SetText('', None, True) 3760 self._PRW_meta_type.SetText('', None, True) 3761 3762 self._PRW_name.SetFocus()
3763 #----------------------------------------------------------------
3764 - def _refresh_from_existing(self):
3765 self._PRW_name.SetText(self.data['name'], self.data['name'], True) 3766 self._on_name_lost_focus() 3767 self._PRW_abbrev.SetText(self.data['abbrev'], self.data['abbrev'], True) 3768 self._PRW_reference_unit.SetText ( 3769 gmTools.coalesce(self.data['reference_unit'], ''), 3770 self.data['reference_unit'], 3771 True 3772 ) 3773 self._PRW_loinc.SetText ( 3774 gmTools.coalesce(self.data['loinc'], ''), 3775 self.data['loinc'], 3776 True 3777 ) 3778 self._on_loinc_lost_focus() 3779 self._TCTRL_comment_type.SetValue(gmTools.coalesce(self.data['comment_type'], '')) 3780 self._PRW_test_org.SetText ( 3781 gmTools.coalesce(self.data['pk_test_org'], '', self.data['name_org']), 3782 self.data['pk_test_org'], 3783 True 3784 ) 3785 if self.data['pk_meta_test_type'] is None: 3786 self._PRW_meta_type.SetText('', None, True) 3787 else: 3788 self._PRW_meta_type.SetText('%s: %s' % (self.data['abbrev_meta'], self.data['name_meta']), self.data['pk_meta_test_type'], True) 3789 3790 self._PRW_name.SetFocus()
3791 #----------------------------------------------------------------
3793 self._refresh_as_new() 3794 self._PRW_test_org.SetText ( 3795 gmTools.coalesce(self.data['pk_test_org'], '', self.data['name_org']), 3796 self.data['pk_test_org'], 3797 True 3798 ) 3799 self._PRW_name.SetFocus()
3800 3801 #================================================================ 3802 _SQL_units_from_test_results = """ 3803 -- via clin.v_test_results.pk_type (for types already used in results) 3804 SELECT 3805 val_unit AS data, 3806 val_unit AS field_label, 3807 val_unit || ' (' || name_tt || ')' AS list_label, 3808 1 AS rank 3809 FROM 3810 clin.v_test_results 3811 WHERE 3812 ( 3813 val_unit %(fragment_condition)s 3814 OR 3815 reference_unit %(fragment_condition)s 3816 ) 3817 %(ctxt_type_pk)s 3818 %(ctxt_test_name)s 3819 """ 3820 3821 _SQL_units_from_test_types = """ 3822 -- via clin.test_type (for types not yet used in results) 3823 SELECT 3824 reference_unit AS data, 3825 reference_unit AS field_label, 3826 reference_unit || ' (' || name || ')' AS list_label, 3827 2 AS rank 3828 FROM 3829 clin.test_type 3830 WHERE 3831 reference_unit %(fragment_condition)s 3832 %(ctxt_ctt)s 3833 """ 3834 3835 _SQL_units_from_loinc_ipcc = """ 3836 -- via ref.loinc.ipcc_units 3837 SELECT 3838 ipcc_units AS data, 3839 ipcc_units AS field_label, 3840 ipcc_units || ' (LOINC.ipcc: ' || term || ')' AS list_label, 3841 3 AS rank 3842 FROM 3843 ref.loinc 3844 WHERE 3845 ipcc_units %(fragment_condition)s 3846 %(ctxt_loinc)s 3847 %(ctxt_loinc_term)s 3848 """ 3849 3850 _SQL_units_from_loinc_submitted = """ 3851 -- via ref.loinc.submitted_units 3852 SELECT 3853 submitted_units AS data, 3854 submitted_units AS field_label, 3855 submitted_units || ' (LOINC.submitted:' || term || ')' AS list_label, 3856 3 AS rank 3857 FROM 3858 ref.loinc 3859 WHERE 3860 submitted_units %(fragment_condition)s 3861 %(ctxt_loinc)s 3862 %(ctxt_loinc_term)s 3863 """ 3864 3865 _SQL_units_from_loinc_example = """ 3866 -- via ref.loinc.example_units 3867 SELECT 3868 example_units AS data, 3869 example_units AS field_label, 3870 example_units || ' (LOINC.example: ' || term || ')' AS list_label, 3871 3 AS rank 3872 FROM 3873 ref.loinc 3874 WHERE 3875 example_units %(fragment_condition)s 3876 %(ctxt_loinc)s 3877 %(ctxt_loinc_term)s 3878 """ 3879 3880 _SQL_units_from_substance_doses = """ 3881 -- via ref.v_substance_doses.unit 3882 SELECT 3883 unit AS data, 3884 unit AS field_label, 3885 unit || ' (' || substance || ')' AS list_label, 3886 2 AS rank 3887 FROM 3888 ref.v_substance_doses 3889 WHERE 3890 unit %(fragment_condition)s 3891 %(ctxt_substance)s 3892 """ 3893 3894 _SQL_units_from_substance_doses2 = """ 3895 -- via ref.v_substance_doses.dose_unit 3896 SELECT 3897 dose_unit AS data, 3898 dose_unit AS field_label, 3899 dose_unit || ' (' || substance || ')' AS list_label, 3900 2 AS rank 3901 FROM 3902 ref.v_substance_doses 3903 WHERE 3904 dose_unit %(fragment_condition)s 3905 %(ctxt_substance)s 3906 """ 3907 3908 #----------------------------------------------------------------
3909 -class cUnitPhraseWheel(gmPhraseWheel.cPhraseWheel):
3910
3911 - def __init__(self, *args, **kwargs):
3912 3913 query = """ 3914 SELECT DISTINCT ON (data) 3915 data, 3916 field_label, 3917 list_label 3918 FROM ( 3919 3920 SELECT 3921 data, 3922 field_label, 3923 list_label, 3924 rank 3925 FROM ( 3926 (%s) UNION ALL 3927 (%s) UNION ALL 3928 (%s) UNION ALL 3929 (%s) UNION ALL 3930 (%s) UNION ALL 3931 (%s) UNION ALL 3932 (%s) 3933 ) AS all_matching_units 3934 WHERE data IS NOT NULL 3935 ORDER BY rank, list_label 3936 3937 ) AS ranked_matching_units 3938 LIMIT 50""" % ( 3939 _SQL_units_from_test_results, 3940 _SQL_units_from_test_types, 3941 _SQL_units_from_loinc_ipcc, 3942 _SQL_units_from_loinc_submitted, 3943 _SQL_units_from_loinc_example, 3944 _SQL_units_from_substance_doses, 3945 _SQL_units_from_substance_doses2 3946 ) 3947 3948 ctxt = { 3949 'ctxt_type_pk': { 3950 'where_part': 'AND pk_test_type = %(pk_type)s', 3951 'placeholder': 'pk_type' 3952 }, 3953 'ctxt_test_name': { 3954 'where_part': 'AND %(test_name)s IN (name_tt, name_meta, abbrev_meta)', 3955 'placeholder': 'test_name' 3956 }, 3957 'ctxt_ctt': { 3958 'where_part': 'AND %(test_name)s IN (name, abbrev)', 3959 'placeholder': 'test_name' 3960 }, 3961 'ctxt_loinc': { 3962 'where_part': 'AND code = %(loinc)s', 3963 'placeholder': 'loinc' 3964 }, 3965 'ctxt_loinc_term': { 3966 'where_part': 'AND term ~* %(test_name)s', 3967 'placeholder': 'test_name' 3968 }, 3969 'ctxt_substance': { 3970 'where_part': 'AND description ~* %(substance)s', 3971 'placeholder': 'substance' 3972 } 3973 } 3974 3975 mp = gmMatchProvider.cMatchProvider_SQL2(queries = query, context = ctxt) 3976 mp.setThresholds(1, 2, 4) 3977 #mp.print_queries = True 3978 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 3979 self.matcher = mp 3980 self.SetToolTip(_('Select the desired unit for the amount or measurement.')) 3981 self.selection_only = False 3982 self.phrase_separators = '[;|]+'
3983 3984 #================================================================ 3985 3986 #================================================================
3987 -class cTestResultIndicatorPhraseWheel(gmPhraseWheel.cPhraseWheel):
3988
3989 - def __init__(self, *args, **kwargs):
3990 3991 query = """ 3992 select distinct abnormality_indicator, 3993 abnormality_indicator, abnormality_indicator 3994 from clin.v_test_results 3995 where 3996 abnormality_indicator %(fragment_condition)s 3997 order by abnormality_indicator 3998 limit 25""" 3999 4000 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 4001 mp.setThresholds(1, 1, 2) 4002 mp.ignored_chars = "[.'\\\[\]#$%_]+" + '"' 4003 mp.word_separators = '[ \t&:]+' 4004 gmPhraseWheel.cPhraseWheel.__init__ ( 4005 self, 4006 *args, 4007 **kwargs 4008 ) 4009 self.matcher = mp 4010 self.SetToolTip(_('Select an indicator for the level of abnormality.')) 4011 self.selection_only = False
4012 4013 #================================================================ 4014 # measurement org widgets / functions 4015 #----------------------------------------------------------------
4016 -def edit_measurement_org(parent=None, org=None):
4017 ea = cMeasurementOrgEAPnl(parent, -1) 4018 ea.data = org 4019 ea.mode = gmTools.coalesce(org, 'new', 'edit') 4020 dlg = gmEditArea.cGenericEditAreaDlg2(parent, -1, edit_area = ea) 4021 dlg.SetTitle(gmTools.coalesce(org, _('Adding new diagnostic org'), _('Editing diagnostic org'))) 4022 if dlg.ShowModal() == wx.ID_OK: 4023 dlg.Destroy() 4024 return True 4025 dlg.Destroy() 4026 return False
4027 #----------------------------------------------------------------
4028 -def manage_measurement_orgs(parent=None, msg=None):
4029 4030 if parent is None: 4031 parent = wx.GetApp().GetTopWindow() 4032 4033 #------------------------------------------------------------ 4034 def edit(org=None): 4035 return edit_measurement_org(parent = parent, org = org)
4036 #------------------------------------------------------------ 4037 def refresh(lctrl): 4038 orgs = gmPathLab.get_test_orgs() 4039 lctrl.set_string_items ([ 4040 (o['unit'], o['organization'], gmTools.coalesce(o['test_org_contact'], ''), gmTools.coalesce(o['comment'], ''), o['pk_test_org']) 4041 for o in orgs 4042 ]) 4043 lctrl.set_data(orgs) 4044 #------------------------------------------------------------ 4045 def delete(test_org): 4046 gmPathLab.delete_test_org(test_org = test_org['pk_test_org']) 4047 return True 4048 #------------------------------------------------------------ 4049 if msg is None: 4050 msg = _('\nThese are the diagnostic orgs (path labs etc) currently defined in GNUmed.\n\n') 4051 4052 return gmListWidgets.get_choices_from_list ( 4053 parent = parent, 4054 msg = msg, 4055 caption = _('Showing diagnostic orgs.'), 4056 columns = [_('Name'), _('Organization'), _('Contact'), _('Comment'), '#'], 4057 single_selection = True, 4058 refresh_callback = refresh, 4059 edit_callback = edit, 4060 new_callback = edit, 4061 delete_callback = delete 4062 ) 4063 4064 #---------------------------------------------------------------- 4065 from Gnumed.wxGladeWidgets import wxgMeasurementOrgEAPnl 4066
4067 -class cMeasurementOrgEAPnl(wxgMeasurementOrgEAPnl.wxgMeasurementOrgEAPnl, gmEditArea.cGenericEditAreaMixin):
4068
4069 - def __init__(self, *args, **kwargs):
4070 4071 try: 4072 data = kwargs['org'] 4073 del kwargs['org'] 4074 except KeyError: 4075 data = None 4076 4077 wxgMeasurementOrgEAPnl.wxgMeasurementOrgEAPnl.__init__(self, *args, **kwargs) 4078 gmEditArea.cGenericEditAreaMixin.__init__(self) 4079 4080 self.mode = 'new' 4081 self.data = data 4082 if data is not None: 4083 self.mode = 'edit'
4084 4085 #self.__init_ui() 4086 #---------------------------------------------------------------- 4087 # def __init_ui(self): 4088 # # adjust phrasewheels etc 4089 #---------------------------------------------------------------- 4090 # generic Edit Area mixin API 4091 #----------------------------------------------------------------
4092 - def _valid_for_save(self):
4093 has_errors = False 4094 if self._PRW_org_unit.GetData() is None: 4095 if self._PRW_org_unit.GetValue().strip() == '': 4096 has_errors = True 4097 self._PRW_org_unit.display_as_valid(valid = False) 4098 else: 4099 self._PRW_org_unit.display_as_valid(valid = True) 4100 else: 4101 self._PRW_org_unit.display_as_valid(valid = True) 4102 4103 return (not has_errors)
4104 #----------------------------------------------------------------
4105 - def _save_as_new(self):
4106 data = gmPathLab.create_test_org ( 4107 name = self._PRW_org_unit.GetValue().strip(), 4108 comment = self._TCTRL_comment.GetValue().strip(), 4109 pk_org_unit = self._PRW_org_unit.GetData() 4110 ) 4111 data['test_org_contact'] = self._TCTRL_contact.GetValue().strip() 4112 data.save() 4113 self.data = data 4114 return True
4115 #----------------------------------------------------------------
4116 - def _save_as_update(self):
4117 # get or create the org unit 4118 name = self._PRW_org_unit.GetValue().strip() 4119 org = gmOrganization.org_exists(organization = name) 4120 if org is None: 4121 org = gmOrganization.create_org ( 4122 organization = name, 4123 category = 'Laboratory' 4124 ) 4125 org_unit = gmOrganization.create_org_unit ( 4126 pk_organization = org['pk_org'], 4127 unit = name 4128 ) 4129 # update test_org fields 4130 self.data['pk_org_unit'] = org_unit['pk_org_unit'] 4131 self.data['test_org_contact'] = self._TCTRL_contact.GetValue().strip() 4132 self.data['comment'] = self._TCTRL_comment.GetValue().strip() 4133 self.data.save() 4134 return True
4135 #----------------------------------------------------------------
4136 - def _refresh_as_new(self):
4137 self._PRW_org_unit.SetText(value = '', data = None) 4138 self._TCTRL_contact.SetValue('') 4139 self._TCTRL_comment.SetValue('')
4140 #----------------------------------------------------------------
4141 - def _refresh_from_existing(self):
4142 self._PRW_org_unit.SetText(value = self.data['unit'], data = self.data['pk_org_unit']) 4143 self._TCTRL_contact.SetValue(gmTools.coalesce(self.data['test_org_contact'], '')) 4144 self._TCTRL_comment.SetValue(gmTools.coalesce(self.data['comment'], ''))
4145 #----------------------------------------------------------------
4147 self._refresh_as_new()
4148 #----------------------------------------------------------------
4149 - def _on_manage_orgs_button_pressed(self, event):
4151 4152 #----------------------------------------------------------------
4153 -class cMeasurementOrgPhraseWheel(gmPhraseWheel.cPhraseWheel):
4154
4155 - def __init__(self, *args, **kwargs):
4156 4157 query = """ 4158 SELECT DISTINCT ON (list_label) 4159 pk_test_org AS data, 4160 unit || ' (' || organization || ')' AS field_label, 4161 unit || ' @ ' || organization AS list_label 4162 FROM clin.v_test_orgs 4163 WHERE 4164 unit %(fragment_condition)s 4165 OR 4166 organization %(fragment_condition)s 4167 ORDER BY list_label 4168 LIMIT 50""" 4169 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 4170 mp.setThresholds(1, 2, 4) 4171 #mp.word_separators = '[ \t:@]+' 4172 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 4173 self.matcher = mp 4174 self.SetToolTip(_('The name of the path lab/diagnostic organisation.')) 4175 self.selection_only = False
4176 #------------------------------------------------------------
4177 - def _create_data(self):
4178 if self.GetData() is not None: 4179 _log.debug('data already set, not creating') 4180 return 4181 4182 if self.GetValue().strip() == '': 4183 _log.debug('cannot create new lab, missing name') 4184 return 4185 4186 lab = gmPathLab.create_test_org(name = self.GetValue().strip()) 4187 self.SetText(value = lab['unit'], data = lab['pk_test_org']) 4188 return
4189 #------------------------------------------------------------
4190 - def _data2instance(self):
4191 return gmPathLab.cTestOrg(aPK_obj = self.GetData())
4192 4193 #================================================================ 4194 # Meta test type widgets 4195 #----------------------------------------------------------------
4196 -def edit_meta_test_type(parent=None, meta_test_type=None):
4197 ea = cMetaTestTypeEAPnl(parent, -1) 4198 ea.data = meta_test_type 4199 ea.mode = gmTools.coalesce(meta_test_type, 'new', 'edit') 4200 dlg = gmEditArea.cGenericEditAreaDlg2 ( 4201 parent = parent, 4202 id = -1, 4203 edit_area = ea, 4204 single_entry = gmTools.bool2subst((meta_test_type is None), False, True) 4205 ) 4206 dlg.SetTitle(gmTools.coalesce(meta_test_type, _('Adding new meta test type'), _('Editing meta test type'))) 4207 if dlg.ShowModal() == wx.ID_OK: 4208 dlg.Destroy() 4209 return True 4210 dlg.Destroy() 4211 return False
4212 4213 #----------------------------------------------------------------
4214 -def manage_meta_test_types(parent=None):
4215 4216 if parent is None: 4217 parent = wx.GetApp().GetTopWindow() 4218 4219 #------------------------------------------------------------ 4220 def edit(meta_test_type=None): 4221 return edit_meta_test_type(parent = parent, meta_test_type = meta_test_type)
4222 #------------------------------------------------------------ 4223 def delete(meta_test_type): 4224 gmPathLab.delete_meta_type(meta_type = meta_test_type['pk']) 4225 return True 4226 #---------------------------------------- 4227 def get_tooltip(data): 4228 if data is None: 4229 return None 4230 return data.format(with_tests = True) 4231 #------------------------------------------------------------ 4232 def refresh(lctrl): 4233 mtts = gmPathLab.get_meta_test_types() 4234 items = [ [ 4235 m['abbrev'], 4236 m['name'], 4237 gmTools.coalesce(m['loinc'], ''), 4238 gmTools.coalesce(m['comment'], ''), 4239 m['pk'] 4240 ] for m in mtts ] 4241 lctrl.set_string_items(items) 4242 lctrl.set_data(mtts) 4243 #---------------------------------------- 4244 4245 msg = _( 4246 '\n' 4247 'These are the meta test types currently defined in GNUmed.\n' 4248 '\n' 4249 'Meta test types allow you to aggregate several actual test types used\n' 4250 'by pathology labs into one logical type.\n' 4251 '\n' 4252 'This is useful for grouping together results of tests which come under\n' 4253 'different names but really are the same thing. This often happens when\n' 4254 'you switch labs or the lab starts using another test method.\n' 4255 ) 4256 4257 gmListWidgets.get_choices_from_list ( 4258 parent = parent, 4259 msg = msg, 4260 caption = _('Showing meta test types.'), 4261 columns = [_('Abbrev'), _('Name'), _('LOINC'), _('Comment'), '#'], 4262 single_selection = True, 4263 list_tooltip_callback = get_tooltip, 4264 edit_callback = edit, 4265 new_callback = edit, 4266 delete_callback = delete, 4267 refresh_callback = refresh 4268 ) 4269 4270 #----------------------------------------------------------------
4271 -class cMetaTestTypePRW(gmPhraseWheel.cPhraseWheel):
4272
4273 - def __init__(self, *args, **kwargs):
4274 4275 query = """ 4276 SELECT DISTINCT ON (field_label) 4277 c_mtt.pk 4278 AS data, 4279 c_mtt.abbrev || ': ' || name 4280 AS field_label, 4281 c_mtt.abbrev || ': ' || name 4282 || coalesce ( 4283 ' (' || c_mtt.comment || ')', 4284 '' 4285 ) 4286 || coalesce ( 4287 ', LOINC: ' || c_mtt.loinc, 4288 '' 4289 ) 4290 AS list_label 4291 FROM 4292 clin.meta_test_type c_mtt 4293 WHERE 4294 abbrev %(fragment_condition)s 4295 OR 4296 name %(fragment_condition)s 4297 OR 4298 loinc %(fragment_condition)s 4299 ORDER BY field_label 4300 LIMIT 50""" 4301 4302 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 4303 mp.setThresholds(1, 2, 4) 4304 mp.word_separators = '[ \t:@]+' 4305 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 4306 self.matcher = mp 4307 self.SetToolTip(_('Select the meta test type.')) 4308 self.selection_only = True
4309 #------------------------------------------------------------
4310 - def _data2instance(self):
4311 if self.GetData() is None: 4312 return None 4313 4314 return gmPathLab.cMetaTestType(aPK_obj = self.GetData())
4315 4316 #---------------------------------------------------------------- 4317 from Gnumed.wxGladeWidgets import wxgMetaTestTypeEAPnl 4318
4319 -class cMetaTestTypeEAPnl(wxgMetaTestTypeEAPnl.wxgMetaTestTypeEAPnl, gmEditArea.cGenericEditAreaMixin):
4320
4321 - def __init__(self, *args, **kwargs):
4322 4323 try: 4324 data = kwargs['meta_test_type'] 4325 del kwargs['meta_test_type'] 4326 except KeyError: 4327 data = None 4328 4329 wxgMetaTestTypeEAPnl.wxgMetaTestTypeEAPnl.__init__(self, *args, **kwargs) 4330 gmEditArea.cGenericEditAreaMixin.__init__(self) 4331 4332 # Code using this mixin should set mode and data 4333 # after instantiating the class: 4334 self.mode = 'new' 4335 self.data = data 4336 if data is not None: 4337 self.mode = 'edit' 4338 4339 self.__init_ui()
4340 #----------------------------------------------------------------
4341 - def __init_ui(self):
4342 # loinc 4343 mp = gmLOINC.cLOINCMatchProvider() 4344 mp.setThresholds(1, 2, 4) 4345 #mp.print_queries = True 4346 #mp.word_separators = '[ \t:@]+' 4347 self._PRW_loinc.matcher = mp 4348 self._PRW_loinc.selection_only = False 4349 self._PRW_loinc.add_callback_on_lose_focus(callback = self._on_loinc_lost_focus)
4350 4351 #---------------------------------------------------------------- 4352 # generic Edit Area mixin API 4353 #----------------------------------------------------------------
4354 - def _valid_for_save(self):
4355 4356 validity = True 4357 4358 if self._PRW_abbreviation.GetValue().strip() == '': 4359 validity = False 4360 self._PRW_abbreviation.display_as_valid(False) 4361 self.status_message = _('Missing abbreviation for meta test type.') 4362 self._PRW_abbreviation.SetFocus() 4363 else: 4364 self._PRW_abbreviation.display_as_valid(True) 4365 4366 if self._PRW_name.GetValue().strip() == '': 4367 validity = False 4368 self._PRW_name.display_as_valid(False) 4369 self.status_message = _('Missing name for meta test type.') 4370 self._PRW_name.SetFocus() 4371 else: 4372 self._PRW_name.display_as_valid(True) 4373 4374 return validity
4375 #----------------------------------------------------------------
4376 - def _save_as_new(self):
4377 4378 # save the data as a new instance 4379 data = gmPathLab.create_meta_type ( 4380 name = self._PRW_name.GetValue().strip(), 4381 abbreviation = self._PRW_abbreviation.GetValue().strip(), 4382 return_existing = False 4383 ) 4384 if data is None: 4385 self.status_message = _('This meta test type already exists.') 4386 return False 4387 data['loinc'] = self._PRW_loinc.GetData() 4388 data['comment'] = self._TCTRL_comment.GetValue().strip() 4389 data.save() 4390 self.data = data 4391 return True
4392 #----------------------------------------------------------------
4393 - def _save_as_update(self):
4394 self.data['name'] = self._PRW_name.GetValue().strip() 4395 self.data['abbrev'] = self._PRW_abbreviation.GetValue().strip() 4396 self.data['loinc'] = self._PRW_loinc.GetData() 4397 self.data['comment'] = self._TCTRL_comment.GetValue().strip() 4398 self.data.save() 4399 return True
4400 #----------------------------------------------------------------
4401 - def _refresh_as_new(self):
4402 self._PRW_name.SetText('', None) 4403 self._PRW_abbreviation.SetText('', None) 4404 self._PRW_loinc.SetText('', None) 4405 self._TCTRL_loinc_info.SetValue('') 4406 self._TCTRL_comment.SetValue('') 4407 self._LBL_member_detail.SetLabel('') 4408 4409 self._PRW_name.SetFocus()
4410 #----------------------------------------------------------------
4412 self._refresh_as_new()
4413 #----------------------------------------------------------------
4414 - def _refresh_from_existing(self):
4415 self._PRW_name.SetText(self.data['name'], self.data['pk']) 4416 self._PRW_abbreviation.SetText(self.data['abbrev'], self.data['abbrev']) 4417 self._PRW_loinc.SetText(gmTools.coalesce(self.data['loinc'], ''), self.data['loinc']) 4418 self.__refresh_loinc_info() 4419 self._TCTRL_comment.SetValue(gmTools.coalesce(self.data['comment'], '')) 4420 self.__refresh_members() 4421 4422 self._PRW_name.SetFocus()
4423 #---------------------------------------------------------------- 4424 # event handlers 4425 #----------------------------------------------------------------
4426 - def _on_loinc_lost_focus(self):
4427 self.__refresh_loinc_info()
4428 #---------------------------------------------------------------- 4429 # internal helpers 4430 #----------------------------------------------------------------
4431 - def __refresh_loinc_info(self):
4432 loinc = self._PRW_loinc.GetData() 4433 4434 if loinc is None: 4435 self._TCTRL_loinc_info.SetValue('') 4436 return 4437 4438 info = gmLOINC.loinc2term(loinc = loinc) 4439 if len(info) == 0: 4440 self._TCTRL_loinc_info.SetValue('') 4441 return 4442 4443 self._TCTRL_loinc_info.SetValue(info[0])
4444 #----------------------------------------------------------------
4445 - def __refresh_members(self):
4446 if self.data is None: 4447 self._LBL_member_detail.SetLabel('') 4448 return 4449 4450 types = self.data.included_test_types 4451 if len(types) == 0: 4452 self._LBL_member_detail.SetLabel('') 4453 return 4454 4455 lines = [] 4456 for tt in types: 4457 lines.append('%s (%s%s) [#%s] @ %s' % ( 4458 tt['name'], 4459 tt['abbrev'], 4460 gmTools.coalesce(tt['loinc'], '', ', LOINC: %s'), 4461 tt['pk_test_type'], 4462 tt['name_org'] 4463 )) 4464 self._LBL_member_detail.SetLabel('\n'.join(lines))
4465 4466 #================================================================ 4467 # test panel handling 4468 #================================================================
4469 -def edit_test_panel(parent=None, test_panel=None):
4470 ea = cTestPanelEAPnl(parent, -1) 4471 ea.data = test_panel 4472 ea.mode = gmTools.coalesce(test_panel, 'new', 'edit') 4473 dlg = gmEditArea.cGenericEditAreaDlg2 ( 4474 parent = parent, 4475 id = -1, 4476 edit_area = ea, 4477 single_entry = gmTools.bool2subst((test_panel is None), False, True) 4478 ) 4479 dlg.SetTitle(gmTools.coalesce(test_panel, _('Adding new test panel'), _('Editing test panel'))) 4480 if dlg.ShowModal() == wx.ID_OK: 4481 dlg.Destroy() 4482 return True 4483 dlg.Destroy() 4484 return False
4485 4486 #----------------------------------------------------------------
4487 -def manage_test_panels(parent=None):
4488 4489 if parent is None: 4490 parent = wx.GetApp().GetTopWindow() 4491 4492 #------------------------------------------------------------ 4493 def edit(test_panel=None): 4494 return edit_test_panel(parent = parent, test_panel = test_panel)
4495 #------------------------------------------------------------ 4496 def delete(test_panel): 4497 gmPathLab.delete_test_panel(pk = test_panel['pk_test_panel']) 4498 return True 4499 #------------------------------------------------------------ 4500 def get_tooltip(test_panel): 4501 return test_panel.format() 4502 #------------------------------------------------------------ 4503 def refresh(lctrl): 4504 panels = gmPathLab.get_test_panels(order_by = 'description') 4505 items = [ [ 4506 p['description'], 4507 gmTools.coalesce(p['comment'], ''), 4508 p['pk_test_panel'] 4509 ] for p in panels ] 4510 lctrl.set_string_items(items) 4511 lctrl.set_data(panels) 4512 #------------------------------------------------------------ 4513 gmListWidgets.get_choices_from_list ( 4514 parent = parent, 4515 caption = 'GNUmed: ' + _('Test panels list'), 4516 columns = [ _('Name'), _('Comment'), '#' ], 4517 single_selection = True, 4518 refresh_callback = refresh, 4519 edit_callback = edit, 4520 new_callback = edit, 4521 delete_callback = delete, 4522 list_tooltip_callback = get_tooltip 4523 ) 4524 4525 #----------------------------------------------------------------
4526 -class cTestPanelPRW(gmPhraseWheel.cPhraseWheel):
4527
4528 - def __init__(self, *args, **kwargs):
4529 query = """ 4530 SELECT 4531 pk_test_panel 4532 AS data, 4533 description 4534 AS field_label, 4535 description 4536 AS list_label 4537 FROM 4538 clin.v_test_panels 4539 WHERE 4540 description %(fragment_condition)s 4541 ORDER BY field_label 4542 LIMIT 30""" 4543 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 4544 mp.setThresholds(1, 2, 4) 4545 #mp.word_separators = '[ \t:@]+' 4546 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 4547 self.matcher = mp 4548 self.SetToolTip(_('Select a test panel.')) 4549 self.selection_only = True
4550 #------------------------------------------------------------
4551 - def _data2instance(self):
4552 if self.GetData() is None: 4553 return None 4554 return gmPathLab.cTestPanel(aPK_obj = self.GetData())
4555 #------------------------------------------------------------
4556 - def _get_data_tooltip(self):
4557 if self.GetData() is None: 4558 return None 4559 return gmPathLab.cTestPanel(aPK_obj = self.GetData()).format()
4560 4561 #==================================================================== 4562 from Gnumed.wxGladeWidgets import wxgTestPanelEAPnl 4563
4564 -class cTestPanelEAPnl(wxgTestPanelEAPnl.wxgTestPanelEAPnl, gmEditArea.cGenericEditAreaMixin):
4565
4566 - def __init__(self, *args, **kwargs):
4567 4568 try: 4569 data = kwargs['panel'] 4570 del kwargs['panel'] 4571 except KeyError: 4572 data = None 4573 4574 wxgTestPanelEAPnl.wxgTestPanelEAPnl.__init__(self, *args, **kwargs) 4575 gmEditArea.cGenericEditAreaMixin.__init__(self) 4576 4577 self.__loincs = None 4578 4579 self.mode = 'new' 4580 self.data = data 4581 if data is not None: 4582 self.mode = 'edit' 4583 4584 self.__init_ui()
4585 4586 #----------------------------------------------------------------
4587 - def __init_ui(self):
4588 self._LCTRL_loincs.set_columns([_('LOINC'), _('Term'), _('Units')]) 4589 self._LCTRL_loincs.set_column_widths(widths = [wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE, wx.LIST_AUTOSIZE]) 4590 #self._LCTRL_loincs.set_resize_column(column = 2) 4591 self._LCTRL_loincs.delete_callback = self._remove_loincs_from_list 4592 self.__refresh_loinc_list() 4593 4594 self._PRW_loinc.final_regex = r'.*' 4595 self._PRW_loinc.add_callback_on_selection(callback = self._on_loinc_selected)
4596 4597 #----------------------------------------------------------------
4598 - def __refresh_loinc_list(self):
4599 self._LCTRL_loincs.remove_items_safely() 4600 if self.__loincs is None: 4601 if self.data is None: 4602 return 4603 self.__loincs = self.data['loincs'] 4604 4605 items = [] 4606 for loinc in self.__loincs: 4607 loinc_detail = gmLOINC.loinc2data(loinc = loinc) 4608 if loinc_detail is None: 4609 # check for test type with this pseudo loinc 4610 ttypes = gmPathLab.get_measurement_types(loincs = [loinc]) 4611 if len(ttypes) == 0: 4612 items.append([loinc, _('LOINC not found'), '']) 4613 else: 4614 for tt in ttypes: 4615 items.append([loinc, _('not a LOINC') + u'; %(name)s @ %(name_org)s [#%(pk_test_type)s]' % tt, '']) 4616 continue 4617 items.append ([ 4618 loinc, 4619 loinc_detail['term'], 4620 gmTools.coalesce(loinc_detail['example_units'], '', '%s') 4621 ]) 4622 4623 self._LCTRL_loincs.set_string_items(items) 4624 self._LCTRL_loincs.set_column_widths()
4625 4626 #---------------------------------------------------------------- 4627 # generic Edit Area mixin API 4628 #----------------------------------------------------------------
4629 - def _valid_for_save(self):
4630 validity = True 4631 4632 if self.__loincs is None: 4633 if self.data is not None: 4634 self.__loincs = self.data['loincs'] 4635 4636 if self.__loincs is None: 4637 # not fatal despite panel being useless 4638 self.status_message = _('No LOINC codes selected.') 4639 self._PRW_loinc.SetFocus() 4640 4641 if self._TCTRL_description.GetValue().strip() == '': 4642 validity = False 4643 self.display_tctrl_as_valid(tctrl = self._TCTRL_description, valid = False) 4644 self._TCTRL_description.SetFocus() 4645 else: 4646 self.display_tctrl_as_valid(tctrl = self._TCTRL_description, valid = True) 4647 4648 return validity
4649 4650 #----------------------------------------------------------------
4651 - def _save_as_new(self):
4652 data = gmPathLab.create_test_panel(description = self._TCTRL_description.GetValue().strip()) 4653 data['comment'] = self._TCTRL_comment.GetValue().strip() 4654 data.save() 4655 if self.__loincs is not None: 4656 data.included_loincs = self.__loincs 4657 self.data = data 4658 return True
4659 4660 #----------------------------------------------------------------
4661 - def _save_as_update(self):
4662 self.data['description'] = self._TCTRL_description.GetValue().strip() 4663 self.data['comment'] = self._TCTRL_comment.GetValue().strip() 4664 self.data.save() 4665 if self.__loincs is not None: 4666 self.data.included_loincs = self.__loincs 4667 return True
4668 4669 #----------------------------------------------------------------
4670 - def _refresh_as_new(self):
4671 self._TCTRL_description.SetValue('') 4672 self._TCTRL_comment.SetValue('') 4673 self._PRW_loinc.SetText('', None) 4674 self._LBL_loinc.SetLabel('') 4675 self.__loincs = None 4676 self.__refresh_loinc_list() 4677 4678 self._TCTRL_description.SetFocus()
4679 4680 #----------------------------------------------------------------
4682 self._refresh_as_new()
4683 4684 #----------------------------------------------------------------
4685 - def _refresh_from_existing(self):
4686 self._TCTRL_description.SetValue(self.data['description']) 4687 self._TCTRL_comment.SetValue(gmTools.coalesce(self.data['comment'], '')) 4688 self._PRW_loinc.SetText('', None) 4689 self._LBL_loinc.SetLabel('') 4690 self.__loincs = self.data['loincs'] 4691 self.__refresh_loinc_list() 4692 4693 self._PRW_loinc.SetFocus()
4694 4695 #---------------------------------------------------------------- 4696 # event handlers 4697 #----------------------------------------------------------------
4698 - def _on_loinc_selected(self, loinc):
4699 loinc = self._PRW_loinc.GetData() 4700 if loinc is None: 4701 self._LBL_loinc.SetLabel('') 4702 return 4703 loinc_detail = gmLOINC.loinc2data(loinc = loinc) 4704 if loinc_detail is None: 4705 loinc_str = _('no LOINC details found') 4706 else: 4707 loinc_str = '%s: %s%s' % ( 4708 loinc, 4709 loinc_detail['term'], 4710 gmTools.coalesce(loinc_detail['example_units'], '', ' (%s)') 4711 ) 4712 self._LBL_loinc.SetLabel(loinc_str)
4713 4714 #----------------------------------------------------------------
4715 - def _on_add_loinc_button_pressed(self, event):
4716 event.Skip() 4717 4718 loinc = self._PRW_loinc.GetData() 4719 if loinc is None: 4720 loinc = self._PRW_loinc.GetValue().strip() 4721 if loinc.strip() == '': 4722 return 4723 4724 if self.__loincs is None: 4725 self.__loincs = [loinc] 4726 else: 4727 if loinc in self.__loincs: 4728 return 4729 self.__loincs.append(loinc) 4730 4731 self.__refresh_loinc_list() 4732 self._PRW_loinc.SetText('', None) 4733 self._LBL_loinc.SetLabel('') 4734 4735 self._PRW_loinc.SetFocus()
4736 4737 #----------------------------------------------------------------
4738 - def _on_remove_loinc_button_pressed(self, event):
4739 event.Skip() 4740 self._remove_loincs_from_list()
4741 4742 #----------------------------------------------------------------
4743 - def _remove_loincs_from_list(self):
4744 loincs2remove = self._LCTRL_loincs.selected_item_data 4745 if loincs2remove is None: 4746 return 4747 for loinc in loincs2remove: 4748 try: 4749 while True: 4750 self.__loincs.remove(loinc[0]) 4751 except ValueError: 4752 pass 4753 self.__refresh_loinc_list()
4754 4755 #================================================================ 4756 # main 4757 #---------------------------------------------------------------- 4758 if __name__ == '__main__': 4759 4760 from Gnumed.pycommon import gmLog2 4761 from Gnumed.wxpython import gmPatSearchWidgets 4762 4763 gmI18N.activate_locale() 4764 gmI18N.install_domain() 4765 gmDateTime.init() 4766 4767 #------------------------------------------------------------
4768 - def test_grid():
4769 pat = gmPersonSearch.ask_for_patient() 4770 app = wx.PyWidgetTester(size = (500, 300)) 4771 lab_grid = cMeasurementsGrid(app.frame, -1) 4772 lab_grid.patient = pat 4773 app.frame.Show() 4774 app.MainLoop()
4775 #------------------------------------------------------------
4776 - def test_test_ea_pnl():
4777 pat = gmPersonSearch.ask_for_patient() 4778 gmPatSearchWidgets.set_active_patient(patient=pat) 4779 app = wx.PyWidgetTester(size = (500, 300)) 4780 ea = cMeasurementEditAreaPnl(app.frame, -1) 4781 app.frame.Show() 4782 app.MainLoop()
4783 #------------------------------------------------------------ 4784 # def test_primary_care_vitals_pnl(): 4785 # app = wx.PyWidgetTester(size = (500, 300)) 4786 # pnl = wxgPrimaryCareVitalsInputPnl.wxgPrimaryCareVitalsInputPnl(app.frame, -1) 4787 # app.frame.Show() 4788 # app.MainLoop() 4789 #------------------------------------------------------------ 4790 if (len(sys.argv) > 1) and (sys.argv[1] == 'test'): 4791 #test_grid() 4792 test_test_ea_pnl() 4793 #test_primary_care_vitals_pnl() 4794 4795 #================================================================ 4796