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

Source Code for Module Gnumed.wxpython.gmMeasurementWidgets

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