Home | Trees | Indices | Help |
|
---|
|
1 """GNUmed SOAP related widgets. 2 """ 3 #============================================================ 4 __version__ = "$Revision: 1.114 $" 5 __author__ = "Carlos Moro <cfmoro1976@yahoo.es>, K.Hilbert <Karsten.Hilbert@gmx.net>" 6 __license__ = "GPL" 7 8 # std library 9 import types, logging 10 11 12 # 3rd party 13 import wx 14 15 16 # GNUmed 17 from Gnumed.pycommon import gmDispatcher, gmI18N, gmExceptions, gmMatchProvider, gmTools, gmCfg 18 from Gnumed.wxpython import gmResizingWidgets, gmPhraseWheel, gmEMRStructWidgets, gmGuiHelpers, gmRegetMixin, gmEditArea, gmPatSearchWidgets 19 from Gnumed.business import gmPerson, gmEMRStructItems, gmSOAPimporter, gmPraxis, gmPersonSearch, gmStaff 20 21 _log = logging.getLogger('gm.ui') 22 _log.info(__version__) 23 24 #============================================================26 ea = gmEMRStructWidgets.cHealthIssueEditArea ( 27 parent, 28 -1, 29 wx.DefaultPosition, 30 wx.DefaultSize, 31 wx.NO_BORDER | wx.TAB_TRAVERSAL, 32 data_sink = data_sink 33 ) 34 popup = gmEditArea.cEditAreaPopup ( 35 parent = parent, 36 id = -1, 37 title = '', 38 pos = pos, 39 size = size, 40 style = style, 41 name = '', 42 edit_area = ea 43 ) 44 return popup45 #============================================================47 ea = gmVaccWidgets.cVaccinationEditArea ( 48 parent = parent, 49 id = -1, 50 pos = pos, 51 size = size, 52 style = style, 53 data_sink = data_sink 54 ) 55 popup = gmEditArea.cEditAreaPopup ( 56 parent = parent, 57 id = -1, 58 title = _('Enter vaccination given'), 59 pos = pos, 60 size = size, 61 style = style, 62 name = '', 63 edit_area = ea 64 ) 65 return popup66 #============================================================ 67 # FIXME: keywords hardcoded for now, load from cfg in backend instead 68 progress_note_keywords = { 69 's': { 70 '$missing_action': {}, 71 'phx$': { 72 'widget_factory': create_issue_popup, 73 'widget_data_sink': None 74 }, 75 'ea$:': { 76 'widget_factory': create_issue_popup, 77 'widget_data_sink': None 78 }, 79 '$vacc': { 80 'widget_factory': create_vacc_popup, 81 'widget_data_sink': None 82 }, 83 'impf:': { 84 'widget_factory': create_vacc_popup, 85 'widget_data_sink': None 86 }, 87 'icpc:': {}, 88 'icpc?': {} 89 }, 90 'o': { 91 'icpc:': {}, 92 'icpc?': {} 93 }, 94 'a': { 95 'icpc:': {}, 96 'icpc?': {} 97 }, 98 'p': { 99 '$vacc': { 100 'widget_factory': create_vacc_popup, 101 'widget_data_sink': None 102 }, 103 'icpc:': {}, 104 'icpc?': {} 105 } 106 } 107 #============================================================109 """A notebook holding panels with progress note editors. 110 111 There is one progress note editor panel for each episode being worked on. 112 """341 #-------------------------------------------------------- 342 # def _on_application_closing(self): 343 # """GNUmed is shutting down.""" 344 # print "[%s]: the application is closing down" % self.__class__.__name__ 345 # print "************************************" 346 # print "need to ask user about SOAP saving !" 347 # print "************************************" 348 #-------------------------------------------------------- 349 # def _on_episodes_modified(self): 350 # print "[%s]: episode modified" % self.__class__.__name__ 351 # print "need code to deal with:" 352 # print "- deleted episode that we show so we can notify the user" 353 # print "- renamed episode so we can update our episode label" 354 # self._schedule_data_reget() 355 # pass 356 #============================================================114 wx.Notebook.__init__ ( 115 self, 116 parent = parent, 117 id = id, 118 pos = pos, 119 size = size, 120 style = wx.NB_TOP | wx.NB_MULTILINE | wx.NO_BORDER, 121 name = self.__class__.__name__ 122 ) 123 gmRegetMixin.cRegetOnPaintMixin.__init__(self) 124 self.__pat = gmPerson.gmCurrentPatient() 125 self.__do_layout() 126 self.__register_interests()127 #-------------------------------------------------------- 128 # public API 129 #--------------------------------------------------------131 """Add a progress note editor page. 132 133 The way <allow_same_problem> is currently used in callers 134 it only applies to unassociated episodes. 135 """ 136 problem_to_add = problem 137 138 # determine label 139 if problem_to_add is None: 140 label = _('new problem') 141 else: 142 # normalize problem type 143 emr = self.__pat.get_emr() 144 if isinstance(problem_to_add, gmEMRStructItems.cEpisode): 145 problem_to_add = emr.episode2problem(episode = problem_to_add) 146 elif isinstance(problem_to_add, gmEMRStructItems.cHealthIssue): 147 problem_to_add = emr.health_issue2problem(issue = problem_to_add) 148 if not isinstance(problem_to_add, gmEMRStructItems.cProblem): 149 raise TypeError('cannot open progress note editor for [%s]' % problem_to_add) 150 label = problem_to_add['problem'] 151 # FIXME: configure maximum length 152 if len(label) > 23: 153 label = label[:21] + gmTools.u_ellipsis 154 155 if allow_same_problem: 156 new_page = cResizingSoapPanel(parent = self, problem = problem_to_add) 157 result = self.AddPage ( 158 page = new_page, 159 text = label, 160 select = True 161 ) 162 return result 163 164 # check for dupes 165 # new unassociated problem 166 if problem_to_add is None: 167 # check for dupes 168 for page_idx in range(self.GetPageCount()): 169 page = self.GetPage(page_idx) 170 # found 171 if page.get_problem() is None: 172 self.SetSelection(page_idx) 173 return True 174 continue 175 # not found 176 new_page = cResizingSoapPanel(parent = self, problem = problem_to_add) 177 result = self.AddPage ( 178 page = new_page, 179 text = label, 180 select = True 181 ) 182 return result 183 184 # real problem 185 # - raise existing editor ? 186 for page_idx in range(self.GetPageCount()): 187 page = self.GetPage(page_idx) 188 problem_of_page = page.get_problem() 189 # editor is for unassociated new problem 190 if problem_of_page is None: 191 continue 192 # editor is for episode 193 if problem_of_page['type'] == 'episode': 194 if problem_to_add['type'] == 'issue': 195 is_equal = (problem_of_page['pk_health_issue'] == problem_to_add['pk_health_issue']) 196 else: 197 is_equal = (problem_of_page['pk_episode'] == problem_to_add['pk_episode']) 198 if is_equal: 199 self.SetSelection(page_idx) 200 return True 201 continue 202 # editor is for health issue 203 if problem_of_page['type'] == 'issue': 204 if problem_of_page['pk_health_issue'] == problem_to_add['pk_health_issue']: 205 self.SetSelection(page_idx) 206 return True 207 continue 208 209 # - add new editor 210 new_page = cResizingSoapPanel(parent = self, problem = problem_to_add) 211 result = self.AddPage ( 212 page = new_page, 213 text = label, 214 select = True 215 ) 216 217 return result218 #--------------------------------------------------------220 221 page_idx = self.GetSelection() 222 page = self.GetPage(page_idx) 223 224 if not page.editor_empty(): 225 really_discard = gmGuiHelpers.gm_show_question ( 226 _('Are you sure you really want to\n' 227 'discard this progress note ?\n' 228 ), 229 _('Discarding progress note') 230 ) 231 if really_discard is False: 232 return 233 234 self.DeletePage(page_idx) 235 236 # always keep one unassociated editor open 237 if self.GetPageCount() == 0: 238 self.add_editor()239 #--------------------------------------------------------241 242 for page_idx in range(self.GetPageCount()): 243 page = self.GetPage(page_idx) 244 if page.editor_empty(): 245 continue 246 247 gmGuiHelpers.gm_show_warning ( 248 _('There are unsaved progress notes !\n'), 249 _('Unsaved progress notes') 250 ) 251 return False 252 253 return True254 #--------------------------------------------------------256 save_all = False 257 dlg = None 258 for page_idx in range(self.GetPageCount()): 259 page = self.GetPage(page_idx) 260 if page.editor_empty(): 261 continue 262 263 if dlg is None: 264 dlg = gmGuiHelpers.c3ButtonQuestionDlg ( 265 self, 266 -1, 267 caption = _('Unsaved progress note'), 268 question = _( 269 'This progress note has not been saved yet.\n' 270 '\n' 271 'Do you want to save it or discard it ?\n\n' 272 ), 273 button_defs = [ 274 {'label': _('&Save'), 'tooltip': _('Save this progress note'), 'default': True}, 275 {'label': _('&Discard'), 'tooltip': _('Discard this progress note'), 'default': False}, 276 {'label': _('Save &all'), 'tooltip': _('Save all remaining unsaved progress notes'), 'default': False} 277 ] 278 ) 279 280 if not save_all: 281 self.ChangeSelection(page_idx) 282 decision = dlg.ShowModal() 283 if decision == wx.ID_NO: 284 _log.info('user requested discarding of unsaved progress note') 285 continue 286 if decision == wx.ID_CANCEL: 287 save_all = True 288 page.save() 289 290 if dlg is not None: 291 dlg.Destroy()292 #-------------------------------------------------------- 293 # internal API 294 #--------------------------------------------------------296 # add one empty unassociated progress note editor - which to 297 # have (by all sensible accounts) seems to be the intent when 298 # instantiating this class 299 self.add_editor()300 #-------------------------------------------------------- 301 # reget mixin API 302 #--------------------------------------------------------304 print '[%s._populate_with_data] nothing to do, really...' % self.__class__.__name__ 305 return True306 #-------------------------------------------------------- 307 # event handling 308 #--------------------------------------------------------310 """Configure enabled event signals 311 """ 312 # wxPython events 313 314 # client internal signals 315 gmDispatcher.connect(signal = u'post_patient_selection', receiver=self._on_post_patient_selection) 316 # gmDispatcher.connect(signal = u'application_closing', receiver=self._on_application_closing) 317 318 self.__pat.register_pre_selection_callback(callback = self._pre_selection_callback) 319 320 gmDispatcher.send(signal = u'register_pre_exit_callback', callback = self._pre_exit_callback)321 #--------------------------------------------------------323 """Another patient is about to be activated. 324 325 Patient change will not proceed before this returns True. 326 """ 327 return self.warn_on_unsaved_soap()328 #--------------------------------------------------------330 """The client is about to be shut down. 331 332 Shutdown will not proceed before this returns. 333 """ 334 self.save_unsaved_soap()335 #--------------------------------------------------------337 """Patient changed.""" 338 self.DeleteAllPages() 339 self.add_editor() 340 self._schedule_data_reget()358 """A panel for entering multiple progress notes in context. 359 360 Expects to be used as a notebook page. 361 362 Left hand side: 363 - problem list (health issues and active episodes) 364 365 Right hand side: 366 - notebook with progress note editors 367 368 Listens to patient change signals, thus acts on the current patient. 369 """ 370 #--------------------------------------------------------624 #============================================================ 632 #============================================================ 633 # FIXME: this should be a more generic(ally named) class 634 # FIXME: living elsewhere372 """Contructs a new instance of SOAP input panel 373 374 @param parent: Wx parent widget 375 @param id: Wx widget id 376 """ 377 # Call parents constructors 378 wx.Panel.__init__ ( 379 self, 380 parent = parent, 381 id = id, 382 pos = wx.DefaultPosition, 383 size = wx.DefaultSize, 384 style = wx.NO_BORDER 385 ) 386 self.__pat = gmPerson.gmCurrentPatient() 387 388 # ui contruction and event handling set up 389 self.__do_layout() 390 self.__register_interests() 391 self.reset_ui_content()392 #-------------------------------------------------------- 393 # public API 394 #--------------------------------------------------------396 """ 397 Clear all information from input panel 398 """ 399 self.__LST_problems.Clear() 400 self.__soap_notebook.DeleteAllPages() 401 self.__soap_notebook.add_editor()402 #-------------------------------------------------------- 403 # internal helpers 404 #--------------------------------------------------------406 """Arrange widgets. 407 408 left: problem list (mix of issues and episodes) 409 right: soap editors 410 """ 411 # SOAP input panel main splitter window 412 self.__splitter = wx.SplitterWindow(self, -1) 413 414 # left hand side 415 PNL_list = wx.Panel(self.__splitter, -1) 416 # - header 417 list_header = wx.StaticText ( 418 parent = PNL_list, 419 id = -1, 420 label = _('Active problems'), 421 style = wx.NO_BORDER | wx.ALIGN_CENTRE 422 ) 423 # - problem list 424 self.__LST_problems = wx.ListBox ( 425 PNL_list, 426 -1, 427 style= wx.NO_BORDER 428 ) 429 # - arrange 430 szr_left = wx.BoxSizer(wx.VERTICAL) 431 szr_left.Add(list_header, 0) 432 szr_left.Add(self.__LST_problems, 1, wx.EXPAND) 433 PNL_list.SetSizerAndFit(szr_left) 434 435 # right hand side 436 # - soap inputs panel 437 PNL_soap_editors = wx.Panel(self.__splitter, -1) 438 # - progress note notebook 439 self.__soap_notebook = cProgressNoteInputNotebook(PNL_soap_editors, -1) 440 # - buttons 441 self.__BTN_add_unassociated = wx.Button(PNL_soap_editors, -1, _('&New')) 442 tt = _( 443 'Add editor for a new unassociated progress note.\n\n' 444 'There is a configuration option whether or not to\n' 445 'allow several new unassociated progress notes at once.' 446 ) 447 self.__BTN_add_unassociated.SetToolTipString(tt) 448 449 self.__BTN_save = wx.Button(PNL_soap_editors, -1, _('&Save')) 450 self.__BTN_save.SetToolTipString(_('Save progress note into medical record and close this editor.')) 451 452 self.__BTN_clear = wx.Button(PNL_soap_editors, -1, _('&Clear')) 453 self.__BTN_clear.SetToolTipString(_('Clear this progress note editor.')) 454 455 self.__BTN_discard = wx.Button(PNL_soap_editors, -1, _('&Discard')) 456 self.__BTN_discard.SetToolTipString(_('Discard progress note and close this editor. You will loose any data already typed into this editor !')) 457 458 # - arrange 459 szr_btns_right = wx.BoxSizer(wx.HORIZONTAL) 460 szr_btns_right.Add(self.__BTN_add_unassociated, 0, wx.SHAPED) 461 szr_btns_right.Add(self.__BTN_clear, 0, wx.SHAPED) 462 szr_btns_right.Add(self.__BTN_save, 0, wx.SHAPED) 463 szr_btns_right.Add(self.__BTN_discard, 0, wx.SHAPED) 464 465 szr_right = wx.BoxSizer(wx.VERTICAL) 466 szr_right.Add(self.__soap_notebook, 1, wx.EXPAND) 467 szr_right.Add(szr_btns_right) 468 PNL_soap_editors.SetSizerAndFit(szr_right) 469 470 # arrange widgets 471 self.__splitter.SetMinimumPaneSize(20) 472 self.__splitter.SplitVertically(PNL_list, PNL_soap_editors) 473 474 szr_main = wx.BoxSizer(wx.VERTICAL) 475 szr_main.Add(self.__splitter, 1, wx.EXPAND, 0) 476 self.SetSizerAndFit(szr_main)477 #--------------------------------------------------------479 """Update health problems list. 480 """ 481 self.__LST_problems.Clear() 482 emr = self.__pat.get_emr() 483 problems = emr.get_problems() 484 for problem in problems: 485 if not problem['problem_active']: 486 continue 487 if problem['type'] == 'issue': 488 issue = emr.problem2issue(problem) 489 last_encounter = emr.get_last_encounter(issue_id = issue['pk_health_issue']) 490 if last_encounter is None: 491 last = issue['modified_when'].strftime('%m/%Y') 492 else: 493 last = last_encounter['last_affirmed'].strftime('%m/%Y') 494 label = u'%s: %s "%s"' % (last, problem['l10n_type'], problem['problem']) 495 elif problem['type'] == 'episode': 496 epi = emr.problem2episode(problem) 497 last_encounter = emr.get_last_encounter(episode_id = epi['pk_episode']) 498 if last_encounter is None: 499 last = epi['episode_modified_when'].strftime('%m/%Y') 500 else: 501 last = last_encounter['last_affirmed'].strftime('%m/%Y') 502 label = u'%s: %s "%s"%s' % ( 503 last, 504 problem['l10n_type'], 505 problem['problem'], 506 gmTools.coalesce(initial = epi['health_issue'], instead = '', template_initial = ' (%s)') 507 ) 508 self.__LST_problems.Append(label, problem) 509 splitter_width = self.__splitter.GetSizeTuple()[0] 510 self.__splitter.SetSashPosition((splitter_width / 2), True) 511 self.Refresh() 512 #self.Update() 513 return True514 #-------------------------------------------------------- 515 # event handling 516 #--------------------------------------------------------518 """Configure enabled event signals 519 """ 520 # wxPython events 521 wx.EVT_LISTBOX_DCLICK(self.__LST_problems, self.__LST_problems.GetId(), self.__on_problem_activated) 522 wx.EVT_BUTTON(self.__BTN_save, self.__BTN_save.GetId(), self.__on_save) 523 wx.EVT_BUTTON(self.__BTN_clear, self.__BTN_clear.GetId(), self.__on_clear) 524 wx.EVT_BUTTON(self.__BTN_discard, self.__BTN_discard.GetId(), self.__on_discard) 525 wx.EVT_BUTTON(self.__BTN_add_unassociated, self.__BTN_add_unassociated.GetId(), self.__on_add_unassociated) 526 527 # client internal signals 528 gmDispatcher.connect(signal='post_patient_selection', receiver=self._on_post_patient_selection) 529 gmDispatcher.connect(signal = 'clin.episode_mod_db', receiver = self._on_episode_issue_mod_db) 530 gmDispatcher.connect(signal = 'clin.health_issue_mod_db', receiver = self._on_episode_issue_mod_db)531 #--------------------------------------------------------533 """Patient changed.""" 534 if self.GetParent().GetCurrentPage() == self: 535 self.reset_ui_content()536 #-------------------------------------------------------- 540 #--------------------------------------------------------542 """Clear raised SOAP input widget. 543 """ 544 soap_nb_page = self.__soap_notebook.GetPage(self.__soap_notebook.GetSelection()) 545 soap_nb_page.Clear()546 #--------------------------------------------------------548 """Discard raised SOAP input widget. 549 550 Will throw away data ! 551 """ 552 self.__soap_notebook.close_current_editor()553 #--------------------------------------------------------555 """Add new editor for as-yet unassociated progress note. 556 557 Clinical logic as per discussion with Jim Busser: 558 559 - if patient has no episodes: 560 - new patient 561 - always allow several NEWs 562 - if patient has episodes: 563 - allow several NEWs per configuration 564 """ 565 emr = self.__pat.get_emr() 566 epis = emr.get_episodes() 567 568 if len(epis) == 0: 569 value = True 570 else: 571 dbcfg = gmCfg.cCfgSQL() 572 value = bool(dbcfg.get2 ( 573 option = u'horstspace.soap_editor.allow_same_episode_multiple_times', 574 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace, 575 bias = u'user', 576 default = False 577 )) 578 579 self.__soap_notebook.add_editor(allow_same_problem = value)580 #--------------------------------------------------------582 """ 583 When the user changes health issue selection, update selected issue 584 reference and update buttons according its input status. 585 586 when the user selects a problem in the problem list: 587 - check whether selection is issue or episode 588 - if editor for episode exists: focus it 589 - if no editor for episode exists: create one and focus it 590 """ 591 problem_idx = self.__LST_problems.GetSelection() 592 problem = self.__LST_problems.GetClientData(problem_idx) 593 594 if self.__soap_notebook.add_editor(problem = problem): 595 return True 596 597 gmGuiHelpers.gm_show_error ( 598 aMessage = _( 599 'Cannot open progress note editor for\n\n' 600 '[%s].\n\n' 601 ) % problem['problem'], 602 aTitle = _('opening progress note editor') 603 ) 604 return False605 #--------------------------------------------------------607 """Save data to backend and close editor. 608 """ 609 page_idx = self.__soap_notebook.GetSelection() 610 soap_nb_page = self.__soap_notebook.GetPage(page_idx) 611 if not soap_nb_page.save(): 612 gmDispatcher.send(signal='statustext', msg=_('Problem saving progress note: duplicate information ?')) 613 return False 614 self.__soap_notebook.DeletePage(page_idx) 615 # always keep one unassociated editor open 616 self.__soap_notebook.add_editor() 617 #self.__refresh_problem_list() 618 return True619 #-------------------------------------------------------- 620 # notebook plugin API 621 #--------------------------------------------------------636 637 _data_savers = {} 638 641 #--------------------------------------------------------679 #-------------------------------------------------------- 680 # def remove_data(self, popup_type=None, desc=None): 681 # del self.__data[popup_type][desc] 682 #-------------------------------------------------------- 683 # def get_descs(self, popup_type=None, origination_soap=None): 684 # def get_data(self, desc=None): 685 # def rename_data(self, old_desc=None, new_desc=None): 686 #============================================================643 # FIXME: do fancy validations 644 645 print "storing popup data:", desc 646 print "type", popup_type 647 print "data", data 648 649 # verify structure 650 try: 651 self.__data[popup_type] 652 except KeyError: 653 self.__data[popup_type] = {} 654 # store new data 655 self.__data[popup_type][desc] = { 656 'data': data 657 } 658 # remove old data if necessary 659 try: 660 del self.__data[popup_type][old_desc] 661 except: 662 pass 663 return True664 #--------------------------------------------------------666 for popup_type in self.__data.keys(): 667 try: 668 saver_func = self.__data_savers[popup_type] 669 except KeyError: 670 _log.exception('no saver for popup data type [%s] configured', popup_type) 671 return False 672 for desc in self.__data[popup_type].keys(): 673 data = self.__data[popup_type][desc]['data'] 674 saver_func(data) 675 return True676 #--------------------------------------------------------688858 #============================================================690 """Resizing SOAP note input editor. 691 692 This is a wrapper around a few resizing STCs (the 693 labels and categories are settable) which are 694 customized to accept progress note input. It provides 695 the unified resizing behaviour. 696 697 Knows how to save it's data into the backend. 698 699 @param input_defs: note's labels and categories 700 @type input_defs: list of cSOAPLineDef instances 701 """ 702 if input_defs is None or len(input_defs) == 0: 703 raise gmExceptions.ConstructorError, 'cannot generate note with field defs [%s]' % input_defs 704 705 # FIXME: *actually* this should be a session-local 706 # FIXME: holding store at the cClinicalRecord level 707 self.__embedded_data_holder = cPopupDataHolder() 708 709 self.__input_defs = input_defs 710 711 gmResizingWidgets.cResizingWindow.__init__(self, parent, id=-1, size=size) 712 713 self.__problem = problem 714 if isinstance(problem, gmEMRStructItems.cEpisode): 715 self.__problem = emr.episode2problem(episode = problem) 716 elif isinstance(problem, gmEMRStructItems.cHealthIssue): 717 self.__problem = emr.health_issue2problem(issue = problem) 718 self.__pat = gmPerson.gmCurrentPatient()719 #-------------------------------------------------------- 720 # cResizingWindow API 721 #--------------------------------------------------------723 """Visually display input note according to user defined labels. 724 """ 725 # configure keywords 726 for soap_cat in progress_note_keywords.keys(): 727 category = progress_note_keywords[soap_cat] 728 for kwd in category.keys(): 729 category[kwd]['widget_data_sink'] = self.__embedded_data_holder.store_data 730 input_fields = [] 731 # add fields to edit widget 732 # note: this may produce identically labelled lines 733 for line_def in self.__input_defs: 734 input_field = gmResizingWidgets.cResizingSTC(self, -1, data = line_def) 735 input_field.SetText(line_def.text) 736 kwds = progress_note_keywords[line_def.soap_cat] 737 input_field.set_keywords(popup_keywords=kwds) 738 # FIXME: pending matcher setup 739 self.AddWidget(widget=input_field, label=line_def.label) 740 self.Newline() 741 input_fields.append(input_field) 742 # setup tab navigation between input fields 743 for field_idx in range(len(input_fields)): 744 # previous 745 try: 746 input_fields[field_idx].prev_in_tab_order = input_fields[field_idx-1] 747 except IndexError: 748 input_fields[field_idx].prev_in_tab_order = None 749 # next 750 try: 751 input_fields[field_idx].next_in_tab_order = input_fields[field_idx+1] 752 except IndexError: 753 input_fields[field_idx].next_in_tab_order = None754 #-------------------------------------------------------- 755 # public API 756 #--------------------------------------------------------758 """Save data into backend.""" 759 760 # fill progress_note for import 761 progress_note = [] 762 aoe = u'' 763 rfe = u'' 764 has_rfe = False 765 soap_lines_contents = self.GetValue() 766 for line_content in soap_lines_contents.values(): 767 if line_content.text.strip() == u'': 768 continue 769 progress_note.append ({ 770 gmSOAPimporter.soap_bundle_SOAP_CAT_KEY: line_content.data.soap_cat, 771 gmSOAPimporter.soap_bundle_TYPES_KEY: [], # these types need to come from the editor 772 gmSOAPimporter.soap_bundle_TEXT_KEY: line_content.text.rstrip() 773 }) 774 if line_content.data.is_rfe: 775 has_rfe = True 776 rfe += line_content.text.rstrip() 777 if line_content.data.soap_cat == u'a': 778 aoe += line_content.text.rstrip() 779 780 emr = self.__pat.get_emr() 781 782 # - new episode, must get name from narrative (or user) 783 if (self.__problem is None) or (self.__problem['type'] == 'issue'): 784 # work out episode name 785 epi_name = u'' 786 if len(aoe) != 0: 787 epi_name = aoe 788 else: 789 epi_name = rfe 790 791 dlg = wx.TextEntryDialog ( 792 parent = self, 793 message = _('Enter a descriptive name for this new problem:'), 794 caption = _('Creating a problem (episode) to save the notelet under ...'), 795 defaultValue = epi_name.replace('\r', '//').replace('\n', '//'), 796 style = wx.OK | wx.CANCEL | wx.CENTRE 797 ) 798 decision = dlg.ShowModal() 799 if decision != wx.ID_OK: 800 return False 801 802 epi_name = dlg.GetValue().strip() 803 if epi_name == u'': 804 gmGuiHelpers.gm_show_error(_('Cannot save a new problem without a name.'), _('saving progress note')) 805 return False 806 807 # new unassociated episode 808 new_episode = emr.add_episode(episode_name = epi_name[:45], pk_health_issue = None, is_open = True) 809 810 if self.__problem is not None: 811 issue = emr.problem2issue(self.__problem) 812 if not gmEMRStructWidgets.move_episode_to_issue(episode = new_episode, target_issue = issue, save_to_backend = True): 813 print "error moving episode to issue" 814 815 epi_id = new_episode['pk_episode'] 816 else: 817 epi_id = self.__problem['pk_episode'] 818 819 # set up clinical context in progress note 820 encounter = emr.active_encounter 821 staff_id = gmStaff.gmCurrentProvider()['pk_staff'] 822 clin_ctx = { 823 gmSOAPimporter.soap_bundle_EPISODE_ID_KEY: epi_id, 824 gmSOAPimporter.soap_bundle_ENCOUNTER_ID_KEY: encounter['pk_encounter'], 825 gmSOAPimporter.soap_bundle_STAFF_ID_KEY: staff_id 826 } 827 for line in progress_note: 828 line[gmSOAPimporter.soap_bundle_CLIN_CTX_KEY] = clin_ctx 829 830 # dump progress note to backend 831 importer = gmSOAPimporter.cSOAPImporter() 832 if not importer.import_soap(progress_note): 833 gmGuiHelpers.gm_show_error(_('Error saving progress note.'), _('saving progress note')) 834 return False 835 836 # dump embedded data to backend 837 if not self.__embedded_data_holder.save(): 838 gmGuiHelpers.gm_show_error ( 839 _('Error saving embedded data.'), 840 _('saving progress note') 841 ) 842 return False 843 self.__embedded_data_holder.clear() 844 845 return True846 #-------------------------------------------------------- 849 #--------------------------------------------------------851 editor_content = self.GetValue() 852 853 for field_content in editor_content.values(): 854 if field_content.text.strip() != u'': 855 return False 856 857 return True860 """Basic progress note panel. 861 862 It provides a gmResizingWindow based progress note editor 863 with a header line. The header either displays the episode 864 this progress note is associated with or it allows for 865 entering an episode name. The episode name either names 866 an existing episode or is the name for a new episode. 867 868 This panel knows how to save it's data into the backend. 869 870 Can work as: 871 a) Progress note creation: displays an empty set of soap entries to 872 create a new soap note for the given episode (or unassociated) 873 """ 874 #--------------------------------------------------------984 #============================================================876 """ 877 Construct a new SOAP input widget. 878 879 @param parent: the parent widget 880 881 @param episode: the episode to create the SOAP editor for. 882 @type episode gmEMRStructItems.cEpisode instance or None (to create an 883 unassociated progress note). A gmEMRStructItems.cProblem instance is 884 also allowed to be passed, as the widget will obtain the related cEpisode. 885 886 @param input_defs: the display and associated data for each displayed narrative 887 @type input_defs: a list of cSOAPLineDef instances 888 """ 889 if not isinstance(problem, (gmEMRStructItems.cHealthIssue, gmEMRStructItems.cEpisode, gmEMRStructItems.cProblem, types.NoneType)): 890 raise gmExceptions.ConstructorError, 'problem [%s] is of type %s, must be issue, episode, problem or None' % (str(problem), type(problem)) 891 892 self.__is_saved = False 893 # do layout 894 wx.Panel.__init__(self, parent, -1, style = wx.NO_BORDER | wx.TAB_TRAVERSAL) 895 # - editor 896 if input_defs is None: 897 soap_lines = [] 898 # make Richard the default ;-) 899 # FIXME: actually, should be read from backend 900 line = cSOAPLineDef() 901 line.label = _('Visit Purpose') 902 line.soap_cat = 's' 903 line.is_rfe = True 904 soap_lines.append(line) 905 906 line = cSOAPLineDef() 907 line.label = _('History Taken') 908 line.soap_cat = 's' 909 soap_lines.append(line) 910 911 line = cSOAPLineDef() 912 line.label = _('Findings') 913 line.soap_cat = 'o' 914 soap_lines.append(line) 915 916 line = cSOAPLineDef() 917 line.label = _('Assessment') 918 line.soap_cat = 'a' 919 soap_lines.append(line) 920 921 line = cSOAPLineDef() 922 line.label = _('Plan') 923 line.soap_cat = 'p' 924 soap_lines.append(line) 925 else: 926 soap_lines = input_defs 927 self.__soap_editor = cResizingSoapWin ( 928 self, 929 size = wx.DefaultSize, 930 input_defs = soap_lines, 931 problem = problem 932 ) 933 # - arrange 934 self.__szr_main = wx.BoxSizer(wx.VERTICAL) 935 self.__szr_main.Add(self.__soap_editor, 1, wx.EXPAND) 936 self.SetSizerAndFit(self.__szr_main)937 #-------------------------------------------------------- 938 # public API 939 #--------------------------------------------------------941 """Retrieve the related problem for this SOAP input widget. 942 """ 943 return self.__soap_editor.get_problem()944 #--------------------------------------------------------946 """ 947 Retrieves whether the current editor is not associated 948 with any episode. 949 """ 950 return ((self.__problem is None) or (self.__problem['type'] == 'issue'))951 #-------------------------------------------------------- 956 #-------------------------------------------------------- 961 #--------------------------------------------------------963 """ 964 Set SOAP input widget saved (dumped to backend) state 965 966 @param is_saved: Flag indicating wether the SOAP has been dumped to 967 persistent backend 968 @type is_saved: boolean 969 """ 970 self.__is_saved = is_saved 971 self.Clear()972 #--------------------------------------------------------974 """ 975 Check SOAP input widget saved (dumped to backend) state 976 """ 977 return self.__is_saved978 #--------------------------------------------------------980 return self.__soap_editor.save()981 #--------------------------------------------------------983 return self.__soap_editor.is_empty()986 """if we separate it out like this it can transparently gain features"""989 #============================================================988 wx.TextCtrl.__init__(self, *args, **kwargs)991 """Single Box free text SOAP input. 992 993 This widget was suggested by David Guest on the mailing 994 list. All it does is provide a single multi-line textbox 995 for typing free-text clinical notes which are stored as 996 Subjective. 997 """1080 #============================================================ 1081 # main 1082 #------------------------------------------------------------ 1083 if __name__ == "__main__": 1084 1085 import sys 1086 1087 from Gnumed.pycommon import gmPG2 1088 #--------------------------------------------------------999 wx.Panel.__init__(self, *args, **kwargs) 1000 self.__do_layout() 1001 self.__pat = gmPerson.gmCurrentPatient() 1002 if not self.__register_events(): 1003 raise gmExceptions.ConstructorError, 'cannot register interests'1004 #--------------------------------------------------------1006 # large box for free-text clinical notes 1007 self.__soap_box = cSingleBoxSOAP ( 1008 self, 1009 -1, 1010 '', 1011 style = wx.TE_MULTILINE 1012 ) 1013 # buttons below that 1014 self.__BTN_save = wx.Button(self, wx.NewId(), _("save")) 1015 self.__BTN_save.SetToolTipString(_('save clinical note in EMR')) 1016 self.__BTN_discard = wx.Button(self, wx.NewId(), _("discard")) 1017 self.__BTN_discard.SetToolTipString(_('discard clinical note')) 1018 szr_btns = wx.BoxSizer(wx.HORIZONTAL) 1019 szr_btns.Add(self.__BTN_save, 1, wx.ALIGN_CENTER_HORIZONTAL, 0) 1020 szr_btns.Add(self.__BTN_discard, 1, wx.ALIGN_CENTER_HORIZONTAL, 0) 1021 # arrange widgets 1022 szr_outer = wx.StaticBoxSizer(wx.StaticBox(self, -1, _("clinical progress note")), wx.VERTICAL) 1023 szr_outer.Add(self.__soap_box, 1, wx.EXPAND, 0) 1024 szr_outer.Add(szr_btns, 0, wx.EXPAND, 0) 1025 # and do layout 1026 self.SetAutoLayout(1) 1027 self.SetSizer(szr_outer) 1028 szr_outer.Fit(self) 1029 szr_outer.SetSizeHints(self) 1030 self.Layout()1031 #--------------------------------------------------------1033 # wxPython events 1034 wx.EVT_BUTTON(self.__BTN_save, self.__BTN_save.GetId(), self._on_save_note) 1035 wx.EVT_BUTTON(self.__BTN_discard, self.__BTN_discard.GetId(), self._on_discard_note) 1036 1037 # client internal signals 1038 gmDispatcher.connect(signal = 'pre_patient_selection', receiver = self._save_note) 1039 gmDispatcher.connect(signal = 'application_closing', receiver = self._save_note) 1040 1041 return True1042 #-------------------------------------------------------- 1043 # event handlers 1044 #-------------------------------------------------------- 1047 #event.Skip() 1048 #-------------------------------------------------------- 1052 #event.Skip() 1053 #-------------------------------------------------------- 1054 # internal helpers 1055 #-------------------------------------------------------- 1058 #--------------------------------------------------------1060 # sanity checks 1061 if self.__pat is None: 1062 return True 1063 if not self.__pat.connected: 1064 return True 1065 if not self.__soap_box.IsModified(): 1066 return True 1067 note = self.__soap_box.GetValue() 1068 if note.strip() == '': 1069 return True 1070 # now save note 1071 emr = self.__pat.get_emr() 1072 if emr is None: 1073 _log.error('cannot access clinical record of patient') 1074 return False 1075 if not emr.add_clin_narrative(note, soap_cat='s'): 1076 _log.error('error saving clinical note') 1077 return False 1078 self.__soap_box.SetValue('') 1079 return True1090 """ 1091 Retrieve the soap editor input lines definitions built from 1092 all the narratives for the given issue along a specific 1093 encounter. 1094 1095 @param pk_health_issue The id of the health issue to obtain the narratives for. 1096 @param pk_health_issue An integer instance 1097 1098 @param pk_encounter The id of the encounter to obtain the narratives for. 1099 @type A gmEMRStructItems.cEncounter instance. 1100 1101 @param default_labels: The user customized labels for each 1102 soap category. 1103 @type default_labels: A dictionary instance which keys are 1104 soap categories. 1105 """ 1106 # custom labels 1107 if default_labels is None: 1108 default_labels = { 1109 's': _('History Taken'), 1110 'o': _('Findings'), 1111 'a': _('Assessment'), 1112 'p': _('Plan') 1113 } 1114 1115 pat = gmPerson.gmCurrentPatient() 1116 emr = pat.get_emr() 1117 soap_lines = [] 1118 # for each soap cat 1119 for soap_cat in gmSOAPimporter.soap_bundle_SOAP_CATS: 1120 # retrieve narrative for given encounter 1121 narr_items = emr.get_clin_narrative ( 1122 encounters = [pk_encounter], 1123 issues = [pk_health_issue], 1124 soap_cats = [soap_cat] 1125 ) 1126 for narrative in narr_items: 1127 try: 1128 # FIXME: add more data such as doctor sig 1129 label_txt = default_labels[narrative['soap_cat']] 1130 except: 1131 label_txt = narrative['soap_cat'] 1132 line = cSOAPLineDef() 1133 line.label = label_txt 1134 line.text = narrative['narrative'] 1135 # line.data['narrative instance'] = narrative 1136 soap_lines.append(line) 1137 return soap_lines1138 #--------------------------------------------------------1140 print "test keyword must have been typed..." 1141 print "actually this would have to return a suitable wx.Window subclass instance" 1142 print "args:", args 1143 print "kwd args:" 1144 for key in kwargs.keys(): 1145 print key, "->", kwargs[key]1146 #--------------------------------------------------------1148 msg = ( 1149 "test keyword must have been typed...\n" 1150 "actually this would have to return a suitable wx.Window subclass instance\n" 1151 ) 1152 for arg in args: 1153 msg = msg + "\narg ==> %s" % arg 1154 for key in kwargs.keys(): 1155 msg = msg + "\n%s ==> %s" % (key, kwargs[key]) 1156 gmGuiHelpers.gm_show_info ( 1157 aMessage = msg, 1158 aTitle = 'msg box on create_widget from test_keyword' 1159 )1160 #--------------------------------------------------------1162 print 'testing notebooked soap input...' 1163 application = wx.PyWidgetTester(size=(800,500)) 1164 soap_input = cProgressNoteInputNotebook(application.frame, -1) 1165 application.frame.Show(True) 1166 application.MainLoop()1167 #--------------------------------------------------------1169 print 'testing notebooked soap panel...' 1170 application = wx.PyWidgetTester(size=(800,500)) 1171 soap_input = cNotebookedProgressNoteInputPanel(application.frame, -1) 1172 application.frame.Show(True) 1173 application.MainLoop()1174 #-------------------------------------------------------- 1175 1176 try: 1177 # obtain patient 1178 patient = gmPersonSearch.ask_for_patient() 1179 if patient is None: 1180 print "No patient. Exiting gracefully..." 1181 sys.exit(0) 1182 gmPatSearchWidgets.set_active_patient(patient=patient) 1183 1184 #test_soap_notebook() 1185 test_soap_notebook_panel() 1186 1187 # # multisash soap 1188 # print 'testing multisashed soap input...' 1189 # application = wx.PyWidgetTester(size=(800,500)) 1190 # soap_input = cMultiSashedProgressNoteInputPanel(application.frame, -1) 1191 # application.frame.Show(True) 1192 # application.MainLoop() 1193 1194 # # soap widget displaying all narratives for an issue along an encounter 1195 # print 'testing soap editor for encounter narratives...' 1196 # episode = gmEMRStructItems.cEpisode(aPK_obj=1) 1197 # encounter = gmEMRStructItems.cEncounter(aPK_obj=1) 1198 # narrative = get_narrative(pk_encounter = encounter['pk_encounter'], pk_health_issue = episode['pk_health_issue']) 1199 # default_labels = {'s':'Subjective', 'o':'Objective', 'a':'Assesment', 'p':'Plan'} 1200 # app = wx.PyWidgetTester(size=(300,500)) 1201 # app.SetWidget(cResizingSoapPanel, episode, narrative) 1202 # app.MainLoop() 1203 # del app 1204 1205 # # soap progress note for episode 1206 # print 'testing soap editor for episode...' 1207 # app = wx.PyWidgetTester(size=(300,300)) 1208 # app.SetWidget(cResizingSoapPanel, episode) 1209 # app.MainLoop() 1210 # del app 1211 1212 # # soap progress note for problem 1213 # print 'testing soap editor for problem...' 1214 # problem = gmEMRStructItems.cProblem(aPK_obj={'pk_patient': 12, 'pk_health_issue': 1, 'pk_episode': 1}) 1215 # app = wx.PyWidgetTester(size=(300,300)) 1216 # app.SetWidget(cResizingSoapPanel, problem) 1217 # app.MainLoop() 1218 # del app 1219 1220 # # unassociated soap progress note 1221 # print 'testing unassociated soap editor...' 1222 # app = wx.PyWidgetTester(size=(300,300)) 1223 # app.SetWidget(cResizingSoapPanel, None) 1224 # app.MainLoop() 1225 # del app 1226 1227 # # unstructured progress note 1228 # print 'testing unstructured progress note...' 1229 # app = wx.PyWidgetTester(size=(600,600)) 1230 # app.SetWidget(cSingleBoxSOAPPanel, -1) 1231 # app.MainLoop() 1232 1233 except StandardError: 1234 _log.exception("unhandled exception caught !") 1235 # but re-raise them 1236 raise 1237 1238 #============================================================ 1239
Home | Trees | Indices | Help |
|
---|
Generated by Epydoc 3.0.1 on Sat Aug 3 03:56:29 2013 | http://epydoc.sourceforge.net |