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

Source Code for Module Gnumed.wxpython.gmMeasurementWidgets

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