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