| Home | Trees | Indices | Help |
|
|---|
|
|
1 """GNUmed narrative handling widgets."""
2 #================================================================
3 __author__ = "Karsten Hilbert <Karsten.Hilbert@gmx.net>"
4 __license__ = "GPL v2 or later (details at http://www.gnu.org)"
5
6 import sys
7 import logging
8 import os
9 import os.path
10 import time
11 import re as regex
12 import shutil
13
14
15 import wx
16 import wx.lib.expando as wx_expando
17 import wx.lib.agw.supertooltip as agw_stt
18 import wx.lib.statbmp as wx_genstatbmp
19
20
21 if __name__ == '__main__':
22 sys.path.insert(0, '../../')
23
24 from Gnumed.pycommon import gmI18N
25
26 if __name__ == '__main__':
27 gmI18N.activate_locale()
28 gmI18N.install_domain()
29
30 from Gnumed.pycommon import gmDispatcher
31 from Gnumed.pycommon import gmTools
32 from Gnumed.pycommon import gmDateTime
33 from Gnumed.pycommon import gmShellAPI
34 from Gnumed.pycommon import gmPG2
35 from Gnumed.pycommon import gmCfg
36 from Gnumed.pycommon import gmMatchProvider
37
38 from Gnumed.business import gmPerson
39 from Gnumed.business import gmStaff
40 from Gnumed.business import gmEMRStructItems
41 from Gnumed.business import gmClinNarrative
42 from Gnumed.business import gmSurgery
43 from Gnumed.business import gmForms
44 from Gnumed.business import gmDocuments
45 from Gnumed.business import gmPersonSearch
46 from Gnumed.business import gmKeywordExpansion
47
48 from Gnumed.wxpython import gmListWidgets
49 from Gnumed.wxpython import gmEMRStructWidgets
50 from Gnumed.wxpython import gmRegetMixin
51 from Gnumed.wxpython import gmPhraseWheel
52 from Gnumed.wxpython import gmGuiHelpers
53 from Gnumed.wxpython import gmPatSearchWidgets
54 from Gnumed.wxpython import gmCfgWidgets
55 from Gnumed.wxpython import gmDocumentWidgets
56 from Gnumed.wxpython import gmKeywordExpansionWidgets
57
58 from Gnumed.exporters import gmPatientExporter
59
60
61 _log = logging.getLogger('gm.ui')
62 #============================================================
63 # narrative related widgets/functions
64 #------------------------------------------------------------
65 -def move_progress_notes_to_another_encounter(parent=None, encounters=None, episodes=None, patient=None, move_all=False):
66
67 # sanity checks
68 if patient is None:
69 patient = gmPerson.gmCurrentPatient()
70
71 if not patient.connected:
72 gmDispatcher.send(signal = 'statustext', msg = _('Cannot move progress notes. No active patient.'))
73 return False
74
75 if parent is None:
76 parent = wx.GetApp().GetTopWindow()
77
78 emr = patient.get_emr()
79
80 if encounters is None:
81 encs = emr.get_encounters(episodes = episodes)
82 encounters = gmEMRStructWidgets.select_encounters (
83 parent = parent,
84 patient = patient,
85 single_selection = False,
86 encounters = encs
87 )
88 # cancelled
89 if encounters is None:
90 return True
91 # none selected
92 if len(encounters) == 0:
93 return True
94
95 notes = emr.get_clin_narrative (
96 encounters = encounters,
97 episodes = episodes
98 )
99
100 # which narrative
101 if move_all:
102 selected_narr = notes
103 else:
104 selected_narr = gmListWidgets.get_choices_from_list (
105 parent = parent,
106 caption = _('Moving progress notes between encounters ...'),
107 single_selection = False,
108 can_return_empty = True,
109 data = notes,
110 msg = _('\n Select the progress notes to move from the list !\n\n'),
111 columns = [_('when'), _('who'), _('type'), _('entry')],
112 choices = [
113 [ narr['date'].strftime('%x %H:%M'),
114 narr['provider'],
115 gmClinNarrative.soap_cat2l10n[narr['soap_cat']],
116 narr['narrative'].replace('\n', '/').replace('\r', '/')
117 ] for narr in notes
118 ]
119 )
120
121 if not selected_narr:
122 return True
123
124 # which encounter to move to
125 enc2move2 = gmEMRStructWidgets.select_encounters (
126 parent = parent,
127 patient = patient,
128 single_selection = True
129 )
130
131 if not enc2move2:
132 return True
133
134 for narr in selected_narr:
135 narr['pk_encounter'] = enc2move2['pk_encounter']
136 narr.save()
137
138 return True
139 #------------------------------------------------------------
141
142 # sanity checks
143 if patient is None:
144 patient = gmPerson.gmCurrentPatient()
145
146 if not patient.connected:
147 gmDispatcher.send(signal = 'statustext', msg = _('Cannot edit progress notes. No active patient.'))
148 return False
149
150 if parent is None:
151 parent = wx.GetApp().GetTopWindow()
152
153 emr = patient.get_emr()
154 #--------------------------
155 def delete(item):
156 if item is None:
157 return False
158 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
159 parent,
160 -1,
161 caption = _('Deleting progress note'),
162 question = _(
163 'Are you positively sure you want to delete this\n'
164 'progress note from the medical record ?\n'
165 '\n'
166 'Note that even if you chose to delete the entry it will\n'
167 'still be (invisibly) kept in the audit trail to protect\n'
168 'you from litigation because physical deletion is known\n'
169 'to be unlawful in some jurisdictions.\n'
170 ),
171 button_defs = (
172 {'label': _('Delete'), 'tooltip': _('Yes, delete the progress note.'), 'default': False},
173 {'label': _('Cancel'), 'tooltip': _('No, do NOT delete the progress note.'), 'default': True}
174 )
175 )
176 decision = dlg.ShowModal()
177
178 if decision != wx.ID_YES:
179 return False
180
181 gmClinNarrative.delete_clin_narrative(narrative = item['pk_narrative'])
182 return True
183 #--------------------------
184 def edit(item):
185 if item is None:
186 return False
187
188 dlg = gmGuiHelpers.cMultilineTextEntryDlg (
189 parent,
190 -1,
191 title = _('Editing progress note'),
192 msg = _('This is the original progress note:'),
193 data = item.format(left_margin = u' ', fancy = True),
194 text = item['narrative']
195 )
196 decision = dlg.ShowModal()
197
198 if decision != wx.ID_SAVE:
199 return False
200
201 val = dlg.value
202 dlg.Destroy()
203 if val.strip() == u'':
204 return False
205
206 item['narrative'] = val
207 item.save_payload()
208
209 return True
210 #--------------------------
211 def refresh(lctrl):
212 notes = emr.get_clin_narrative (
213 encounters = encounters,
214 episodes = episodes,
215 providers = [ gmStaff.gmCurrentProvider()['short_alias'] ]
216 )
217 lctrl.set_string_items(items = [
218 [ narr['date'].strftime('%x %H:%M'),
219 gmClinNarrative.soap_cat2l10n[narr['soap_cat']],
220 narr['narrative'].replace('\n', '/').replace('\r', '/')
221 ] for narr in notes
222 ])
223 lctrl.set_data(data = notes)
224 #--------------------------
225
226 gmListWidgets.get_choices_from_list (
227 parent = parent,
228 caption = _('Managing progress notes'),
229 msg = _(
230 '\n'
231 ' This list shows the progress notes by %s.\n'
232 '\n'
233 ) % gmStaff.gmCurrentProvider()['short_alias'],
234 columns = [_('when'), _('type'), _('entry')],
235 single_selection = True,
236 can_return_empty = False,
237 edit_callback = edit,
238 delete_callback = delete,
239 refresh_callback = refresh
240 )
241 #------------------------------------------------------------
243
244 if parent is None:
245 parent = wx.GetApp().GetTopWindow()
246
247 searcher = wx.TextEntryDialog (
248 parent = parent,
249 message = _('Enter (regex) term to search for across all EMRs:'),
250 caption = _('Text search across all EMRs'),
251 style = wx.OK | wx.CANCEL | wx.CENTRE
252 )
253 result = searcher.ShowModal()
254
255 if result != wx.ID_OK:
256 return
257
258 wx.BeginBusyCursor()
259 term = searcher.GetValue()
260 searcher.Destroy()
261 results = gmClinNarrative.search_text_across_emrs(search_term = term)
262 wx.EndBusyCursor()
263
264 if len(results) == 0:
265 gmGuiHelpers.gm_show_info (
266 _(
267 'Nothing found for search term:\n'
268 ' "%s"'
269 ) % term,
270 _('Search results')
271 )
272 return
273
274 items = [ [gmPerson.cIdentity(aPK_obj =
275 r['pk_patient'])['description_gender'], r['narrative'],
276 r['src_table']] for r in results ]
277
278 selected_patient = gmListWidgets.get_choices_from_list (
279 parent = parent,
280 caption = _('Search results for %s') % term,
281 choices = items,
282 columns = [_('Patient'), _('Match'), _('Match location')],
283 data = [ r['pk_patient'] for r in results ],
284 single_selection = True,
285 can_return_empty = False
286 )
287
288 if selected_patient is None:
289 return
290
291 wx.CallAfter(gmPatSearchWidgets.set_active_patient, patient = gmPerson.cIdentity(aPK_obj = selected_patient))
292 #------------------------------------------------------------
294
295 # sanity checks
296 if patient is None:
297 patient = gmPerson.gmCurrentPatient()
298
299 if not patient.connected:
300 gmDispatcher.send(signal = 'statustext', msg = _('Cannot search EMR. No active patient.'))
301 return False
302
303 if parent is None:
304 parent = wx.GetApp().GetTopWindow()
305
306 searcher = wx.TextEntryDialog (
307 parent = parent,
308 message = _('Enter search term:'),
309 caption = _('Text search of entire EMR of active patient'),
310 style = wx.OK | wx.CANCEL | wx.CENTRE
311 )
312 result = searcher.ShowModal()
313
314 if result != wx.ID_OK:
315 searcher.Destroy()
316 return False
317
318 wx.BeginBusyCursor()
319 val = searcher.GetValue()
320 searcher.Destroy()
321 emr = patient.get_emr()
322 rows = emr.search_narrative_simple(val)
323 wx.EndBusyCursor()
324
325 if len(rows) == 0:
326 gmGuiHelpers.gm_show_info (
327 _(
328 'Nothing found for search term:\n'
329 ' "%s"'
330 ) % val,
331 _('Search results')
332 )
333 return True
334
335 txt = u''
336 for row in rows:
337 txt += u'%s: %s\n' % (
338 row['soap_cat'],
339 row['narrative']
340 )
341
342 txt += u' %s: %s - %s %s\n' % (
343 _('Encounter'),
344 row['encounter_started'].strftime('%x %H:%M'),
345 row['encounter_ended'].strftime('%H:%M'),
346 row['encounter_type']
347 )
348 txt += u' %s: %s\n' % (
349 _('Episode'),
350 row['episode']
351 )
352 txt += u' %s: %s\n\n' % (
353 _('Health issue'),
354 row['health_issue']
355 )
356
357 msg = _(
358 'Search term was: "%s"\n'
359 '\n'
360 'Search results:\n\n'
361 '%s\n'
362 ) % (val, txt)
363
364 dlg = wx.MessageDialog (
365 parent = parent,
366 message = msg,
367 caption = _('Search results for %s') % val,
368 style = wx.OK | wx.STAY_ON_TOP
369 )
370 dlg.ShowModal()
371 dlg.Destroy()
372
373 return True
374 #------------------------------------------------------------
376
377 # sanity checks
378 pat = gmPerson.gmCurrentPatient()
379 if not pat.connected:
380 gmDispatcher.send(signal = 'statustext', msg = _('Cannot export EMR for Medistar. No active patient.'))
381 return False
382
383 if encounter is None:
384 encounter = pat.get_emr().active_encounter
385
386 if parent is None:
387 parent = wx.GetApp().GetTopWindow()
388
389 # get file name
390 aWildcard = "%s (*.txt)|*.txt|%s (*)|*" % (_("text files"), _("all files"))
391 # FIXME: make configurable
392 aDefDir = os.path.abspath(os.path.expanduser(os.path.join('~', 'gnumed','export')))
393 # FIXME: make configurable
394 fname = '%s-%s-%s-%s-%s.txt' % (
395 'Medistar-MD',
396 time.strftime('%Y-%m-%d',time.localtime()),
397 pat['lastnames'].replace(' ', '-'),
398 pat['firstnames'].replace(' ', '_'),
399 pat.get_formatted_dob(format = '%Y-%m-%d')
400 )
401 dlg = wx.FileDialog (
402 parent = parent,
403 message = _("Save EMR extract for MEDISTAR import as..."),
404 defaultDir = aDefDir,
405 defaultFile = fname,
406 wildcard = aWildcard,
407 style = wx.SAVE
408 )
409 choice = dlg.ShowModal()
410 fname = dlg.GetPath()
411 dlg.Destroy()
412 if choice != wx.ID_OK:
413 return False
414
415 wx.BeginBusyCursor()
416 _log.debug('exporting encounter for medistar import to [%s]', fname)
417 exporter = gmPatientExporter.cMedistarSOAPExporter()
418 successful, fname = exporter.export_to_file (
419 filename = fname,
420 encounter = encounter,
421 soap_cats = u'soapu',
422 export_to_import_file = True
423 )
424 if not successful:
425 gmGuiHelpers.gm_show_error (
426 _('Error exporting progress notes for MEDISTAR import.'),
427 _('MEDISTAR progress notes export')
428 )
429 wx.EndBusyCursor()
430 return False
431
432 gmDispatcher.send(signal = 'statustext', msg = _('Successfully exported progress notes into file [%s] for Medistar import.') % fname, beep=False)
433
434 wx.EndBusyCursor()
435 return True
436 #------------------------------------------------------------
438
439 pat = gmPerson.gmCurrentPatient()
440 emr = pat.get_emr()
441
442 # not useful if you think about it:
443 # issues = [ i for i in emr.health_issues ]
444 # if len(issues) == 0:
445 # gmDispatcher.send(signal = 'statustext', msg = _('No progress notes found.'))
446 # return []
447
448 if parent is None:
449 parent = wx.GetApp().GetTopWindow()
450
451 if soap_cats is None:
452 soap_cats = u'soapu'
453 soap_cats = list(soap_cats)
454 i18n_soap_cats = [ gmClinNarrative.soap_cat2l10n[cat].upper() for cat in soap_cats ]
455
456 selected_soap = {}
457 #selected_narrative_pks = []
458
459 #-----------------------------------------------
460 def get_soap_tooltip(soap):
461 return soap.format(fancy = True, width = 60)
462 #-----------------------------------------------
463 def pick_soap_from_issue(issue):
464
465 if issue is None:
466 return False
467
468 narr_for_issue = emr.get_clin_narrative(issues = [issue['pk_health_issue']], soap_cats = soap_cats)
469
470 if len(narr_for_issue) == 0:
471 gmDispatcher.send(signal = 'statustext', msg = _('No narrative available for this health issue.'))
472 return True
473
474 selected_narr = gmListWidgets.get_choices_from_list (
475 parent = parent,
476 msg = _('Pick the [%s] narrative you want to include in the report.') % u'/'.join(i18n_soap_cats),
477 caption = _('Picking [%s] from %s%s%s') % (
478 u'/'.join(i18n_soap_cats),
479 gmTools.u_left_double_angle_quote,
480 issue['description'],
481 gmTools.u_right_double_angle_quote
482 ),
483 columns = [_('When'), _('Who'), _('Type'), _('Entry')],
484 choices = [ [
485 gmDateTime.pydt_strftime(narr['date'], '%Y %b %d %H:%M', accuracy = gmDateTime.acc_minutes),
486 narr['provider'],
487 gmClinNarrative.soap_cat2l10n[narr['soap_cat']],
488 narr['narrative'].replace('\n', '//').replace('\r', '//')
489 ] for narr in narr_for_issue ],
490 data = narr_for_issue,
491 #selections=None,
492 #edit_callback=None,
493 single_selection = False,
494 can_return_empty = False,
495 list_tooltip_callback = get_soap_tooltip
496 )
497
498 if selected_narr is None:
499 return True
500
501 for narr in selected_narr:
502 selected_soap[narr['pk_narrative']] = narr
503
504 return True
505 #-----------------------------------------------
506 def edit_issue(issue):
507 return gmEMRStructWidgets.edit_health_issue(parent = parent, issue = issue)
508 #-----------------------------------------------
509 def refresh_issues(lctrl):
510 #issues = [ i for i in emr.health_issues ]
511 issues = emr.health_issues
512 lctrl.set_string_items ([ [
513 gmTools.bool2subst(i['is_confidential'], _('!! CONFIDENTIAL !!'), u''),
514 i['description'],
515 gmTools.bool2subst(i['is_active'], _('active'), _('inactive'))
516 ] for i in issues
517 ])
518 lctrl.set_data(issues)
519 #-----------------------------------------------
520 def get_issue_tooltip(issue):
521 return issue.format (
522 patient = pat,
523 with_encounters = False,
524 with_medications = False,
525 with_hospital_stays = False,
526 with_procedures = False,
527 with_family_history = False,
528 with_documents = False,
529 with_tests = False,
530 with_vaccinations = False
531 )
532 #-----------------------------------------------
533 #selected_episode_pks = []
534
535 issues_picked_from = gmListWidgets.get_choices_from_list (
536 parent = parent,
537 msg = _('\n Select the issue you want to report on.'),
538 caption = _('Picking [%s] from health issues') % u'/'.join(i18n_soap_cats),
539 columns = [_('Privacy'), _('Issue'), _('Status')],
540 edit_callback = edit_issue,
541 refresh_callback = refresh_issues,
542 single_selection = True,
543 can_return_empty = True,
544 ignore_OK_button = False,
545 left_extra_button = (
546 _('&Pick notes'),
547 _('Pick [%s] entries from selected health issue') % u'/'.join(i18n_soap_cats),
548 pick_soap_from_issue
549 ),
550 list_tooltip_callback = get_issue_tooltip
551 )
552
553 if issues_picked_from is None:
554 return []
555
556 return selected_soap.values()
557
558 # selection_idxs = []
559 # for idx in range(len(all_epis)):
560 # if all_epis[idx]['pk_episode'] in selected_episode_pks:
561 # selection_idxs.append(idx)
562 # if len(selection_idxs) != 0:
563 # dlg.set_selections(selections = selection_idxs)
564 #------------------------------------------------------------
566
567 pat = gmPerson.gmCurrentPatient()
568 emr = pat.get_emr()
569
570 all_epis = [ epi for epi in emr.get_episodes(order_by = u'description') if epi.has_narrative ]
571 if len(all_epis) == 0:
572 gmDispatcher.send(signal = 'statustext', msg = _('No episodes with progress notes found.'))
573 return []
574
575 if parent is None:
576 parent = wx.GetApp().GetTopWindow()
577
578 if soap_cats is None:
579 soap_cats = u'soapu'
580 soap_cats = list(soap_cats)
581 i18n_soap_cats = [ gmClinNarrative.soap_cat2l10n[cat].upper() for cat in soap_cats ]
582
583 selected_soap = {}
584 #selected_narrative_pks = []
585
586 #-----------------------------------------------
587 def get_soap_tooltip(soap):
588 return soap.format(fancy = True, width = 60)
589 #-----------------------------------------------
590 def pick_soap_from_episode(episode):
591
592 if episode is None:
593 return False
594
595 narr_for_epi = emr.get_clin_narrative(episodes = [episode['pk_episode']], soap_cats = soap_cats)
596
597 if len(narr_for_epi) == 0:
598 gmDispatcher.send(signal = 'statustext', msg = _('No narrative available for selected episode.'))
599 return True
600
601 selected_narr = gmListWidgets.get_choices_from_list (
602 parent = parent,
603 msg = _('Pick the [%s] narrative you want to include in the report.') % u'/'.join(i18n_soap_cats),
604 caption = _('Picking [%s] from %s%s%s') % (
605 u'/'.join(i18n_soap_cats),
606 gmTools.u_left_double_angle_quote,
607 episode['description'],
608 gmTools.u_right_double_angle_quote
609 ),
610 columns = [_('When'), _('Who'), _('Type'), _('Entry')],
611 choices = [ [
612 gmDateTime.pydt_strftime(narr['date'], '%Y %b %d %H:%M', accuracy = gmDateTime.acc_minutes),
613 narr['provider'],
614 gmClinNarrative.soap_cat2l10n[narr['soap_cat']],
615 narr['narrative'].replace('\n', '//').replace('\r', '//')
616 ] for narr in narr_for_epi ],
617 data = narr_for_epi,
618 #selections=None,
619 #edit_callback=None,
620 single_selection = False,
621 can_return_empty = False,
622 list_tooltip_callback = get_soap_tooltip
623 )
624
625 if selected_narr is None:
626 return True
627
628 for narr in selected_narr:
629 selected_soap[narr['pk_narrative']] = narr
630
631 return True
632
633 # selection_idxs = []
634 # for idx in range(len(narr_for_epi)):
635 # if narr_for_epi[idx]['pk_narrative'] in selected_narrative_pks:
636 # selection_idxs.append(idx)
637 # if len(selection_idxs) != 0:
638 # dlg.set_selections(selections = selection_idxs)
639
640 # selected_narrative_pks = [ i['pk_narrative'] for i in selected_narr ]
641 # for narr in selected_narr:
642 # selected_soap[narr['pk_narrative']] = narr
643 #
644 # print "before returning from picking soap"
645 #
646 # return True
647 # #-----------------------------------------------
648 def edit_episode(episode):
649 return gmEMRStructWidgets.edit_episode(parent = parent, episode = episode)
650 #-----------------------------------------------
651 def refresh_episodes(lctrl):
652 all_epis = [ epi for epi in emr.get_episodes(order_by = u'description') if epi.has_narrative ]
653 lctrl.set_string_items ([ [
654 u'%s%s' % (e['description'], gmTools.coalesce(e['health_issue'], u'', u' (%s)')),
655 gmTools.bool2subst(e['episode_open'], _('open'), _('closed'))
656 ] for e in all_epis
657 ])
658 lctrl.set_data(all_epis)
659 #-----------------------------------------------
660 def get_episode_tooltip(episode):
661 return episode.format (
662 patient = pat,
663 with_encounters = False,
664 with_documents = False,
665 with_hospital_stays = False,
666 with_procedures = False,
667 with_family_history = False,
668 with_tests = False,
669 with_vaccinations = False
670 )
671 #-----------------------------------------------
672 #selected_episode_pks = []
673
674 epis_picked_from = gmListWidgets.get_choices_from_list (
675 parent = parent,
676 msg = _('\n Select the episode you want to report on.'),
677 caption = _('Picking [%s] from episodes') % u'/'.join(i18n_soap_cats),
678 columns = [_('Episode'), _('Status')],
679 edit_callback = edit_episode,
680 refresh_callback = refresh_episodes,
681 single_selection = True,
682 can_return_empty = True,
683 ignore_OK_button = False,
684 left_extra_button = (
685 _('&Pick notes'),
686 _('Pick [%s] entries from selected episode') % u'/'.join(i18n_soap_cats),
687 pick_soap_from_episode
688 ),
689 list_tooltip_callback = get_episode_tooltip
690 )
691
692 if epis_picked_from is None:
693 return []
694
695 return selected_soap.values()
696
697 # selection_idxs = []
698 # for idx in range(len(all_epis)):
699 # if all_epis[idx]['pk_episode'] in selected_episode_pks:
700 # selection_idxs.append(idx)
701 # if len(selection_idxs) != 0:
702 # dlg.set_selections(selections = selection_idxs)
703 #------------------------------------------------------------
705 """soap_cats needs to be a list"""
706
707 pat = gmPerson.gmCurrentPatient()
708 emr = pat.get_emr()
709
710 if parent is None:
711 parent = wx.GetApp().GetTopWindow()
712
713 selected_soap = {}
714 selected_issue_pks = []
715 selected_episode_pks = []
716 selected_narrative_pks = []
717
718 while 1:
719 # 1) select health issues to select episodes from
720 all_issues = emr.get_health_issues()
721 all_issues.insert(0, gmEMRStructItems.get_dummy_health_issue())
722 dlg = gmEMRStructWidgets.cIssueListSelectorDlg (
723 parent = parent,
724 id = -1,
725 issues = all_issues,
726 msg = _('\n In the list below mark the health issues you want to report on.\n')
727 )
728 selection_idxs = []
729 for idx in range(len(all_issues)):
730 if all_issues[idx]['pk_health_issue'] in selected_issue_pks:
731 selection_idxs.append(idx)
732 if len(selection_idxs) != 0:
733 dlg.set_selections(selections = selection_idxs)
734 btn_pressed = dlg.ShowModal()
735 selected_issues = dlg.get_selected_item_data()
736 dlg.Destroy()
737
738 if btn_pressed == wx.ID_CANCEL:
739 return selected_soap.values()
740
741 selected_issue_pks = [ i['pk_health_issue'] for i in selected_issues ]
742
743 while 1:
744 # 2) select episodes to select items from
745 all_epis = emr.get_episodes(issues = selected_issue_pks)
746
747 if len(all_epis) == 0:
748 gmDispatcher.send(signal = 'statustext', msg = _('No episodes recorded for the health issues selected.'))
749 break
750
751 dlg = gmEMRStructWidgets.cEpisodeListSelectorDlg (
752 parent = parent,
753 id = -1,
754 episodes = all_epis,
755 msg = _(
756 '\n These are the episodes known for the health issues just selected.\n\n'
757 ' Now, mark the the episodes you want to report on.\n'
758 )
759 )
760 selection_idxs = []
761 for idx in range(len(all_epis)):
762 if all_epis[idx]['pk_episode'] in selected_episode_pks:
763 selection_idxs.append(idx)
764 if len(selection_idxs) != 0:
765 dlg.set_selections(selections = selection_idxs)
766 btn_pressed = dlg.ShowModal()
767 selected_epis = dlg.get_selected_item_data()
768 dlg.Destroy()
769
770 if btn_pressed == wx.ID_CANCEL:
771 break
772
773 selected_episode_pks = [ i['pk_episode'] for i in selected_epis ]
774
775 # 3) select narrative corresponding to the above constraints
776 all_narr = emr.get_clin_narrative(episodes = selected_episode_pks, soap_cats = soap_cats)
777
778 if len(all_narr) == 0:
779 gmDispatcher.send(signal = 'statustext', msg = _('No narrative available for selected episodes.'))
780 continue
781
782 dlg = cNarrativeListSelectorDlg (
783 parent = parent,
784 id = -1,
785 narrative = all_narr,
786 msg = _(
787 '\n This is the narrative (type %s) for the chosen episodes.\n\n'
788 ' Now, mark the entries you want to include in your report.\n'
789 ) % u'/'.join([ gmClinNarrative.soap_cat2l10n[cat] for cat in gmTools.coalesce(soap_cats, list(u'soapu')) ])
790 )
791 selection_idxs = []
792 for idx in range(len(all_narr)):
793 if all_narr[idx]['pk_narrative'] in selected_narrative_pks:
794 selection_idxs.append(idx)
795 if len(selection_idxs) != 0:
796 dlg.set_selections(selections = selection_idxs)
797 btn_pressed = dlg.ShowModal()
798 selected_narr = dlg.get_selected_item_data()
799 dlg.Destroy()
800
801 if btn_pressed == wx.ID_CANCEL:
802 continue
803
804 selected_narrative_pks = [ i['pk_narrative'] for i in selected_narr ]
805 for narr in selected_narr:
806 selected_soap[narr['pk_narrative']] = narr
807 #------------------------------------------------------------
809
811
812 narrative = kwargs['narrative']
813 del kwargs['narrative']
814
815 gmListWidgets.cGenericListSelectorDlg.__init__(self, *args, **kwargs)
816
817 self.SetTitle(_('Select the narrative you are interested in ...'))
818 # FIXME: add epi/issue
819 self._LCTRL_items.set_columns([_('when'), _('who'), _('type'), _('entry')]) #, _('Episode'), u'', _('Health Issue')])
820 # FIXME: date used should be date of encounter, not date_modified
821 self._LCTRL_items.set_string_items (
822 items = [ [narr['date'].strftime('%x %H:%M'), narr['provider'], gmClinNarrative.soap_cat2l10n[narr['soap_cat']], narr['narrative'].replace('\n', '/').replace('\r', '/')] for narr in narrative ]
823 )
824 self._LCTRL_items.set_column_widths()
825 self._LCTRL_items.set_data(data = narrative)
826 #------------------------------------------------------------
827 from Gnumed.wxGladeWidgets import wxgMoveNarrativeDlg
828
830
832
833 self.encounter = kwargs['encounter']
834 self.source_episode = kwargs['episode']
835 del kwargs['encounter']
836 del kwargs['episode']
837
838 wxgMoveNarrativeDlg.wxgMoveNarrativeDlg.__init__(self, *args, **kwargs)
839
840 self.LBL_source_episode.SetLabel(u'%s%s' % (self.source_episode['description'], gmTools.coalesce(self.source_episode['health_issue'], u'', u' (%s)')))
841 self.LBL_encounter.SetLabel('%s: %s %s - %s' % (
842 self.encounter['started'].strftime('%x').decode(gmI18N.get_encoding()),
843 self.encounter['l10n_type'],
844 self.encounter['started'].strftime('%H:%M'),
845 self.encounter['last_affirmed'].strftime('%H:%M')
846 ))
847 pat = gmPerson.gmCurrentPatient()
848 emr = pat.get_emr()
849 narr = emr.get_clin_narrative(episodes=[self.source_episode['pk_episode']], encounters=[self.encounter['pk_encounter']])
850 if len(narr) == 0:
851 narr = [{'narrative': _('There is no narrative for this episode in this encounter.')}]
852 self.LBL_narrative.SetLabel(u'\n'.join([n['narrative'] for n in narr]))
853
854 #------------------------------------------------------------
876 #============================================================
877 from Gnumed.wxGladeWidgets import wxgSoapPluginPnl
878
880 """A panel for in-context editing of progress notes.
881
882 Expects to be used as a notebook page.
883
884 Left hand side:
885 - problem list (health issues and active episodes)
886 - previous notes
887
888 Right hand side:
889 - panel handling
890 - encounter details fields
891 - notebook with progress note editors
892 - visual progress notes
893
894 Listens to patient change signals, thus acts on the current patient.
895 """
897
898 wxgSoapPluginPnl.wxgSoapPluginPnl.__init__(self, *args, **kwargs)
899 gmRegetMixin.cRegetOnPaintMixin.__init__(self)
900
901 self.__pat = gmPerson.gmCurrentPatient()
902 self.__init_ui()
903 self.__reset_ui_content()
904 self.__register_interests()
905 #--------------------------------------------------------
906 # internal helpers
907 #--------------------------------------------------------
909 self._LCTRL_active_problems.set_columns([_('Last'), _('Problem'), _('In health issue')])
910 self._LCTRL_active_problems.set_string_items()
911
912 self._splitter_main.SetSashGravity(0.5)
913 self._splitter_left.SetSashGravity(0.5)
914
915 splitter_size = self._splitter_main.GetSizeTuple()[0]
916 self._splitter_main.SetSashPosition(splitter_size * 3 / 10, True)
917
918 splitter_size = self._splitter_left.GetSizeTuple()[1]
919 self._splitter_left.SetSashPosition(splitter_size * 6 / 20, True)
920 #--------------------------------------------------------
922 """Clear all information from input panel."""
923
924 self._LCTRL_active_problems.set_string_items()
925
926 self._TCTRL_recent_notes.SetValue(u'')
927 self._SZR_recent_notes_staticbox.SetLabel(_('Most recent notes on selected problem'))
928
929 self._PNL_editors.patient = None
930 #--------------------------------------------------------
932 """Update health problems list."""
933
934 self._LCTRL_active_problems.set_string_items()
935
936 emr = self.__pat.get_emr()
937 problems = emr.get_problems (
938 include_closed_episodes = self._CHBOX_show_closed_episodes.IsChecked(),
939 include_irrelevant_issues = self._CHBOX_irrelevant_issues.IsChecked()
940 )
941
942 list_items = []
943 active_problems = []
944 for problem in problems:
945 if not problem['problem_active']:
946 if not problem['is_potential_problem']:
947 continue
948
949 active_problems.append(problem)
950
951 if problem['type'] == 'issue':
952 issue = emr.problem2issue(problem)
953 last_encounter = emr.get_last_encounter(issue_id = issue['pk_health_issue'])
954 if last_encounter is None:
955 last = issue['modified_when'].strftime('%m/%Y')
956 else:
957 last = last_encounter['last_affirmed'].strftime('%m/%Y')
958
959 list_items.append([last, problem['problem'], gmTools.u_left_arrow_with_tail])
960
961 elif problem['type'] == 'episode':
962 epi = emr.problem2episode(problem)
963 last_encounter = emr.get_last_encounter(episode_id = epi['pk_episode'])
964 if last_encounter is None:
965 last = epi['episode_modified_when'].strftime('%m/%Y')
966 else:
967 last = last_encounter['last_affirmed'].strftime('%m/%Y')
968
969 list_items.append ([
970 last,
971 problem['problem'],
972 gmTools.coalesce(initial = epi['health_issue'], instead = u'?') #gmTools.u_diameter
973 ])
974
975 self._LCTRL_active_problems.set_string_items(items = list_items)
976 self._LCTRL_active_problems.set_column_widths()
977 self._LCTRL_active_problems.set_data(data = active_problems)
978
979 showing_potential_problems = (
980 self._CHBOX_show_closed_episodes.IsChecked()
981 or
982 self._CHBOX_irrelevant_issues.IsChecked()
983 )
984 if showing_potential_problems:
985 self._SZR_problem_list_staticbox.SetLabel(_('%s (active+potential) problems') % len(list_items))
986 else:
987 self._SZR_problem_list_staticbox.SetLabel(_('%s active problems') % len(list_items))
988
989 return True
990 #--------------------------------------------------------
992 soap = u''
993 emr = self.__pat.get_emr()
994 prev_enc = emr.get_last_but_one_encounter(issue_id = problem['pk_health_issue'])
995 if prev_enc is not None:
996 soap += prev_enc.format (
997 issues = [ problem['pk_health_issue'] ],
998 with_soap = True,
999 with_docs = fancy,
1000 with_tests = fancy,
1001 patient = self.__pat,
1002 fancy_header = False,
1003 with_rfe_aoe = True
1004 )
1005
1006 tmp = emr.active_encounter.format_soap (
1007 soap_cats = 'soapu',
1008 emr = emr,
1009 issues = [ problem['pk_health_issue'] ],
1010 )
1011 if len(tmp) > 0:
1012 soap += _('Current encounter:') + u'\n'
1013 soap += u'\n'.join(tmp) + u'\n'
1014
1015 if problem['summary'] is not None:
1016 soap += u'\n-- %s ----------\n%s' % (
1017 _('Cumulative summary'),
1018 gmTools.wrap (
1019 text = problem['summary'],
1020 width = 45,
1021 initial_indent = u' ',
1022 subsequent_indent = u' '
1023 ).strip('\n')
1024 )
1025
1026 return soap
1027 #--------------------------------------------------------
1029 soap = u''
1030 emr = self.__pat.get_emr()
1031 prev_enc = emr.get_last_but_one_encounter(episode_id = problem['pk_episode'])
1032 if prev_enc is not None:
1033 soap += prev_enc.format (
1034 episodes = [ problem['pk_episode'] ],
1035 with_soap = True,
1036 with_docs = fancy,
1037 with_tests = fancy,
1038 patient = self.__pat,
1039 fancy_header = False,
1040 with_rfe_aoe = True
1041 )
1042 else:
1043 if problem['pk_health_issue'] is not None:
1044 prev_enc = emr.get_last_but_one_encounter(episode_id = problem['pk_health_issue'])
1045 if prev_enc is not None:
1046 soap += prev_enc.format (
1047 with_soap = True,
1048 with_docs = fancy,
1049 with_tests = fancy,
1050 patient = self.__pat,
1051 issues = [ problem['pk_health_issue'] ],
1052 fancy_header = False,
1053 with_rfe_aoe = True
1054 )
1055
1056 tmp = emr.active_encounter.format_soap (
1057 soap_cats = 'soapu',
1058 emr = emr,
1059 issues = [ problem['pk_health_issue'] ],
1060 )
1061 if len(tmp) > 0:
1062 soap += _('Current encounter:') + u'\n'
1063 soap += u'\n'.join(tmp) + u'\n'
1064
1065 if problem['summary'] is not None:
1066 soap += u'\n-- %s ----------\n%s' % (
1067 _('Cumulative summary'),
1068 gmTools.wrap (
1069 text = problem['summary'],
1070 width = 45,
1071 initial_indent = u' ',
1072 subsequent_indent = u' '
1073 ).strip('\n')
1074 )
1075
1076 return soap
1077 #--------------------------------------------------------
1079 """This refreshes the recent-notes part."""
1080
1081 if problem is None:
1082 caption = u'<?>'
1083 txt = u''
1084 elif problem['type'] == u'issue':
1085 caption = problem['problem'][:35]
1086 txt = self.__get_info_for_issue_problem(problem = problem, fancy = not self._RBTN_notes_only.GetValue())
1087 elif problem['type'] == u'episode':
1088 caption = problem['problem'][:35]
1089 txt = self.__get_info_for_episode_problem(problem = problem, fancy = not self._RBTN_notes_only.GetValue())
1090
1091 self._TCTRL_recent_notes.SetValue(txt)
1092 self._TCTRL_recent_notes.ShowPosition(self._TCTRL_recent_notes.GetLastPosition())
1093 self._SZR_recent_notes_staticbox.SetLabel(_('Most recent info on %s%s%s') % (
1094 gmTools.u_left_double_angle_quote,
1095 caption,
1096 gmTools.u_right_double_angle_quote
1097 ))
1098
1099 self._TCTRL_recent_notes.Refresh()
1100
1101 return True
1102 #--------------------------------------------------------
1103 # event handling
1104 #--------------------------------------------------------
1106 """Configure enabled event signals."""
1107 # client internal signals
1108 gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._on_pre_patient_selection)
1109 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
1110 gmDispatcher.connect(signal = u'episode_mod_db', receiver = self._on_episode_issue_mod_db)
1111 gmDispatcher.connect(signal = u'health_issue_mod_db', receiver = self._on_episode_issue_mod_db)
1112 gmDispatcher.connect(signal = u'episode_code_mod_db', receiver = self._on_episode_issue_mod_db)
1113 #--------------------------------------------------------
1116
1119 #--------------------------------------------------------
1122
1126 #--------------------------------------------------------
1129 #--------------------------------------------------------
1130 # problem list specific events
1131 #--------------------------------------------------------
1135 #--------------------------------------------------------
1137 problem = self._LCTRL_active_problems.get_selected_item_data(only_one = True)
1138 if problem['type'] == u'issue':
1139 gmEMRStructWidgets.edit_health_issue(parent = self, issue = problem.get_as_health_issue())
1140 return
1141
1142 if problem['type'] == u'episode':
1143 gmEMRStructWidgets.edit_episode(parent = self, episode = problem.get_as_episode())
1144 return
1145
1146 event.Skip()
1147 #--------------------------------------------------------
1149 """Show related note at the bottom."""
1150 self.__refresh_recent_notes (
1151 problem = self._LCTRL_active_problems.get_selected_item_data(only_one = True)
1152 )
1153 #--------------------------------------------------------
1155 """Open progress note editor for this problem.
1156 """
1157 problem = self._LCTRL_active_problems.get_selected_item_data(only_one = True)
1158 if problem is None:
1159 return True
1160
1161 dbcfg = gmCfg.cCfgSQL()
1162 allow_duplicate_editors = bool(dbcfg.get2 (
1163 option = u'horstspace.soap_editor.allow_same_episode_multiple_times',
1164 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1165 bias = u'user',
1166 default = False
1167 ))
1168 if self._PNL_editors.add_editor(problem = problem, allow_same_problem = allow_duplicate_editors):
1169 return True
1170
1171 gmGuiHelpers.gm_show_error (
1172 aMessage = _(
1173 'Cannot open progress note editor for\n\n'
1174 '[%s].\n\n'
1175 ) % problem['problem'],
1176 aTitle = _('opening progress note editor')
1177 )
1178 return False
1179 #--------------------------------------------------------
1182 #--------------------------------------------------------
1185 #--------------------------------------------------------
1186 # recent-notes specific events
1187 #--------------------------------------------------------
1189 self.__refresh_recent_notes (
1190 problem = self._LCTRL_active_problems.get_selected_item_data(only_one = True)
1191 )
1192 #--------------------------------------------------------
1194 self.__refresh_recent_notes (
1195 problem = self._LCTRL_active_problems.get_selected_item_data(only_one = True)
1196 )
1197 #--------------------------------------------------------
1198 # reget mixin API
1199 #--------------------------------------------------------
1200 # only needed for debugging:
1201 #def _schedule_data_reget(self):
1202 # gmRegetMixin.cRegetOnPaintMixin._schedule_data_reget(self)
1203 #--------------------------------------------------------
1207
1208 #============================================================
1209 from Gnumed.wxGladeWidgets import wxgFancySoapEditorPnl
1210
1212 """A panel holding everything needed to edit
1213
1214 - encounter metadata
1215 - textual progress notes
1216 - visual progress notes
1217
1218 in context. Does NOT act on the current patient.
1219 """
1221
1222 wxgFancySoapEditorPnl.wxgFancySoapEditorPnl.__init__(self, *args, **kwargs)
1223
1224 self.__init_ui()
1225 self.patient = None
1226 self.__register_interests()
1227 #--------------------------------------------------------
1228 # public API
1229 #--------------------------------------------------------
1231 return self._NB_soap_editors.add_editor(problem = problem, allow_same_problem = allow_same_problem)
1232 #--------------------------------------------------------
1235
1237 #if
1238 # self.__pat.register_pre_selection_callback(callback = self._pre_selection_callback)
1239 self.__pat = patient
1240 self.__refresh_encounter()
1241 self.__refresh_soap_notebook()
1242
1243 patient = property(_get_patient, _set_patient)
1244 #--------------------------------------------------------
1246
1247 if self.__pat is None:
1248 return True
1249
1250 if not self.__encounter_valid_for_save():
1251 return False
1252
1253 enc = self.__pat.emr.active_encounter
1254
1255 rfe = self._TCTRL_rfe.GetValue().strip()
1256 if len(rfe) == 0:
1257 enc['reason_for_encounter'] = None
1258 else:
1259 enc['reason_for_encounter'] = rfe
1260 aoe = self._TCTRL_aoe.GetValue().strip()
1261 if len(aoe) == 0:
1262 enc['assessment_of_encounter'] = None
1263 else:
1264 enc['assessment_of_encounter'] = aoe
1265
1266 enc.save_payload()
1267
1268 enc.generic_codes_rfe = [ c['data'] for c in self._PRW_rfe_codes.GetData() ]
1269 enc.generic_codes_aoe = [ c['data'] for c in self._PRW_aoe_codes.GetData() ]
1270
1271 return True
1272 #--------------------------------------------------------
1273 # internal helpers
1274 #--------------------------------------------------------
1277 #--------------------------------------------------------
1281 #--------------------------------------------------------
1283 self.__reset_soap_notebook()
1284
1285 if self.__pat is None:
1286 return
1287
1288 dbcfg = gmCfg.cCfgSQL()
1289 auto_open_recent_problems = bool(dbcfg.get2 (
1290 option = u'horstspace.soap_editor.auto_open_latest_episodes',
1291 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1292 bias = u'user',
1293 default = True
1294 ))
1295
1296 emr = self.__pat.emr
1297 recent_epis = emr.active_encounter.get_episodes()
1298 prev_enc = emr.get_last_but_one_encounter()
1299 if prev_enc is not None:
1300 recent_epis.extend(prev_enc.get_episodes())
1301
1302 for epi in recent_epis:
1303 if not epi['episode_open']:
1304 continue
1305 self._NB_soap_editors.add_editor(problem = epi)
1306 #--------------------------------------------------------
1308 self._TCTRL_rfe.SetValue(u'')
1309 self._PRW_rfe_codes.SetText(suppress_smarts = True)
1310 self._TCTRL_aoe.SetValue(u'')
1311 self._PRW_aoe_codes.SetText(suppress_smarts = True)
1312 #--------------------------------------------------------
1314 """Update encounter fields."""
1315
1316 self.__reset_encounter_fields()
1317
1318 if self.__pat is None:
1319 return
1320
1321 enc = self.__pat.emr.active_encounter
1322
1323 self._TCTRL_rfe.SetValue(gmTools.coalesce(enc['reason_for_encounter'], u''))
1324 val, data = self._PRW_rfe_codes.generic_linked_codes2item_dict(enc.generic_codes_rfe)
1325 self._PRW_rfe_codes.SetText(val, data)
1326
1327 self._TCTRL_aoe.SetValue(gmTools.coalesce(enc['assessment_of_encounter'], u''))
1328 val, data = self._PRW_aoe_codes.generic_linked_codes2item_dict(enc.generic_codes_aoe)
1329 self._PRW_aoe_codes.SetText(val, data)
1330
1331 self._TCTRL_rfe.Refresh()
1332 self._PRW_rfe_codes.Refresh()
1333 self._TCTRL_aoe.Refresh()
1334 self._PRW_aoe_codes.Refresh()
1335 #--------------------------------------------------------
1337 self._NB_soap_editors.refresh_current_editor()
1338 # #--------------------------------------------------------
1339 # def __encounter_modified(self):
1340 # """Assumes that the field data is valid."""
1341 #
1342 # emr = self.__pat.get_emr()
1343 # enc = emr.active_encounter
1344 #
1345 # data = {
1346 # 'pk_type': enc['pk_type'],
1347 # 'reason_for_encounter': gmTools.none_if(self._TCTRL_rfe.GetValue().strip(), u''),
1348 # 'assessment_of_encounter': gmTools.none_if(self._TCTRL_aoe.GetValue().strip(), u''),
1349 # 'pk_location': enc['pk_location'],
1350 # 'pk_patient': enc['pk_patient'],
1351 # 'pk_generic_codes_rfe': self._PRW_rfe_codes.GetData(),
1352 # 'pk_generic_codes_aoe': self._PRW_aoe_codes.GetData(),
1353 # 'started': enc['started'],
1354 # 'last_affirmed': enc['last_affirmed']
1355 # }
1356 #
1357 # return not enc.same_payload(another_object = data)
1358 #--------------------------------------------------------
1361 #--------------------------------------------------------
1362 # event handling
1363 #--------------------------------------------------------
1365 """Configure enabled event signals."""
1366 # synchronous signals
1367 gmDispatcher.send(signal = u'register_pre_exit_callback', callback = self._pre_exit_callback)
1368
1369 # client internal signals
1370 gmDispatcher.connect(signal = u'doc_mod_db', receiver = self._on_doc_mod_db) # visual progress notes
1371 gmDispatcher.connect(signal = u'current_encounter_modified', receiver = self._on_current_encounter_modified)
1372 gmDispatcher.connect(signal = u'current_encounter_switched', receiver = self._on_current_encounter_switched)
1373 gmDispatcher.connect(signal = u'rfe_code_mod_db', receiver = self._on_encounter_code_modified)
1374 gmDispatcher.connect(signal = u'aoe_code_mod_db', receiver = self._on_encounter_code_modified)
1375 #--------------------------------------------------------
1377 """Another patient is about to be activated.
1378
1379 Patient change will not proceed before this returns True.
1380 """
1381 # don't worry about the encounter here - it will be offered
1382 # for editing higher up if anything was saved to the EMR
1383 if self.__pat is None:
1384 return True
1385 return self._NB_soap_editors.warn_on_unsaved_soap()
1386 #--------------------------------------------------------
1388 """The client is about to (be) shut down.
1389
1390 Shutdown will not proceed before this returns.
1391 """
1392 if self.__pat is None:
1393 return True
1394
1395 # if self.__encounter_modified():
1396 # do_save_enc = gmGuiHelpers.gm_show_question (
1397 # aMessage = _(
1398 # 'You have modified the details\n'
1399 # 'of the current encounter.\n'
1400 # '\n'
1401 # 'Do you want to save those changes ?'
1402 # ),
1403 # aTitle = _('Starting new encounter')
1404 # )
1405 # if do_save_enc:
1406 # if not self.save_encounter():
1407 # gmDispatcher.send(signal = u'statustext', msg = _('Error saving current encounter.'), beep = True)
1408
1409 saved = self._NB_soap_editors.save_all_editors (
1410 emr = self.__pat.emr,
1411 episode_name_candidates = [
1412 gmTools.none_if(self._TCTRL_aoe.GetValue().strip(), u''),
1413 gmTools.none_if(self._TCTRL_rfe.GetValue().strip(), u'')
1414 ]
1415 )
1416 if not saved:
1417 gmDispatcher.send(signal = 'statustext', msg = _('Cannot save all editors. Some were kept open.'), beep = True)
1418 return True
1419 #--------------------------------------------------------
1422 #--------------------------------------------------------
1425 #--------------------------------------------------------
1429 #--------------------------------------------------------
1432 #--------------------------------------------------------
1435 #--------------------------------------------------------
1436 # SOAP editor specific buttons
1437 #--------------------------------------------------------
1441 #--------------------------------------------------------
1445 #--------------------------------------------------------
1449 #--------------------------------------------------------
1459 #--------------------------------------------------------
1479 #--------------------------------------------------------
1483 #--------------------------------------------------------
1484 # encounter specific buttons
1485 #--------------------------------------------------------
1489 #--------------------------------------------------------
1490 # other buttons
1491 #--------------------------------------------------------
1500 #--------------------------------------------------------
1512
1513 #============================================================
1515 """A notebook holding panels with progress note editors.
1516
1517 There can be one or several progress note editor panel
1518 for each episode being worked on. The editor class in
1519 each panel is configurable.
1520
1521 There will always be one open editor.
1522 """
1524
1525 kwargs['style'] = wx.NB_TOP | wx.NB_MULTILINE | wx.NO_BORDER
1526
1527 wx.Notebook.__init__(self, *args, **kwargs)
1528 #--------------------------------------------------------
1529 # public API
1530 #--------------------------------------------------------
1532 """Add a progress note editor page.
1533
1534 The way <allow_same_problem> is currently used in callers
1535 it only applies to unassociated episodes.
1536 """
1537 problem_to_add = problem
1538
1539 # determine label
1540 if problem_to_add is None:
1541 label = _('new problem')
1542 else:
1543 # normalize problem type
1544 if isinstance(problem_to_add, gmEMRStructItems.cEpisode):
1545 problem_to_add = gmEMRStructItems.episode2problem(episode = problem_to_add, allow_closed = True)
1546
1547 elif isinstance(problem_to_add, gmEMRStructItems.cHealthIssue):
1548 problem_to_add = gmEMRStructItems.health_issue2problem(health_issue = problem_to_add, allow_irrelevant = True)
1549
1550 if not isinstance(problem_to_add, gmEMRStructItems.cProblem):
1551 raise TypeError('cannot open progress note editor for [%s]' % problem_to_add)
1552
1553 label = problem_to_add['problem']
1554 # FIXME: configure maximum length
1555 if len(label) > 23:
1556 label = label[:21] + gmTools.u_ellipsis
1557
1558 # new unassociated problem or dupes allowed
1559 if allow_same_problem:
1560 new_page = cSoapNoteExpandoEditAreaPnl(parent = self, id = -1, problem = problem_to_add)
1561 result = self.AddPage (
1562 page = new_page,
1563 text = label,
1564 select = True
1565 )
1566 return result
1567
1568 # real problem, no dupes allowed
1569 # - raise existing editor
1570 for page_idx in range(self.GetPageCount()):
1571 page = self.GetPage(page_idx)
1572
1573 if problem_to_add is None:
1574 if page.problem is None:
1575 self.SetSelection(page_idx)
1576 gmDispatcher.send(signal = u'statustext', msg = u'Raising existing editor.', beep = True)
1577 return True
1578 continue
1579
1580 # editor is for unassociated new problem
1581 if page.problem is None:
1582 continue
1583
1584 # editor is for episode
1585 if page.problem['type'] == 'episode':
1586 if page.problem['pk_episode'] == problem_to_add['pk_episode']:
1587 self.SetSelection(page_idx)
1588 gmDispatcher.send(signal = u'statustext', msg = u'Raising existing editor.', beep = True)
1589 return True
1590 continue
1591
1592 # editor is for health issue
1593 if page.problem['type'] == 'issue':
1594 if page.problem['pk_health_issue'] == problem_to_add['pk_health_issue']:
1595 self.SetSelection(page_idx)
1596 gmDispatcher.send(signal = u'statustext', msg = u'Raising existing editor.', beep = True)
1597 return True
1598 continue
1599
1600 # - or add new editor
1601 new_page = cSoapNoteExpandoEditAreaPnl(parent = self, id = -1, problem = problem_to_add)
1602 result = self.AddPage (
1603 page = new_page,
1604 text = label,
1605 select = True
1606 )
1607
1608 return result
1609 #--------------------------------------------------------
1611
1612 page_idx = self.GetSelection()
1613 page = self.GetPage(page_idx)
1614
1615 if not page.empty:
1616 really_discard = gmGuiHelpers.gm_show_question (
1617 _('Are you sure you really want to\n'
1618 'discard this progress note ?\n'
1619 ),
1620 _('Discarding progress note')
1621 )
1622 if really_discard is False:
1623 return
1624
1625 self.DeletePage(page_idx)
1626
1627 # always keep one unassociated editor open
1628 if self.GetPageCount() == 0:
1629 self.add_editor()
1630 #--------------------------------------------------------
1632
1633 page_idx = self.GetSelection()
1634 page = self.GetPage(page_idx)
1635
1636 if not page.save(emr = emr, episode_name_candidates = episode_name_candidates, encounter = encounter):
1637 return
1638
1639 self.DeletePage(page_idx)
1640
1641 # always keep one unassociated editor open
1642 if self.GetPageCount() == 0:
1643 self.add_editor()
1644 #--------------------------------------------------------
1646 for page_idx in range(self.GetPageCount()):
1647 page = self.GetPage(page_idx)
1648 if page.empty:
1649 continue
1650
1651 gmGuiHelpers.gm_show_warning (
1652 _('There are unsaved progress notes !\n'),
1653 _('Unsaved progress notes')
1654 )
1655 return False
1656
1657 return True
1658 #--------------------------------------------------------
1660
1661 _log.debug('saving editors: %s', self.GetPageCount())
1662
1663 all_closed = True
1664 for page_idx in range((self.GetPageCount() - 1), -1, -1):
1665 _log.debug('#%s of %s', page_idx, self.GetPageCount())
1666 try:
1667 self.ChangeSelection(page_idx)
1668 _log.debug('editor raised')
1669 except:
1670 _log.exception('cannot raise editor')
1671 page = self.GetPage(page_idx)
1672 if page.save(emr = emr, episode_name_candidates = episode_name_candidates):
1673 _log.debug('saved, deleting now')
1674 self.DeletePage(page_idx)
1675 else:
1676 _log.debug('not saved, not deleting')
1677 all_closed = False
1678
1679 # always keep one unassociated editor open
1680 if self.GetPageCount() == 0:
1681 self.add_editor()
1682
1683 return (all_closed is True)
1684 #--------------------------------------------------------
1689 #--------------------------------------------------------
1694 #--------------------------------------------------------
1699 #--------------------------------------------------------
1701 page_idx = self.GetSelection()
1702 page = self.GetPage(page_idx)
1703 page.add_visual_progress_note()
1704
1705 #============================================================
1706 from Gnumed.wxGladeWidgets import wxgSoapNoteExpandoEditAreaPnl
1707
1708 -class cSoapNoteExpandoEditAreaPnl(wxgSoapNoteExpandoEditAreaPnl.wxgSoapNoteExpandoEditAreaPnl):
1709 """An Edit Area like panel for entering progress notes.
1710
1711 Subjective: Codes:
1712 expando text ctrl
1713 Objective: Codes:
1714 expando text ctrl
1715 Assessment: Codes:
1716 expando text ctrl
1717 Plan: Codes:
1718 expando text ctrl
1719 visual progress notes
1720 panel with images
1721 Episode synopsis: Codes:
1722 text ctrl
1723
1724 - knows the problem this edit area is about
1725 - can deal with issue or episode type problems
1726 """
1727
1729
1730 try:
1731 self.problem = kwargs['problem']
1732 del kwargs['problem']
1733 except KeyError:
1734 self.problem = None
1735
1736 wxgSoapNoteExpandoEditAreaPnl.wxgSoapNoteExpandoEditAreaPnl.__init__(self, *args, **kwargs)
1737
1738 self.soap_fields = [
1739 self._TCTRL_Soap,
1740 self._TCTRL_sOap,
1741 self._TCTRL_soAp,
1742 self._TCTRL_soaP
1743 ]
1744
1745 self.__init_ui()
1746 self.__register_interests()
1747 #--------------------------------------------------------
1749 self.refresh_summary()
1750 if self.problem is not None:
1751 if self.problem['summary'] is None:
1752 self._TCTRL_episode_summary.SetValue(u'')
1753 self.refresh_visual_soap()
1754 #--------------------------------------------------------
1758 #--------------------------------------------------------
1760 self._TCTRL_episode_summary.SetValue(u'')
1761 self._PRW_episode_codes.SetText(u'', self._PRW_episode_codes.list2data_dict([]))
1762 self._LBL_summary.SetLabel(_('Episode synopsis'))
1763
1764 # new problem ?
1765 if self.problem is None:
1766 return
1767
1768 # issue-level problem ?
1769 if self.problem['type'] == u'issue':
1770 return
1771
1772 # episode-level problem
1773 caption = _(u'Synopsis (%s)') % (
1774 gmDateTime.pydt_strftime (
1775 self.problem['modified_when'],
1776 format = '%B %Y',
1777 accuracy = gmDateTime.acc_days
1778 )
1779 )
1780 self._LBL_summary.SetLabel(caption)
1781
1782 if self.problem['summary'] is not None:
1783 self._TCTRL_episode_summary.SetValue(self.problem['summary'].strip())
1784
1785 val, data = self._PRW_episode_codes.generic_linked_codes2item_dict(self.problem.generic_codes)
1786 self._PRW_episode_codes.SetText(val, data)
1787 #--------------------------------------------------------
1789 if self.problem is None:
1790 self._PNL_visual_soap.refresh(document_folder = None)
1791 return
1792
1793 if self.problem['type'] == u'issue':
1794 self._PNL_visual_soap.refresh(document_folder = None)
1795 return
1796
1797 if self.problem['type'] == u'episode':
1798 pat = gmPerson.gmCurrentPatient()
1799 doc_folder = pat.get_document_folder()
1800 emr = pat.get_emr()
1801 self._PNL_visual_soap.refresh (
1802 document_folder = doc_folder,
1803 episodes = [self.problem['pk_episode']],
1804 encounter = emr.active_encounter['pk_encounter']
1805 )
1806 return
1807 #--------------------------------------------------------
1809 for field in self.soap_fields:
1810 field.SetValue(u'')
1811 self._TCTRL_episode_summary.SetValue(u'')
1812 self._LBL_summary.SetLabel(_('Episode synopsis'))
1813 self._PRW_episode_codes.SetText(u'', self._PRW_episode_codes.list2data_dict([]))
1814 self._PNL_visual_soap.clear()
1815 #--------------------------------------------------------
1817 fname, discard_unmodified = select_visual_progress_note_template(parent = self)
1818 if fname is None:
1819 return False
1820
1821 if self.problem is None:
1822 issue = None
1823 episode = None
1824 elif self.problem['type'] == 'issue':
1825 issue = self.problem['pk_health_issue']
1826 episode = None
1827 else:
1828 issue = self.problem['pk_health_issue']
1829 episode = gmEMRStructItems.problem2episode(self.problem)
1830
1831 wx.CallAfter (
1832 edit_visual_progress_note,
1833 filename = fname,
1834 episode = episode,
1835 discard_unmodified = discard_unmodified,
1836 health_issue = issue
1837 )
1838 #--------------------------------------------------------
1840
1841 if self.empty:
1842 return True
1843
1844 # new episode (standalone=unassociated or new-in-issue)
1845 if (self.problem is None) or (self.problem['type'] == 'issue'):
1846 episode = self.__create_new_episode(emr = emr, episode_name_candidates = episode_name_candidates)
1847 # user cancelled
1848 if episode is None:
1849 return False
1850 # existing episode
1851 else:
1852 episode = emr.problem2episode(self.problem)
1853
1854 if encounter is None:
1855 encounter = emr.current_encounter['pk_encounter']
1856
1857 soap_notes = []
1858 for note in self.soap:
1859 saved, data = gmClinNarrative.create_clin_narrative (
1860 soap_cat = note[0],
1861 narrative = note[1],
1862 episode_id = episode['pk_episode'],
1863 encounter_id = encounter
1864 )
1865 if saved:
1866 soap_notes.append(data)
1867
1868 # codes per narrative !
1869 # for note in soap_notes:
1870 # if note['soap_cat'] == u's':
1871 # codes = self._PRW_Soap_codes
1872 # elif note['soap_cat'] == u'o':
1873 # elif note['soap_cat'] == u'a':
1874 # elif note['soap_cat'] == u'p':
1875
1876 # set summary but only if not already set above for a
1877 # newly created episode (either standalone or within
1878 # a health issue)
1879 if self.problem is not None:
1880 if self.problem['type'] == 'episode':
1881 episode['summary'] = self._TCTRL_episode_summary.GetValue().strip()
1882 episode.save()
1883
1884 # codes for episode
1885 episode.generic_codes = [ d['data'] for d in self._PRW_episode_codes.GetData() ]
1886
1887 return True
1888 #--------------------------------------------------------
1889 # internal helpers
1890 #--------------------------------------------------------
1892
1893 episode_name_candidates.append(self._TCTRL_episode_summary.GetValue().strip())
1894 for candidate in episode_name_candidates:
1895 if candidate is None:
1896 continue
1897 epi_name = candidate.strip().replace('\r', '//').replace('\n', '//')
1898 break
1899
1900 dlg = wx.TextEntryDialog (
1901 parent = self,
1902 message = _('Enter a short working name for this new problem:'),
1903 caption = _('Creating a problem (episode) to save the notelet under ...'),
1904 defaultValue = epi_name,
1905 style = wx.OK | wx.CANCEL | wx.CENTRE
1906 )
1907 decision = dlg.ShowModal()
1908 if decision != wx.ID_OK:
1909 return None
1910
1911 epi_name = dlg.GetValue().strip()
1912 if epi_name == u'':
1913 gmGuiHelpers.gm_show_error(_('Cannot save a new problem without a name.'), _('saving progress note'))
1914 return None
1915
1916 # create episode
1917 new_episode = emr.add_episode(episode_name = epi_name[:45], pk_health_issue = None, is_open = True)
1918 new_episode['summary'] = self._TCTRL_episode_summary.GetValue().strip()
1919 new_episode.save()
1920
1921 if self.problem is not None:
1922 issue = emr.problem2issue(self.problem)
1923 if not gmEMRStructWidgets.move_episode_to_issue(episode = new_episode, target_issue = issue, save_to_backend = True):
1924 gmGuiHelpers.gm_show_warning (
1925 _(
1926 'The new episode:\n'
1927 '\n'
1928 ' "%s"\n'
1929 '\n'
1930 'will remain unassociated despite the editor\n'
1931 'having been invoked from the health issue:\n'
1932 '\n'
1933 ' "%s"'
1934 ) % (
1935 new_episode['description'],
1936 issue['description']
1937 ),
1938 _('saving progress note')
1939 )
1940
1941 return new_episode
1942 #--------------------------------------------------------
1943 # event handling
1944 #--------------------------------------------------------
1946 for field in self.soap_fields:
1947 wx_expando.EVT_ETC_LAYOUT_NEEDED(field, field.GetId(), self._on_expando_needs_layout)
1948 wx_expando.EVT_ETC_LAYOUT_NEEDED(self._TCTRL_episode_summary, self._TCTRL_episode_summary.GetId(), self._on_expando_needs_layout)
1949 gmDispatcher.connect(signal = u'doc_page_mod_db', receiver = self._refresh_visual_soap)
1950 #--------------------------------------------------------
1952 wx.CallAfter(self.refresh_visual_soap)
1953 #--------------------------------------------------------
1955 # need to tell ourselves to re-Layout to refresh scroll bars
1956
1957 # provoke adding scrollbar if needed
1958 #self.Fit() # works on Linux but not on Windows
1959 self.FitInside() # needed on Windows rather than self.Fit()
1960
1961 if self.HasScrollbar(wx.VERTICAL):
1962 # scroll panel to show cursor
1963 expando = self.FindWindowById(evt.GetId())
1964 y_expando = expando.GetPositionTuple()[1]
1965 h_expando = expando.GetSizeTuple()[1]
1966 line_cursor = expando.PositionToXY(expando.GetInsertionPoint())[1] + 1
1967 if expando.NumberOfLines == 0:
1968 no_of_lines = 1
1969 else:
1970 no_of_lines = expando.NumberOfLines
1971 y_cursor = int(round((float(line_cursor) / no_of_lines) * h_expando))
1972 y_desired_visible = y_expando + y_cursor
1973
1974 y_view = self.ViewStart[1]
1975 h_view = self.GetClientSizeTuple()[1]
1976
1977 # print "expando:", y_expando, "->", h_expando, ", lines:", expando.NumberOfLines
1978 # print "cursor :", y_cursor, "at line", line_cursor, ", insertion point:", expando.GetInsertionPoint()
1979 # print "wanted :", y_desired_visible
1980 # print "view-y :", y_view
1981 # print "scroll2:", h_view
1982
1983 # expando starts before view
1984 if y_desired_visible < y_view:
1985 # print "need to scroll up"
1986 self.Scroll(0, y_desired_visible)
1987
1988 if y_desired_visible > h_view:
1989 # print "need to scroll down"
1990 self.Scroll(0, y_desired_visible)
1991 #--------------------------------------------------------
1992 # properties
1993 #--------------------------------------------------------
1995 soap_notes = []
1996
1997 tmp = self._TCTRL_Soap.GetValue().strip()
1998 if tmp != u'':
1999 soap_notes.append(['s', tmp])
2000
2001 tmp = self._TCTRL_sOap.GetValue().strip()
2002 if tmp != u'':
2003 soap_notes.append(['o', tmp])
2004
2005 tmp = self._TCTRL_soAp.GetValue().strip()
2006 if tmp != u'':
2007 soap_notes.append(['a', tmp])
2008
2009 tmp = self._TCTRL_soaP.GetValue().strip()
2010 if tmp != u'':
2011 soap_notes.append(['p', tmp])
2012
2013 return soap_notes
2014
2015 soap = property(_get_soap, lambda x:x)
2016 #--------------------------------------------------------
2018
2019 # soap fields
2020 for field in self.soap_fields:
2021 if field.GetValue().strip() != u'':
2022 return False
2023
2024 # summary
2025 summary = self._TCTRL_episode_summary.GetValue().strip()
2026 if self.problem is None:
2027 if summary != u'':
2028 return False
2029 elif self.problem['type'] == u'issue':
2030 if summary != u'':
2031 return False
2032 else:
2033 if self.problem['summary'] is None:
2034 if summary != u'':
2035 return False
2036 else:
2037 if summary != self.problem['summary'].strip():
2038 return False
2039
2040 # codes
2041 new_codes = self._PRW_episode_codes.GetData()
2042 if self.problem is None:
2043 if len(new_codes) > 0:
2044 return False
2045 elif self.problem['type'] == u'issue':
2046 if len(new_codes) > 0:
2047 return False
2048 else:
2049 old_code_pks = self.problem.generic_codes
2050 if len(old_code_pks) != len(new_codes):
2051 return False
2052 for code in new_codes:
2053 if code['data'] not in old_code_pks:
2054 return False
2055
2056 return True
2057
2058 empty = property(_get_empty, lambda x:x)
2059 #============================================================
2060 -class cSoapLineTextCtrl(wx_expando.ExpandoTextCtrl, gmKeywordExpansionWidgets.cKeywordExpansion_TextCtrlMixin):
2061
2063
2064 wx_expando.ExpandoTextCtrl.__init__(self, *args, **kwargs)
2065 gmKeywordExpansionWidgets.cKeywordExpansion_TextCtrlMixin.__init__(self)
2066 self.enable_keyword_expansions()
2067
2068 self.__register_interests()
2069 #------------------------------------------------
2070 # monkeypatch platform expando.py
2071 #------------------------------------------------
2073
2074 if (wx.MAJOR_VERSION >= 2) and (wx.MINOR_VERSION > 8):
2075 return wx_expando.ExpandoTextCtrl._wrapLine(line, dc, width)
2076
2077 # THIS FIX LIFTED FROM TRUNK IN SVN:
2078 # Estimate where the control will wrap the lines and
2079 # return the count of extra lines needed.
2080 pte = dc.GetPartialTextExtents(line)
2081 width -= wx.SystemSettings.GetMetric(wx.SYS_VSCROLL_X)
2082 idx = 0
2083 start = 0
2084 count = 0
2085 spc = -1
2086 while idx < len(pte):
2087 if line[idx] == ' ':
2088 spc = idx
2089 if pte[idx] - start > width:
2090 # we've reached the max width, add a new line
2091 count += 1
2092 # did we see a space? if so restart the count at that pos
2093 if spc != -1:
2094 idx = spc + 1
2095 spc = -1
2096 if idx < len(pte):
2097 start = pte[idx]
2098 else:
2099 idx += 1
2100 return count
2101 #------------------------------------------------
2102 # event handling
2103 #------------------------------------------------
2105 #wx.EVT_KEY_DOWN (self, self.__on_key_down)
2106 #wx.EVT_KEY_UP (self, self.__OnKeyUp)
2107 wx.EVT_SET_FOCUS(self, self.__on_focus)
2108 #--------------------------------------------------------
2112 #--------------------------------------------------------
2114 # robustify against PyDeadObjectError - since we are called
2115 # from wx.CallAfter this SoapCtrl may be gone by the time
2116 # we get to handling this layout request, say, on patient
2117 # change or some such
2118 if not self:
2119 return
2120 #wx.CallAfter(self._adjustCtrl)
2121 evt = wx.PyCommandEvent(wx_expando.wxEVT_ETC_LAYOUT_NEEDED, self.GetId())
2122 evt.SetEventObject(self)
2123 #evt.height = None
2124 #evt.numLines = None
2125 #evt.height = self.GetSize().height
2126 #evt.numLines = self.GetNumberOfLines()
2127 self.GetEventHandler().ProcessEvent(evt)
2128
2129 #============================================================
2130 # visual progress notes
2131 #============================================================
2133
2134 def is_valid(value):
2135
2136 if value is None:
2137 gmDispatcher.send (
2138 signal = 'statustext',
2139 msg = _('You need to actually set an editor.'),
2140 beep = True
2141 )
2142 return False, value
2143
2144 if value.strip() == u'':
2145 gmDispatcher.send (
2146 signal = 'statustext',
2147 msg = _('You need to actually set an editor.'),
2148 beep = True
2149 )
2150 return False, value
2151
2152 found, binary = gmShellAPI.detect_external_binary(value)
2153 if not found:
2154 gmDispatcher.send (
2155 signal = 'statustext',
2156 msg = _('The command [%s] is not found.') % value,
2157 beep = True
2158 )
2159 return True, value
2160
2161 return True, binary
2162 #------------------------------------------
2163 cmd = gmCfgWidgets.configure_string_option (
2164 message = _(
2165 'Enter the shell command with which to start\n'
2166 'the image editor for visual progress notes.\n'
2167 '\n'
2168 'Any "%(img)s" included with the arguments\n'
2169 'will be replaced by the file name of the\n'
2170 'note template.'
2171 ),
2172 option = u'external.tools.visual_soap_editor_cmd',
2173 bias = 'user',
2174 default_value = None,
2175 validator = is_valid
2176 )
2177
2178 return cmd
2179 #============================================================
2181 if parent is None:
2182 parent = wx.GetApp().GetTopWindow()
2183
2184 dlg = wx.FileDialog (
2185 parent = parent,
2186 message = _('Choose file to use as template for new visual progress note'),
2187 defaultDir = os.path.expanduser('~'),
2188 defaultFile = '',
2189 #wildcard = "%s (*)|*|%s (*.*)|*.*" % (_('all files'), _('all files (Win)')),
2190 style = wx.OPEN | wx.HIDE_READONLY | wx.FILE_MUST_EXIST
2191 )
2192 result = dlg.ShowModal()
2193
2194 if result == wx.ID_CANCEL:
2195 dlg.Destroy()
2196 return None
2197
2198 full_filename = dlg.GetPath()
2199 dlg.Hide()
2200 dlg.Destroy()
2201 return full_filename
2202 #------------------------------------------------------------
2204
2205 if parent is None:
2206 parent = wx.GetApp().GetTopWindow()
2207
2208 dlg = gmGuiHelpers.c3ButtonQuestionDlg (
2209 parent,
2210 -1,
2211 caption = _('Visual progress note source'),
2212 question = _('From which source do you want to pick the image template ?'),
2213 button_defs = [
2214 {'label': _('Database'), 'tooltip': _('List of templates in the database.'), 'default': True},
2215 {'label': _('File'), 'tooltip': _('Files in the filesystem.'), 'default': False},
2216 {'label': _('Device'), 'tooltip': _('Image capture devices (scanners, cameras, etc)'), 'default': False}
2217 ]
2218 )
2219 result = dlg.ShowModal()
2220 dlg.Destroy()
2221
2222 # 1) select from template
2223 if result == wx.ID_YES:
2224 _log.debug('visual progress note template from: database template')
2225 from Gnumed.wxpython import gmFormWidgets
2226 template = gmFormWidgets.manage_form_templates (
2227 parent = parent,
2228 template_types = [gmDocuments.DOCUMENT_TYPE_VISUAL_PROGRESS_NOTE],
2229 active_only = True
2230 )
2231 if template is None:
2232 return (None, None)
2233 filename = template.export_to_file()
2234 if filename is None:
2235 gmDispatcher.send(signal = u'statustext', msg = _('Cannot export visual progress note template for [%s].') % template['name_long'])
2236 return (None, None)
2237 return (filename, True)
2238
2239 # 2) select from disk file
2240 if result == wx.ID_NO:
2241 _log.debug('visual progress note template from: disk file')
2242 fname = select_file_as_visual_progress_note_template(parent = parent)
2243 if fname is None:
2244 return (None, None)
2245 # create a copy of the picked file -- don't modify the original
2246 ext = os.path.splitext(fname)[1]
2247 tmp_name = gmTools.get_unique_filename(suffix = ext)
2248 _log.debug('visual progress note from file: [%s] -> [%s]', fname, tmp_name)
2249 shutil.copy2(fname, tmp_name)
2250 return (tmp_name, False)
2251
2252 # 3) acquire from capture device
2253 if result == wx.ID_CANCEL:
2254 _log.debug('visual progress note template from: image capture device')
2255 fnames = gmDocumentWidgets.acquire_images_from_capture_device(device = None, calling_window = parent)
2256 if fnames is None:
2257 return (None, None)
2258 if len(fnames) == 0:
2259 return (None, None)
2260 return (fnames[0], False)
2261
2262 _log.debug('no visual progress note template source selected')
2263 return (None, None)
2264 #------------------------------------------------------------
2265 -def edit_visual_progress_note(filename=None, episode=None, discard_unmodified=False, doc_part=None, health_issue=None):
2266 """This assumes <filename> contains an image which can be handled by the configured image editor."""
2267
2268 if doc_part is not None:
2269 filename = doc_part.export_to_file()
2270 if filename is None:
2271 gmDispatcher.send(signal = u'statustext', msg = _('Cannot export visual progress note to file.'))
2272 return None
2273
2274 dbcfg = gmCfg.cCfgSQL()
2275 cmd = dbcfg.get2 (
2276 option = u'external.tools.visual_soap_editor_cmd',
2277 workplace = gmSurgery.gmCurrentPractice().active_workplace,
2278 bias = 'user'
2279 )
2280
2281 if cmd is None:
2282 gmDispatcher.send(signal = u'statustext', msg = _('Editor for visual progress note not configured.'), beep = False)
2283 cmd = configure_visual_progress_note_editor()
2284 if cmd is None:
2285 gmDispatcher.send(signal = u'statustext', msg = _('Editor for visual progress note not configured.'), beep = True)
2286 return None
2287
2288 if u'%(img)s' in cmd:
2289 cmd = cmd % {u'img': filename}
2290 else:
2291 cmd = u'%s %s' % (cmd, filename)
2292
2293 if discard_unmodified:
2294 original_stat = os.stat(filename)
2295 original_md5 = gmTools.file2md5(filename)
2296
2297 success = gmShellAPI.run_command_in_shell(cmd, blocking = True)
2298 if not success:
2299 gmGuiHelpers.gm_show_error (
2300 _(
2301 'There was a problem with running the editor\n'
2302 'for visual progress notes.\n'
2303 '\n'
2304 ' [%s]\n'
2305 '\n'
2306 ) % cmd,
2307 _('Editing visual progress note')
2308 )
2309 return None
2310
2311 try:
2312 open(filename, 'r').close()
2313 except StandardError:
2314 _log.exception('problem accessing visual progress note file [%s]', filename)
2315 gmGuiHelpers.gm_show_error (
2316 _(
2317 'There was a problem reading the visual\n'
2318 'progress note from the file:\n'
2319 '\n'
2320 ' [%s]\n'
2321 '\n'
2322 ) % filename,
2323 _('Saving visual progress note')
2324 )
2325 return None
2326
2327 if discard_unmodified:
2328 modified_stat = os.stat(filename)
2329 # same size ?
2330 if original_stat.st_size == modified_stat.st_size:
2331 modified_md5 = gmTools.file2md5(filename)
2332 # same hash ?
2333 if original_md5 == modified_md5:
2334 _log.debug('visual progress note (template) not modified')
2335 # ask user to decide
2336 msg = _(
2337 u'You either created a visual progress note from a template\n'
2338 u'in the database (rather than from a file on disk) or you\n'
2339 u'edited an existing visual progress note.\n'
2340 u'\n'
2341 u'The template/original was not modified at all, however.\n'
2342 u'\n'
2343 u'Do you still want to save the unmodified image as a\n'
2344 u'visual progress note into the EMR of the patient ?\n'
2345 )
2346 save_unmodified = gmGuiHelpers.gm_show_question (
2347 msg,
2348 _('Saving visual progress note')
2349 )
2350 if not save_unmodified:
2351 _log.debug('user discarded unmodified note')
2352 return
2353
2354 if doc_part is not None:
2355 _log.debug('updating visual progress note')
2356 doc_part.update_data_from_file(fname = filename)
2357 doc_part.set_reviewed(technically_abnormal = False, clinically_relevant = True)
2358 return None
2359
2360 if not isinstance(episode, gmEMRStructItems.cEpisode):
2361 if episode is None:
2362 episode = _('visual progress notes')
2363 pat = gmPerson.gmCurrentPatient()
2364 emr = pat.get_emr()
2365 episode = emr.add_episode(episode_name = episode.strip(), pk_health_issue = health_issue, is_open = False)
2366
2367 doc = gmDocumentWidgets.save_file_as_new_document (
2368 filename = filename,
2369 document_type = gmDocuments.DOCUMENT_TYPE_VISUAL_PROGRESS_NOTE,
2370 episode = episode,
2371 unlock_patient = False
2372 )
2373 doc.set_reviewed(technically_abnormal = False, clinically_relevant = True)
2374
2375 return doc
2376 #============================================================
2378 """Phrasewheel to allow selection of visual SOAP template."""
2379
2381
2382 gmPhraseWheel.cPhraseWheel.__init__ (self, *args, **kwargs)
2383
2384 query = u"""
2385 SELECT
2386 pk AS data,
2387 name_short AS list_label,
2388 name_sort AS field_label
2389 FROM
2390 ref.paperwork_templates
2391 WHERE
2392 fk_template_type = (SELECT pk FROM ref.form_types WHERE name = '%s') AND (
2393 name_long %%(fragment_condition)s
2394 OR
2395 name_short %%(fragment_condition)s
2396 )
2397 ORDER BY list_label
2398 LIMIT 15
2399 """ % gmDocuments.DOCUMENT_TYPE_VISUAL_PROGRESS_NOTE
2400
2401 mp = gmMatchProvider.cMatchProvider_SQL2(queries = [query])
2402 mp.setThresholds(2, 3, 5)
2403
2404 self.matcher = mp
2405 self.selection_only = True
2406 #--------------------------------------------------------
2408 if self.GetData() is None:
2409 return None
2410
2411 return gmForms.cFormTemplate(aPK_obj = self.GetData())
2412 #============================================================
2413 from Gnumed.wxGladeWidgets import wxgVisualSoapPresenterPnl
2414
2416
2418 wxgVisualSoapPresenterPnl.wxgVisualSoapPresenterPnl.__init__(self, *args, **kwargs)
2419 self._SZR_soap = self.GetSizer()
2420 self.__bitmaps = []
2421 #--------------------------------------------------------
2422 # external API
2423 #--------------------------------------------------------
2425
2426 self.clear()
2427 if document_folder is not None:
2428 soap_docs = document_folder.get_visual_progress_notes(episodes = episodes, encounter = encounter)
2429 if len(soap_docs) > 0:
2430 for soap_doc in soap_docs:
2431 parts = soap_doc.parts
2432 if len(parts) == 0:
2433 continue
2434 part = parts[0]
2435 fname = part.export_to_file()
2436 if fname is None:
2437 continue
2438
2439 # create bitmap
2440 img = gmGuiHelpers.file2scaled_image (
2441 filename = fname,
2442 height = 30
2443 )
2444 #bmp = wx.StaticBitmap(self, -1, img, style = wx.NO_BORDER)
2445 bmp = wx_genstatbmp.GenStaticBitmap(self, -1, img, style = wx.NO_BORDER)
2446
2447 # create tooltip
2448 img = gmGuiHelpers.file2scaled_image (
2449 filename = fname,
2450 height = 150
2451 )
2452 tip = agw_stt.SuperToolTip (
2453 u'',
2454 bodyImage = img,
2455 header = _('Created: %s') % part['date_generated'].strftime('%Y %B %d').decode(gmI18N.get_encoding()),
2456 footer = gmTools.coalesce(part['doc_comment'], u'').strip()
2457 )
2458 tip.SetTopGradientColor('white')
2459 tip.SetMiddleGradientColor('white')
2460 tip.SetBottomGradientColor('white')
2461 tip.SetTarget(bmp)
2462
2463 bmp.doc_part = part
2464 bmp.Bind(wx.EVT_LEFT_UP, self._on_bitmap_leftclicked)
2465 # FIXME: add context menu for Delete/Clone/Add/Configure
2466 self._SZR_soap.Add(bmp, 0, wx.LEFT | wx.RIGHT | wx.TOP | wx.BOTTOM | wx.EXPAND, 3)
2467 self.__bitmaps.append(bmp)
2468
2469 self.GetParent().Layout()
2470 #--------------------------------------------------------
2472 while len(self._SZR_soap.GetChildren()) > 0:
2473 self._SZR_soap.Detach(0)
2474 # for child_idx in range(len(self._SZR_soap.GetChildren())):
2475 # self._SZR_soap.Detach(child_idx)
2476 for bmp in self.__bitmaps:
2477 bmp.Destroy()
2478 self.__bitmaps = []
2479 #--------------------------------------------------------
2481 wx.CallAfter (
2482 edit_visual_progress_note,
2483 doc_part = evt.GetEventObject().doc_part,
2484 discard_unmodified = True
2485 )
2486
2487 #============================================================
2488 #============================================================
2489 from Gnumed.wxGladeWidgets import wxgSimpleSoapPluginPnl
2490
2491 -class cSimpleSoapPluginPnl(wxgSimpleSoapPluginPnl.wxgSimpleSoapPluginPnl, gmRegetMixin.cRegetOnPaintMixin):
2493
2494 wxgSimpleSoapPluginPnl.wxgSimpleSoapPluginPnl.__init__(self, *args, **kwargs)
2495 gmRegetMixin.cRegetOnPaintMixin.__init__(self)
2496
2497 self.__curr_pat = gmPerson.gmCurrentPatient()
2498 self.__problem = None
2499 self.__init_ui()
2500 self.__register_interests()
2501 #-----------------------------------------------------
2502 # internal API
2503 #-----------------------------------------------------
2505 self._LCTRL_problems.set_columns(columns = [_('Problem list')])
2506 self._LCTRL_problems.activate_callback = self._on_problem_activated
2507 self._LCTRL_problems.item_tooltip_callback = self._on_get_problem_tooltip
2508
2509 self._splitter_main.SetSashGravity(0.5)
2510 splitter_width = self._splitter_main.GetSizeTuple()[0]
2511 self._splitter_main.SetSashPosition(splitter_width / 2, True)
2512
2513 self._TCTRL_soap.Disable()
2514 self._BTN_save_soap.Disable()
2515 self._BTN_clear_soap.Disable()
2516 #-----------------------------------------------------
2518 self._LCTRL_problems.set_string_items()
2519 self._TCTRL_soap_problem.SetValue(_('<above, double-click problem to start entering SOAP note>'))
2520 self._TCTRL_soap.SetValue(u'')
2521 self._CHBOX_filter_by_problem.SetLabel(_('&Filter by problem'))
2522 self._TCTRL_journal.SetValue(u'')
2523
2524 self._TCTRL_soap.Disable()
2525 self._BTN_save_soap.Disable()
2526 self._BTN_clear_soap.Disable()
2527 #-----------------------------------------------------
2529 if not self.__curr_pat.connected:
2530 return None
2531
2532 if self.__problem is None:
2533 return None
2534
2535 saved = self.__curr_pat.emr.add_clin_narrative (
2536 note = self._TCTRL_soap.GetValue().strip(),
2537 soap_cat = u'u',
2538 episode = self.__problem
2539 )
2540
2541 if saved is None:
2542 return False
2543
2544 self._TCTRL_soap.SetValue(u'')
2545 self.__refresh_journal()
2546 return True
2547 #-----------------------------------------------------
2549 if self._TCTRL_soap.GetValue().strip() == u'':
2550 return True
2551 if self.__problem is None:
2552 # FIXME: this could potentially lose input
2553 self._TCTRL_soap.SetValue(u'')
2554 return None
2555 save_it = gmGuiHelpers.gm_show_question (
2556 title = _('Saving SOAP note'),
2557 question = _('Do you want to save the SOAP note ?')
2558 )
2559 if save_it:
2560 return self.__save_soap()
2561 return False
2562 #-----------------------------------------------------
2564 self._LCTRL_problems.set_string_items()
2565 emr = self.__curr_pat.get_emr()
2566 epis = emr.get_episodes(open_status = True)
2567 if len(epis) > 0:
2568 self._LCTRL_problems.set_string_items(items = [ u'%s%s' % (
2569 e['description'],
2570 gmTools.coalesce(e['health_issue'], u'', u' (%s)')
2571 ) for e in epis ])
2572 self._LCTRL_problems.set_data(epis)
2573 #-----------------------------------------------------
2575 self._TCTRL_journal.SetValue(u'')
2576 epi = self._LCTRL_problems.get_selected_item_data(only_one = True)
2577
2578 if epi is not None:
2579 self._CHBOX_filter_by_problem.SetLabel(_('&Filter by problem %s%s%s') % (
2580 gmTools.u_left_double_angle_quote,
2581 epi['description'],
2582 gmTools.u_right_double_angle_quote
2583 ))
2584 self._CHBOX_filter_by_problem.Refresh()
2585
2586 if not self._CHBOX_filter_by_problem.IsChecked():
2587 self._TCTRL_journal.SetValue(self.__curr_pat.emr.format_summary(dob = self.__curr_pat['dob']))
2588 return
2589
2590 if epi is None:
2591 return
2592
2593 self._TCTRL_journal.SetValue(epi.format_as_journal())
2594 #-----------------------------------------------------
2595 # event handling
2596 #-----------------------------------------------------
2598 """Configure enabled event signals."""
2599 # client internal signals
2600 gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._on_pre_patient_selection)
2601 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
2602 gmDispatcher.connect(signal = u'episode_mod_db', receiver = self._on_episode_issue_mod_db)
2603 gmDispatcher.connect(signal = u'health_issue_mod_db', receiver = self._on_episode_issue_mod_db)
2604
2605 # synchronous signals
2606 self.__curr_pat.register_pre_selection_callback(callback = self._pre_selection_callback)
2607 gmDispatcher.send(signal = u'register_pre_exit_callback', callback = self._pre_exit_callback)
2608 #-----------------------------------------------------
2610 """Another patient is about to be activated.
2611
2612 Patient change will not proceed before this returns True.
2613 """
2614 if not self.__curr_pat.connected:
2615 return True
2616 self.__perhaps_save_soap()
2617 self.__problem = None
2618 return True
2619 #-----------------------------------------------------
2621 """The client is about to be shut down.
2622
2623 Shutdown will not proceed before this returns.
2624 """
2625 if not self.__curr_pat.connected:
2626 return
2627 if not self.__save_soap():
2628 gmDispatcher.send(signal = 'statustext', msg = _('Cannot save SimpleNotes SOAP note.'), beep = True)
2629 return
2630 #-----------------------------------------------------
2633 #-----------------------------------------------------
2636 #-----------------------------------------------------
2639 #-----------------------------------------------------
2641 self.__perhaps_save_soap()
2642 epi = self._LCTRL_problems.get_selected_item_data(only_one = True)
2643 self._TCTRL_soap_problem.SetValue(_('Progress note: %s%s') % (
2644 epi['description'],
2645 gmTools.coalesce(epi['health_issue'], u'', u' (%s)')
2646 ))
2647 self.__problem = epi
2648 self._TCTRL_soap.SetValue(u'')
2649
2650 self._TCTRL_soap.Enable()
2651 self._BTN_save_soap.Enable()
2652 self._BTN_clear_soap.Enable()
2653 #-----------------------------------------------------
2655 return episode.format (
2656 patient = self.__curr_pat,
2657 with_summary = False,
2658 with_codes = True,
2659 with_encounters = False,
2660 with_documents = False,
2661 with_hospital_stays = False,
2662 with_procedures = False,
2663 with_family_history = False,
2664 with_tests = False,
2665 with_vaccinations = False,
2666 with_health_issue = True
2667 )
2668 #-----------------------------------------------------
2672 #-----------------------------------------------------
2676 #-----------------------------------------------------
2691 #-----------------------------------------------------
2698 #-----------------------------------------------------
2706 #-----------------------------------------------------
2710 #-----------------------------------------------------
2714 #-----------------------------------------------------
2715 # reget-on-paint mixin API
2716 #-----------------------------------------------------
2718 self.__refresh_problem_list()
2719 self.__refresh_journal()
2720 self._TCTRL_soap.SetValue(u'')
2721 return True
2722
2723 #============================================================
2724 # main
2725 #------------------------------------------------------------
2726 if __name__ == '__main__':
2727
2728 if len(sys.argv) < 2:
2729 sys.exit()
2730
2731 if sys.argv[1] != 'test':
2732 sys.exit()
2733
2734 gmI18N.activate_locale()
2735 gmI18N.install_domain(domain = 'gnumed')
2736
2737 #----------------------------------------
2739 pat = gmPersonSearch.ask_for_patient()
2740 gmPatSearchWidgets.set_active_patient(patient = pat)
2741 app = wx.PyWidgetTester(size = (200, 200))
2742 sels = select_narrative_from_episodes_new()
2743 print "selected:"
2744 for sel in sels:
2745 print sel
2746 #----------------------------------------
2748 pat = gmPersonSearch.ask_for_patient()
2749 application = wx.PyWidgetTester(size=(800,500))
2750 soap_input = cSoapNoteExpandoEditAreaPnl(application.frame, -1)
2751 application.frame.Show(True)
2752 application.MainLoop()
2753 #----------------------------------------
2755 patient = gmPersonSearch.ask_for_patient()
2756 if patient is None:
2757 print "No patient. Exiting gracefully..."
2758 return
2759 gmPatSearchWidgets.set_active_patient(patient=patient)
2760
2761 application = wx.PyWidgetTester(size=(800,500))
2762 soap_input = cSoapPluginPnl(application.frame, -1)
2763 application.frame.Show(True)
2764 soap_input._schedule_data_reget()
2765 application.MainLoop()
2766 #----------------------------------------
2767 test_select_narrative_from_episodes()
2768 #test_cSoapNoteExpandoEditAreaPnl()
2769 #test_cSoapPluginPnl()
2770
2771 #============================================================
2772
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Fri Feb 1 03:57:03 2013 | http://epydoc.sourceforge.net |