| 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, gmSurgery, 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 popup
45 #============================================================
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 popup
66 #============================================================
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 """
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 result
218 #--------------------------------------------------------
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 True
254 #--------------------------------------------------------
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 True
306 #--------------------------------------------------------
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()
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 #============================================================
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 #--------------------------------------------------------
372 """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 True
514 #--------------------------------------------------------
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 = 'episode_mod_db', receiver = self._on_episode_issue_mod_db)
530 gmDispatcher.connect(signal = '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 = gmSurgery.gmCurrentPractice().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 False
605 #--------------------------------------------------------
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 True
619 #--------------------------------------------------------
620 # notebook plugin API
621 #--------------------------------------------------------
624 #============================================================
632 #============================================================
633 # FIXME: this should be a more generic(ally named) class
634 # FIXME: living elsewhere
636
637 _data_savers = {}
638
641 #--------------------------------------------------------
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 True
664 #--------------------------------------------------------
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 True
676 #--------------------------------------------------------
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 #============================================================
688
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 = None
754 #--------------------------------------------------------
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 True
846 #--------------------------------------------------------
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 True
858 #============================================================
860 """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 #--------------------------------------------------------
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_saved
978 #--------------------------------------------------------
980 return self.__soap_editor.save()
981 #--------------------------------------------------------
983 return self.__soap_editor.is_empty()
984 #============================================================
986 """if we separate it out like this it can transparently gain features"""
988 wx.TextCtrl.__init__(self, *args, **kwargs)
989 #============================================================
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 """
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 True
1042 #--------------------------------------------------------
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 True
1080 #============================================================
1081 # main
1082 #------------------------------------------------------------
1083 if __name__ == "__main__":
1084
1085 import sys
1086
1087 from Gnumed.pycommon import gmPG2
1088 #--------------------------------------------------------
1090 """
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_lines
1138 #--------------------------------------------------------
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 Mon Jun 25 03:58:39 2012 | http://epydoc.sourceforge.net |