| Home | Trees | Indices | Help |
|
|---|
|
|
1 """GNUmed EMR structure editors
2
3 This module contains widgets to create and edit EMR structural
4 elements (issues, enconters, episodes).
5
6 This is based on initial work and ideas by Syan <kittylitter@swiftdsl.com.au>
7 and Karsten <Karsten.Hilbert@gmx.net>.
8 """
9 #================================================================
10 __author__ = "cfmoro1976@yahoo.es, karsten.hilbert@gmx.net"
11 __license__ = "GPL v2 or later"
12
13 # stdlib
14 import sys
15 import time
16 import logging
17 import datetime as pydt
18
19
20 # 3rd party
21 import wx
22
23
24 # GNUmed
25 if __name__ == '__main__':
26 sys.path.insert(0, '../../')
27
28 from Gnumed.pycommon import gmI18N
29 from Gnumed.pycommon import gmDateTime
30
31 if __name__ == '__main__':
32 gmI18N.activate_locale()
33 gmI18N.install_domain()
34 gmDateTime.init()
35
36 from Gnumed.pycommon import gmExceptions
37 from Gnumed.pycommon import gmCfg
38 from Gnumed.pycommon import gmTools
39 from Gnumed.pycommon import gmDispatcher
40 from Gnumed.pycommon import gmMatchProvider
41
42 from Gnumed.business import gmEMRStructItems
43 from Gnumed.business import gmPraxis
44 from Gnumed.business import gmPerson
45
46 from Gnumed.wxpython import gmPhraseWheel
47 from Gnumed.wxpython import gmGuiHelpers
48 from Gnumed.wxpython import gmListWidgets
49 from Gnumed.wxpython import gmEditArea
50
51
52 _log = logging.getLogger('gm.ui')
53
54 #================================================================
55 # EMR access helper functions
56 #----------------------------------------------------------------
58 """Spin time in seconds but let wx go on."""
59 if time2spin == 0:
60 return
61 sleep_time = 0.1 # 100ms
62 total_rounds = int(time2spin / sleep_time)
63 if total_rounds < 1:
64 wx.Yield()
65 time.sleep(sleep_time)
66 return
67 rounds = 0
68 while rounds < total_rounds:
69 wx.Yield()
70 time.sleep(sleep_time)
71 rounds += 1
72
73 #================================================================
74 # episode related widgets/functions
75 #----------------------------------------------------------------
77 ea = cEpisodeEditAreaPnl(parent, -1)
78 ea.data = episode
79 ea.mode = gmTools.coalesce(episode, 'new', 'edit')
80 dlg = gmEditArea.cGenericEditAreaDlg2(parent, -1, edit_area = ea, single_entry = single_entry)
81 dlg.SetTitle(gmTools.coalesce(episode, _('Adding a new episode'), _('Editing an episode')))
82 if dlg.ShowModal() == wx.ID_OK:
83 return True
84 return False
85
86 #----------------------------------------------------------------
88
89 pat = gmPerson.gmCurrentPatient()
90 emr = pat.emr
91
92 if parent is None:
93 parent = wx.GetApp().GetTopWindow()
94 #-----------------------------------------
95 def edit(episode=None):
96 return edit_episode(parent = parent, episode = episode)
97 #-----------------------------------------
98 def delete(episode=None):
99 if gmEMRStructItems.delete_episode(episode = episode):
100 return True
101 gmDispatcher.send (
102 signal = 'statustext',
103 msg = _('Cannot delete episode.'),
104 beep = True
105 )
106 return False
107 #-----------------------------------------
108 def manage_issues(episode=None):
109 return select_health_issues(parent = None, emr = emr)
110 #-----------------------------------------
111 def get_tooltip(data):
112 if data is None:
113 return None
114 return data.format (
115 patient = pat,
116 with_summary = True,
117 with_codes = True,
118 with_encounters = False,
119 with_documents = False,
120 with_hospital_stays = False,
121 with_procedures = False,
122 with_family_history = False,
123 with_tests = False,
124 with_vaccinations = False,
125 with_health_issue = True
126 )
127 #-----------------------------------------
128 def refresh(lctrl):
129 epis = emr.get_episodes(order_by = 'description')
130 items = [
131 [ e['description'],
132 gmTools.bool2subst(e['episode_open'], _('ongoing'), _('closed'), '<unknown>'),
133 gmDateTime.pydt_strftime(e.best_guess_clinical_start_date, '%Y %b %d'),
134 gmTools.coalesce(e['health_issue'], '')
135 ] for e in epis
136 ]
137 lctrl.set_string_items(items = items)
138 lctrl.set_data(data = epis)
139 #-----------------------------------------
140 gmListWidgets.get_choices_from_list (
141 parent = parent,
142 msg = _('\nSelect the episode you want to edit !\n'),
143 caption = _('Editing episodes ...'),
144 columns = [_('Episode'), _('Status'), _('Started'), _('Health issue')],
145 single_selection = True,
146 edit_callback = edit,
147 new_callback = edit,
148 delete_callback = delete,
149 refresh_callback = refresh,
150 list_tooltip_callback = get_tooltip,
151 left_extra_button = (_('Manage issues'), _('Manage health issues'), manage_issues)
152 )
153
154 #----------------------------------------------------------------
156
157 created_new_issue = False
158
159 try:
160 issue = gmEMRStructItems.cHealthIssue(name = episode['description'], patient = episode['pk_patient'])
161 except gmExceptions.NoSuchBusinessObjectError:
162 issue = None
163
164 if issue is None:
165 issue = emr.add_health_issue(issue_name = episode['description'])
166 created_new_issue = True
167 else:
168 # issue exists already, so ask user
169 dlg = gmGuiHelpers.c3ButtonQuestionDlg (
170 parent,
171 -1,
172 caption = _('Promoting episode to health issue'),
173 question = _(
174 'There already is a health issue\n'
175 '\n'
176 ' %s\n'
177 '\n'
178 'What do you want to do ?'
179 ) % issue['description'],
180 button_defs = [
181 {'label': _('Use existing'), 'tooltip': _('Move episode into existing health issue'), 'default': False},
182 {'label': _('Create new'), 'tooltip': _('Create a new health issue with another name'), 'default': True}
183 ]
184 )
185 use_existing = dlg.ShowModal()
186 dlg.DestroyLater()
187
188 if use_existing == wx.ID_CANCEL:
189 return
190
191 # user wants to create new issue with alternate name
192 if use_existing == wx.ID_NO:
193 # loop until name modified but non-empty or cancelled
194 issue_name = episode['description']
195 while issue_name == episode['description']:
196 dlg = wx.TextEntryDialog (
197 parent,
198 _('Enter a short descriptive name for the new health issue:'),
199 caption = _('Creating a new health issue ...'),
200 value = issue_name,
201 style = wx.OK | wx.CANCEL | wx.CENTRE
202 )
203 decision = dlg.ShowModal()
204 if decision != wx.ID_OK:
205 dlg.DestroyLater()
206 return
207 issue_name = dlg.GetValue().strip()
208 dlg.DestroyLater()
209 if issue_name == '':
210 issue_name = episode['description']
211
212 issue = emr.add_health_issue(issue_name = issue_name)
213 created_new_issue = True
214
215 # eventually move the episode to the issue
216 if not move_episode_to_issue(episode = episode, target_issue = issue, save_to_backend = True):
217 # user cancelled the move so delete just-created issue
218 if created_new_issue:
219 # shouldn't fail as it is completely new
220 gmEMRStructItems.delete_health_issue(health_issue = issue)
221 return
222
223 return
224
225 #----------------------------------------------------------------
227 """Prepare changing health issue for an episode.
228
229 Checks for two-open-episodes conflict. When this
230 function succeeds, the pk_health_issue has been set
231 on the episode instance and the episode should - for
232 all practical purposes - be ready for save_payload().
233 """
234 # episode is closed: should always work
235 if not episode['episode_open']:
236 episode['pk_health_issue'] = target_issue['pk_health_issue']
237 if save_to_backend:
238 episode.save_payload()
239 return True
240
241 # un-associate: should always work, too
242 if target_issue is None:
243 episode['pk_health_issue'] = None
244 if save_to_backend:
245 episode.save_payload()
246 return True
247
248 # try closing possibly expired episode on target issue if any
249 db_cfg = gmCfg.cCfgSQL()
250 epi_ttl = int(db_cfg.get2 (
251 option = 'episode.ttl',
252 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
253 bias = 'user',
254 default = 60 # 2 months
255 ))
256 if target_issue.close_expired_episode(ttl=epi_ttl) is True:
257 gmDispatcher.send(signal='statustext', msg=_('Closed episodes older than %s days on health issue [%s]') % (epi_ttl, target_issue['description']))
258 existing_epi = target_issue.get_open_episode()
259
260 # no more open episode on target issue: should work now
261 if existing_epi is None:
262 episode['pk_health_issue'] = target_issue['pk_health_issue']
263 if save_to_backend:
264 episode.save_payload()
265 return True
266
267 # don't conflict on SELF ;-)
268 if existing_epi['pk_episode'] == episode['pk_episode']:
269 episode['pk_health_issue'] = target_issue['pk_health_issue']
270 if save_to_backend:
271 episode.save_payload()
272 return True
273
274 # we got two open episodes at once, ask user
275 move_range = (episode.best_guess_clinical_start_date, episode.best_guess_clinical_end_date)
276 if move_range[1] is None:
277 move_range_end = '?'
278 else:
279 move_range_end = move_range[1].strftime('%m/%y')
280 exist_range = (existing_epi.best_guess_clinical_start_date, existing_epi.best_guess_clinical_end_date)
281 if exist_range[1] is None:
282 exist_range_end = '?'
283 else:
284 exist_range_end = exist_range[1].strftime('%m/%y')
285 question = _(
286 'You want to associate the running episode:\n\n'
287 ' "%(new_epi_name)s" (%(new_epi_start)s - %(new_epi_end)s)\n\n'
288 'with the health issue:\n\n'
289 ' "%(issue_name)s"\n\n'
290 'There already is another episode running\n'
291 'for this health issue:\n\n'
292 ' "%(old_epi_name)s" (%(old_epi_start)s - %(old_epi_end)s)\n\n'
293 'However, there can only be one running\n'
294 'episode per health issue.\n\n'
295 'Which episode do you want to close ?'
296 ) % {
297 'new_epi_name': episode['description'],
298 'new_epi_start': move_range[0].strftime('%m/%y'),
299 'new_epi_end': move_range_end,
300 'issue_name': target_issue['description'],
301 'old_epi_name': existing_epi['description'],
302 'old_epi_start': exist_range[0].strftime('%m/%y'),
303 'old_epi_end': exist_range_end
304 }
305 dlg = gmGuiHelpers.c3ButtonQuestionDlg (
306 parent = None,
307 id = -1,
308 caption = _('Resolving two-running-episodes conflict'),
309 question = question,
310 button_defs = [
311 {'label': _('old episode'), 'default': True, 'tooltip': _('close existing episode "%s"') % existing_epi['description']},
312 {'label': _('new episode'), 'default': False, 'tooltip': _('close moving (new) episode "%s"') % episode['description']}
313 ]
314 )
315 decision = dlg.ShowModal()
316
317 if decision == wx.ID_CANCEL:
318 # button 3: move cancelled by user
319 return False
320
321 elif decision == wx.ID_YES:
322 # button 1: close old episode
323 existing_epi['episode_open'] = False
324 existing_epi.save_payload()
325
326 elif decision == wx.ID_NO:
327 # button 2: close new episode
328 episode['episode_open'] = False
329
330 else:
331 raise ValueError('invalid result from c3ButtonQuestionDlg: [%s]' % decision)
332
333 episode['pk_health_issue'] = target_issue['pk_health_issue']
334 if save_to_backend:
335 episode.save_payload()
336 return True
337
338 #----------------------------------------------------------------
340
341 # FIXME: support pre-selection
342
344
345 episodes = kwargs['episodes']
346 del kwargs['episodes']
347
348 gmListWidgets.cGenericListSelectorDlg.__init__(self, *args, **kwargs)
349
350 self.SetTitle(_('Select the episodes you are interested in ...'))
351 self._LCTRL_items.set_columns([_('Episode'), _('Status'), _('Health Issue')])
352 self._LCTRL_items.set_string_items (
353 items = [
354 [ epi['description'],
355 gmTools.bool2str(epi['episode_open'], _('ongoing'), ''),
356 gmTools.coalesce(epi['health_issue'], '')
357 ]
358 for epi in episodes ]
359 )
360 self._LCTRL_items.set_column_widths()
361 self._LCTRL_items.set_data(data = episodes)
362
363 #----------------------------------------------------------------
365 """Let user select an episode *description*.
366
367 The user can select an episode description from the previously
368 used descriptions across all episodes across all patients.
369
370 Selection is done with a phrasewheel so the user can
371 type the episode name and matches will be shown. Typing
372 "*" will show the entire list of episodes.
373
374 If the user types a description not existing yet a
375 new episode description will be returned.
376 """
378
379 mp = gmMatchProvider.cMatchProvider_SQL2 (
380 queries = [
381 """
382 SELECT DISTINCT ON (description)
383 description
384 AS data,
385 description
386 AS field_label,
387 description || ' ('
388 || CASE
389 WHEN is_open IS TRUE THEN _('ongoing')
390 ELSE _('closed')
391 END
392 || ')'
393 AS list_label
394 FROM
395 clin.episode
396 WHERE
397 description %(fragment_condition)s
398 ORDER BY description
399 LIMIT 30
400 """
401 ]
402 )
403 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
404 self.matcher = mp
405
406 #----------------------------------------------------------------
408 """Let user select an episode.
409
410 The user can select an episode from the existing episodes of a
411 patient. Selection is done with a phrasewheel so the user
412 can type the episode name and matches will be shown. Typing
413 "*" will show the entire list of episodes. Closed episodes
414 will be marked as such. If the user types an episode name not
415 in the list of existing episodes a new episode can be created
416 from it if the programmer activated that feature.
417
418 If keyword <patient_id> is set to None or left out the control
419 will listen to patient change signals and therefore act on
420 gmPerson.gmCurrentPatient() changes.
421 """
423 try:
424 self.__patient_id = int(kwargs['patient_id'])
425 self.use_current_patient = False
426 del kwargs['patient_id']
427 except KeyError:
428 self.__patient_id = None
429 self.use_current_patient = True
430
431 mp = gmEMRStructItems.cEpisodeMatchProvider()
432 if self.use_current_patient:
433 self.__register_patient_change_signals()
434 pat = gmPerson.gmCurrentPatient()
435 if pat.connected:
436 mp.set_context('pat', pat.ID)
437 else:
438 mp.set_context('pat', self.__patient_id)
439
440 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
441
442 self.matcher = mp
443
444 #--------------------------------------------------------
445 # external API
446 #--------------------------------------------------------
448 if self.use_current_patient:
449 return False
450 self.__patient_id = int(patient_id)
451 self.set_context('pat', self.__patient_id)
452 return True
453
454 #--------------------------------------------------------
456 self.__is_open_for_create_data = is_open # used (only) in _create_data()
457 return gmPhraseWheel.cPhraseWheel.GetData(self, can_create = can_create, as_instance = as_instance)
458
459 #--------------------------------------------------------
461
462 epi_name = self.GetValue().strip()
463 if epi_name == '':
464 gmDispatcher.send(signal = 'statustext', msg = _('Cannot create episode without name.'), beep = True)
465 _log.debug('cannot create episode without name')
466 return
467
468 if self.use_current_patient:
469 pat = gmPerson.gmCurrentPatient()
470 else:
471 pat = gmPerson.cPatient(aPK_obj = self.__patient_id)
472
473 emr = pat.emr
474 epi = emr.add_episode(episode_name = epi_name, is_open = self.__is_open_for_create_data)
475 if epi is None:
476 self.data = {}
477 else:
478 self.SetText (
479 value = epi_name,
480 data = epi['pk_episode']
481 )
482
483 #--------------------------------------------------------
486
487 #--------------------------------------------------------
488 # internal API
489 #--------------------------------------------------------
491 gmDispatcher.connect(self._pre_patient_unselection, 'pre_patient_unselection')
492 gmDispatcher.connect(self._post_patient_selection, 'post_patient_selection')
493
494 #--------------------------------------------------------
500
501 #--------------------------------------------------------
503 if self.use_current_patient:
504 patient = gmPerson.gmCurrentPatient()
505 self.set_context('pat', patient.ID)
506 return True
507
508 #----------------------------------------------------------------
509 from Gnumed.wxGladeWidgets import wxgEpisodeEditAreaPnl
510
511 -class cEpisodeEditAreaPnl(gmEditArea.cGenericEditAreaMixin, wxgEpisodeEditAreaPnl.wxgEpisodeEditAreaPnl):
512
514
515 try:
516 episode = kwargs['episode']
517 del kwargs['episode']
518 except KeyError:
519 episode = None
520
521 wxgEpisodeEditAreaPnl.wxgEpisodeEditAreaPnl.__init__(self, *args, **kwargs)
522 gmEditArea.cGenericEditAreaMixin.__init__(self)
523
524 self.data = episode
525
526 #----------------------------------------------------------------
527 # generic Edit Area mixin API
528 #----------------------------------------------------------------
530
531 errors = False
532
533 if len(self._PRW_description.GetValue().strip()) == 0:
534 errors = True
535 self._PRW_description.display_as_valid(False)
536 self._PRW_description.SetFocus()
537 else:
538 self._PRW_description.display_as_valid(True)
539 self._PRW_description.Refresh()
540
541 return not errors
542
543 #----------------------------------------------------------------
545
546 pat = gmPerson.gmCurrentPatient()
547 emr = pat.emr
548
549 epi = emr.add_episode(episode_name = self._PRW_description.GetValue().strip())
550 epi['summary'] = self._TCTRL_status.GetValue().strip()
551 epi['episode_open'] = not self._CHBOX_closed.IsChecked()
552 epi['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
553
554 issue_name = self._PRW_issue.GetValue().strip()
555 if len(issue_name) != 0:
556 epi['pk_health_issue'] = self._PRW_issue.GetData(can_create = True)
557 issue = gmEMRStructItems.cHealthIssue(aPK_obj = epi['pk_health_issue'])
558
559 if not move_episode_to_issue(episode = epi, target_issue = issue, save_to_backend = False):
560 gmDispatcher.send (
561 signal = 'statustext',
562 msg = _('Cannot attach episode [%s] to health issue [%s] because it already has a running episode.') % (
563 epi['description'],
564 issue['description']
565 )
566 )
567 gmEMRStructItems.delete_episode(episode = epi)
568 return False
569
570 epi.save()
571
572 epi.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
573
574 self.data = epi
575 return True
576
577 #----------------------------------------------------------------
579
580 self.data['description'] = self._PRW_description.GetValue().strip()
581 self.data['summary'] = self._TCTRL_status.GetValue().strip()
582 self.data['episode_open'] = not self._CHBOX_closed.IsChecked()
583 self.data['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
584
585 issue_name = self._PRW_issue.GetValue().strip()
586 if len(issue_name) == 0:
587 self.data['pk_health_issue'] = None
588 else:
589 self.data['pk_health_issue'] = self._PRW_issue.GetData(can_create = True)
590 issue = gmEMRStructItems.cHealthIssue(aPK_obj = self.data['pk_health_issue'])
591
592 if not move_episode_to_issue(episode = self.data, target_issue = issue, save_to_backend = False):
593 gmDispatcher.send (
594 signal = 'statustext',
595 msg = _('Cannot attach episode [%s] to health issue [%s] because it already has a running episode.') % (
596 self.data['description'],
597 issue['description']
598 )
599 )
600 return False
601
602 self.data.save()
603 self.data.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
604
605 return True
606
607 #----------------------------------------------------------------
609 if self.data is None:
610 ident = gmPerson.gmCurrentPatient()
611 else:
612 ident = gmPerson.cPerson(aPK_obj = self.data['pk_patient'])
613 self._TCTRL_patient.SetValue(ident.get_description_gender())
614 self._PRW_issue.SetText()
615 self._PRW_description.SetText()
616 self._TCTRL_status.SetValue('')
617 self._PRW_certainty.SetText()
618 self._CHBOX_closed.SetValue(False)
619 self._PRW_codes.SetText()
620
621 self._PRW_issue.SetFocus()
622
623 #----------------------------------------------------------------
625 ident = gmPerson.cPerson(aPK_obj = self.data['pk_patient'])
626 self._TCTRL_patient.SetValue(ident.get_description_gender())
627
628 if self.data['pk_health_issue'] is not None:
629 self._PRW_issue.SetText (
630 self.data['health_issue'],
631 data = self.data['pk_health_issue']
632 )
633
634 self._PRW_description.SetText (
635 self.data['description'],
636 data = self.data['description']
637 )
638
639 self._TCTRL_status.SetValue(gmTools.coalesce(self.data['summary'], ''))
640
641 if self.data['diagnostic_certainty_classification'] is not None:
642 self._PRW_certainty.SetData(data = self.data['diagnostic_certainty_classification'])
643
644 self._CHBOX_closed.SetValue(not self.data['episode_open'])
645
646 val, data = self._PRW_codes.generic_linked_codes2item_dict(self.data.generic_codes)
647 self._PRW_codes.SetText(val, data)
648
649 if self.data['pk_health_issue'] is None:
650 self._PRW_issue.SetFocus()
651 else:
652 self._PRW_description.SetFocus()
653
654 #----------------------------------------------------------------
657
658 #================================================================
659 # health issue related widgets/functions
660 #----------------------------------------------------------------
662 ea = cHealthIssueEditAreaPnl(parent, -1)
663 ea.data = issue
664 ea.mode = gmTools.coalesce(issue, 'new', 'edit')
665 dlg = gmEditArea.cGenericEditAreaDlg2(parent, -1, edit_area = ea, single_entry = single_entry)
666 dlg.SetTitle(gmTools.coalesce(issue, _('Adding a new health issue'), _('Editing a health issue')))
667 if dlg.ShowModal() == wx.ID_OK:
668 dlg.DestroyLater()
669 return True
670 dlg.DestroyLater()
671 return False
672
673 #----------------------------------------------------------------
675
676 if parent is None:
677 parent = wx.GetApp().GetTopWindow()
678 #-----------------------------------------
679 def edit(issue=None):
680 return edit_health_issue(parent = parent, issue = issue)
681 #-----------------------------------------
682 def delete(issue=None):
683 if gmEMRStructItems.delete_health_issue(health_issue = issue):
684 return True
685 gmDispatcher.send (
686 signal = 'statustext',
687 msg = _('Cannot delete health issue.'),
688 beep = True
689 )
690 return False
691 #-----------------------------------------
692 def get_tooltip(data):
693 if data is None:
694 return None
695 patient = gmPerson.cPatient(data['pk_patient'])
696 return data.format (
697 patient = patient,
698 with_summary = True,
699 with_codes = True,
700 with_episodes = True,
701 with_encounters = True,
702 with_medications = False,
703 with_hospital_stays = False,
704 with_procedures = False,
705 with_family_history = False,
706 with_documents = False,
707 with_tests = False,
708 with_vaccinations = False
709 )
710 #-----------------------------------------
711 def refresh(lctrl):
712 issues = emr.get_health_issues()
713 items = [
714 [
715 gmTools.bool2subst(i['is_confidential'], _('CONFIDENTIAL'), '', ''),
716 i['description'],
717 gmTools.bool2subst(i['clinically_relevant'], _('relevant'), '', ''),
718 gmTools.bool2subst(i['is_active'], _('active'), '', ''),
719 gmTools.bool2subst(i['is_cause_of_death'], _('fatal'), '', '')
720 ] for i in issues
721 ]
722 lctrl.set_string_items(items = items)
723 lctrl.set_data(data = issues)
724 #-----------------------------------------
725 return gmListWidgets.get_choices_from_list (
726 parent = parent,
727 msg = _('\nSelect the health issues !\n'),
728 caption = _('Showing health issues ...'),
729 columns = ['', _('Health issue'), '', '', ''],
730 single_selection = False,
731 edit_callback = edit,
732 new_callback = edit,
733 delete_callback = delete,
734 refresh_callback = refresh
735 )
736 #----------------------------------------------------------------
738
739 # FIXME: support pre-selection
740
742
743 issues = kwargs['issues']
744 del kwargs['issues']
745
746 gmListWidgets.cGenericListSelectorDlg.__init__(self, *args, **kwargs)
747
748 self.SetTitle(_('Select the health issues you are interested in ...'))
749 self._LCTRL_items.set_columns(['', _('Health Issue'), '', '', ''])
750
751 for issue in issues:
752 if issue['is_confidential']:
753 row_num = self._LCTRL_items.InsertItem(sys.maxsize, label = _('confidential'))
754 self._LCTRL_items.SetItemTextColour(row_num, wx.Colour('RED'))
755 else:
756 row_num = self._LCTRL_items.InsertItem(sys.maxsize, label = '')
757
758 self._LCTRL_items.SetItem(index = row_num, column = 1, label = issue['description'])
759 if issue['clinically_relevant']:
760 self._LCTRL_items.SetItem(index = row_num, column = 2, label = _('relevant'))
761 if issue['is_active']:
762 self._LCTRL_items.SetItem(index = row_num, column = 3, label = _('active'))
763 if issue['is_cause_of_death']:
764 self._LCTRL_items.SetItem(index = row_num, column = 4, label = _('fatal'))
765
766 self._LCTRL_items.set_column_widths()
767 self._LCTRL_items.set_data(data = issues)
768
769 #----------------------------------------------------------------
771 """Let the user select a health issue.
772
773 The user can select a health issue from the existing issues
774 of a patient. Selection is done with a phrasewheel so the user
775 can type the issue name and matches will be shown. Typing
776 "*" will show the entire list of issues. Inactive issues
777 will be marked as such. If the user types an issue name not
778 in the list of existing issues a new issue can be created
779 from it if the programmer activated that feature.
780
781 If keyword <patient_id> is set to None or left out the control
782 will listen to patient change signals and therefore act on
783 gmPerson.gmCurrentPatient() changes.
784 """
786
787 ctxt = {'ctxt_pat': {'where_part': 'pk_patient=%(pat)s', 'placeholder': 'pat'}}
788
789 mp = gmMatchProvider.cMatchProvider_SQL2 (
790 # FIXME: consider clin.health_issue.clinically_relevant
791 queries = ["""
792 SELECT
793 data,
794 field_label,
795 list_label
796 FROM ((
797 SELECT
798 pk_health_issue AS data,
799 description AS field_label,
800 description AS list_label
801 FROM clin.v_health_issues
802 WHERE
803 is_active IS true
804 AND
805 description %(fragment_condition)s
806 AND
807 %(ctxt_pat)s
808
809 ) UNION (
810
811 SELECT
812 pk_health_issue AS data,
813 description AS field_label,
814 description || _(' (inactive)') AS list_label
815 FROM clin.v_health_issues
816 WHERE
817 is_active IS false
818 AND
819 description %(fragment_condition)s
820 AND
821 %(ctxt_pat)s
822 )) AS union_query
823 ORDER BY
824 list_label"""
825 ],
826 context = ctxt
827 )
828 try: kwargs['patient_id']
829 except KeyError: kwargs['patient_id'] = None
830
831 if kwargs['patient_id'] is None:
832 self.use_current_patient = True
833 self.__register_patient_change_signals()
834 pat = gmPerson.gmCurrentPatient()
835 if pat.connected:
836 mp.set_context('pat', pat.ID)
837 else:
838 self.use_current_patient = False
839 self.__patient_id = int(kwargs['patient_id'])
840 mp.set_context('pat', self.__patient_id)
841
842 del kwargs['patient_id']
843
844 gmPhraseWheel.cPhraseWheel.__init__ (
845 self,
846 *args,
847 **kwargs
848 )
849 self.matcher = mp
850 #--------------------------------------------------------
851 # external API
852 #--------------------------------------------------------
854 if self.use_current_patient:
855 return False
856 self.__patient_id = int(patient_id)
857 self.set_context('pat', self.__patient_id)
858 return True
859 #--------------------------------------------------------
861 issue_name = self.GetValue().strip()
862 if issue_name == '':
863 gmDispatcher.send(signal = 'statustext', msg = _('Cannot create health issue without name.'), beep = True)
864 _log.debug('cannot create health issue without name')
865 return
866
867 if self.use_current_patient:
868 pat = gmPerson.gmCurrentPatient()
869 else:
870 pat = gmPerson.cPatient(aPK_obj = self.__patient_id)
871
872 emr = pat.emr
873 issue = emr.add_health_issue(issue_name = issue_name)
874
875 if issue is None:
876 self.data = {}
877 else:
878 self.SetText (
879 value = issue_name,
880 data = issue['pk_health_issue']
881 )
882 #--------------------------------------------------------
885 #--------------------------------------------------------
887 if self.GetData() is None:
888 return None
889 issue = self._data2instance()
890 if issue is None:
891 return None
892 return issue.format (
893 patient = None,
894 with_summary = True,
895 with_codes = False,
896 with_episodes = True,
897 with_encounters = True,
898 with_medications = False,
899 with_hospital_stays = False,
900 with_procedures = False,
901 with_family_history = False,
902 with_documents = False,
903 with_tests = False,
904 with_vaccinations = False,
905 with_external_care = True
906 )
907 #--------------------------------------------------------
908 # internal API
909 #--------------------------------------------------------
911 gmDispatcher.connect(self._pre_patient_unselection, 'pre_patient_unselection')
912 gmDispatcher.connect(self._post_patient_selection, 'post_patient_selection')
913 #--------------------------------------------------------
916 #--------------------------------------------------------
918 if self.use_current_patient:
919 patient = gmPerson.gmCurrentPatient()
920 self.set_context('pat', patient.ID)
921 return True
922 #------------------------------------------------------------
923 from Gnumed.wxGladeWidgets import wxgIssueSelectionDlg
924
926
928 try:
929 msg = kwargs['message']
930 except KeyError:
931 msg = None
932 del kwargs['message']
933 wxgIssueSelectionDlg.wxgIssueSelectionDlg.__init__(self, *args, **kwargs)
934 if msg is not None:
935 self._lbl_message.SetLabel(label=msg)
936 #--------------------------------------------------------
947 #------------------------------------------------------------
948 from Gnumed.wxGladeWidgets import wxgHealthIssueEditAreaPnl
949
950 -class cHealthIssueEditAreaPnl(gmEditArea.cGenericEditAreaMixin, wxgHealthIssueEditAreaPnl.wxgHealthIssueEditAreaPnl):
951 """Panel encapsulating health issue edit area functionality."""
952
954
955 try:
956 data = kwargs['issue']
957 del kwargs['issue']
958 except KeyError:
959 data = None
960
961 wxgHealthIssueEditAreaPnl.wxgHealthIssueEditAreaPnl.__init__(self, *args, **kwargs)
962 gmEditArea.cGenericEditAreaMixin.__init__(self)
963
964 self.mode = 'new'
965 self.data = data
966 if data is not None:
967 self.mode = 'edit'
968
969 self.__init_ui()
970 #----------------------------------------------------------------
972
973 # FIXME: include more sources: coding systems/other database columns
974 mp = gmMatchProvider.cMatchProvider_SQL2 (
975 queries = ["SELECT DISTINCT ON (description) description, description FROM clin.health_issue WHERE description %(fragment_condition)s LIMIT 50"]
976 )
977 mp.setThresholds(1, 3, 5)
978 self._PRW_condition.matcher = mp
979
980 mp = gmMatchProvider.cMatchProvider_SQL2 (
981 queries = ["""
982 SELECT DISTINCT ON (grouping) grouping, grouping from (
983
984 SELECT rank, grouping from ((
985
986 SELECT
987 grouping,
988 1 as rank
989 from
990 clin.health_issue
991 where
992 grouping %%(fragment_condition)s
993 and
994 (SELECT True from clin.encounter where fk_patient = %s and pk = clin.health_issue.fk_encounter)
995
996 ) union (
997
998 SELECT
999 grouping,
1000 2 as rank
1001 from
1002 clin.health_issue
1003 where
1004 grouping %%(fragment_condition)s
1005
1006 )) as union_result
1007
1008 order by rank
1009
1010 ) as order_result
1011
1012 limit 50""" % gmPerson.gmCurrentPatient().ID
1013 ]
1014 )
1015 mp.setThresholds(1, 3, 5)
1016 self._PRW_grouping.matcher = mp
1017
1018 self._PRW_age_noted.add_callback_on_lose_focus(self._on_leave_age_noted)
1019 self._PRW_year_noted.add_callback_on_lose_focus(self._on_leave_year_noted)
1020
1021 # self._PRW_age_noted.add_callback_on_modified(self._on_modified_age_noted)
1022 # self._PRW_year_noted.add_callback_on_modified(self._on_modified_year_noted)
1023
1024 self._PRW_year_noted.Enable(True)
1025
1026 self._PRW_codes.add_callback_on_lose_focus(self._on_leave_codes)
1027
1028 #----------------------------------------------------------------
1029 # generic Edit Area mixin API
1030 #----------------------------------------------------------------
1032
1033 if self._PRW_condition.GetValue().strip() == '':
1034 self._PRW_condition.display_as_valid(False)
1035 self._PRW_condition.SetFocus()
1036 return False
1037 self._PRW_condition.display_as_valid(True)
1038 self._PRW_condition.Refresh()
1039
1040 # FIXME: sanity check age/year diagnosed
1041 age_noted = self._PRW_age_noted.GetValue().strip()
1042 if age_noted != '':
1043 if gmDateTime.str2interval(str_interval = age_noted) is None:
1044 self._PRW_age_noted.display_as_valid(False)
1045 self._PRW_age_noted.SetFocus()
1046 return False
1047 self._PRW_age_noted.display_as_valid(True)
1048 return True
1049
1050 #----------------------------------------------------------------
1052 pat = gmPerson.gmCurrentPatient()
1053 emr = pat.emr
1054
1055 issue = emr.add_health_issue(issue_name = self._PRW_condition.GetValue().strip())
1056
1057 side = ''
1058 if self._ChBOX_left.GetValue():
1059 side += 's'
1060 if self._ChBOX_right.GetValue():
1061 side += 'd'
1062 issue['laterality'] = side
1063
1064 issue['summary'] = self._TCTRL_status.GetValue().strip()
1065 issue['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
1066 issue['grouping'] = self._PRW_grouping.GetValue().strip()
1067 issue['is_active'] = self._ChBOX_active.GetValue()
1068 issue['clinically_relevant'] = self._ChBOX_relevant.GetValue()
1069 issue['is_confidential'] = self._ChBOX_confidential.GetValue()
1070 issue['is_cause_of_death'] = self._ChBOX_caused_death.GetValue()
1071
1072 age_noted = self._PRW_age_noted.GetData()
1073 if age_noted is not None:
1074 issue['age_noted'] = age_noted
1075
1076 issue.save()
1077
1078 issue.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
1079
1080 self.data = issue
1081 return True
1082 #----------------------------------------------------------------
1084
1085 self.data['description'] = self._PRW_condition.GetValue().strip()
1086
1087 side = ''
1088 if self._ChBOX_left.GetValue():
1089 side += 's'
1090 if self._ChBOX_right.GetValue():
1091 side += 'd'
1092 self.data['laterality'] = side
1093
1094 self.data['summary'] = self._TCTRL_status.GetValue().strip()
1095 self.data['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
1096 self.data['grouping'] = self._PRW_grouping.GetValue().strip()
1097 self.data['is_active'] = bool(self._ChBOX_active.GetValue())
1098 self.data['clinically_relevant'] = bool(self._ChBOX_relevant.GetValue())
1099 self.data['is_confidential'] = bool(self._ChBOX_confidential.GetValue())
1100 self.data['is_cause_of_death'] = bool(self._ChBOX_caused_death.GetValue())
1101
1102 age_noted = self._PRW_age_noted.GetData()
1103 if age_noted is not None:
1104 self.data['age_noted'] = age_noted
1105
1106 self.data.save()
1107 self.data.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
1108
1109 return True
1110 #----------------------------------------------------------------
1112 self._PRW_condition.SetText()
1113 self._ChBOX_left.SetValue(0)
1114 self._ChBOX_right.SetValue(0)
1115 self._PRW_codes.SetText()
1116 self._on_leave_codes()
1117 self._PRW_certainty.SetText()
1118 self._PRW_grouping.SetText()
1119 self._TCTRL_status.SetValue('')
1120 self._PRW_age_noted.SetText()
1121 self._PRW_year_noted.SetText()
1122 self._ChBOX_active.SetValue(1)
1123 self._ChBOX_relevant.SetValue(1)
1124 self._ChBOX_confidential.SetValue(0)
1125 self._ChBOX_caused_death.SetValue(0)
1126
1127 self._PRW_condition.SetFocus()
1128 return True
1129 #----------------------------------------------------------------
1131 self._PRW_condition.SetText(self.data['description'])
1132
1133 lat = gmTools.coalesce(self.data['laterality'], '')
1134 if lat.find('s') == -1:
1135 self._ChBOX_left.SetValue(0)
1136 else:
1137 self._ChBOX_left.SetValue(1)
1138 if lat.find('d') == -1:
1139 self._ChBOX_right.SetValue(0)
1140 else:
1141 self._ChBOX_right.SetValue(1)
1142
1143 val, data = self._PRW_codes.generic_linked_codes2item_dict(self.data.generic_codes)
1144 self._PRW_codes.SetText(val, data)
1145 self._on_leave_codes()
1146
1147 if self.data['diagnostic_certainty_classification'] is not None:
1148 self._PRW_certainty.SetData(data = self.data['diagnostic_certainty_classification'])
1149 self._PRW_grouping.SetText(gmTools.coalesce(self.data['grouping'], ''))
1150 self._TCTRL_status.SetValue(gmTools.coalesce(self.data['summary'], ''))
1151
1152 if self.data['age_noted'] is None:
1153 self._PRW_age_noted.SetText()
1154 else:
1155 self._PRW_age_noted.SetText (
1156 value = '%sd' % self.data['age_noted'].days,
1157 data = self.data['age_noted']
1158 )
1159
1160 self._ChBOX_active.SetValue(self.data['is_active'])
1161 self._ChBOX_relevant.SetValue(self.data['clinically_relevant'])
1162 self._ChBOX_confidential.SetValue(self.data['is_confidential'])
1163 self._ChBOX_caused_death.SetValue(self.data['is_cause_of_death'])
1164
1165 self._TCTRL_status.SetFocus()
1166
1167 return True
1168 #----------------------------------------------------------------
1171 #--------------------------------------------------------
1172 # internal helpers
1173 #--------------------------------------------------------
1175 if not self._PRW_codes.IsModified():
1176 return True
1177
1178 self._TCTRL_code_details.SetValue('- ' + '\n- '.join([ c['list_label'] for c in self._PRW_codes.GetData() ]))
1179 #--------------------------------------------------------
1181
1182 if not self._PRW_age_noted.IsModified():
1183 return True
1184
1185 age_str = self._PRW_age_noted.GetValue().strip()
1186
1187 if age_str == '':
1188 return True
1189
1190 issue_age = gmDateTime.str2interval(str_interval = age_str)
1191
1192 if issue_age is None:
1193 self.StatusText = _('Cannot parse [%s] into valid interval.') % age_str
1194 self._PRW_age_noted.display_as_valid(False)
1195 return True
1196
1197 pat = gmPerson.gmCurrentPatient()
1198 if pat['dob'] is not None:
1199 max_issue_age = pydt.datetime.now(tz=pat['dob'].tzinfo) - pat['dob']
1200 if issue_age >= max_issue_age:
1201 self.StatusText = _('Health issue cannot have been noted at age %s. Patient is only %s old.') % (issue_age, pat.get_medical_age())
1202 self._PRW_age_noted.display_as_valid(False)
1203 return True
1204
1205 self._PRW_age_noted.display_as_valid(True)
1206 self._PRW_age_noted.SetText(value = age_str, data = issue_age)
1207
1208 if pat['dob'] is not None:
1209 fts = gmDateTime.cFuzzyTimestamp (
1210 timestamp = pat['dob'] + issue_age,
1211 accuracy = gmDateTime.acc_months
1212 )
1213 self._PRW_year_noted.SetText(value = str(fts), data = fts)
1214
1215 return True
1216 #--------------------------------------------------------
1218
1219 if not self._PRW_year_noted.IsModified():
1220 return True
1221
1222 year_noted = self._PRW_year_noted.GetData()
1223
1224 if year_noted is None:
1225 if self._PRW_year_noted.GetValue().strip() == '':
1226 self._PRW_year_noted.display_as_valid(True)
1227 return True
1228 self._PRW_year_noted.display_as_valid(False)
1229 return True
1230
1231 year_noted = year_noted.get_pydt()
1232
1233 if year_noted >= pydt.datetime.now(tz = year_noted.tzinfo):
1234 self.StatusText = _('Condition diagnosed in the future.')
1235 self._PRW_year_noted.display_as_valid(False)
1236 return True
1237
1238 self._PRW_year_noted.display_as_valid(True)
1239
1240 pat = gmPerson.gmCurrentPatient()
1241 if pat['dob'] is not None:
1242 issue_age = year_noted - pat['dob']
1243 age_str = gmDateTime.format_interval_medically(interval = issue_age)
1244 self._PRW_age_noted.SetText(age_str, issue_age, True)
1245
1246 return True
1247 #--------------------------------------------------------
1251 #--------------------------------------------------------
1255
1256 #================================================================
1257 # diagnostic certainty related widgets/functions
1258 #----------------------------------------------------------------
1260
1262
1263 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
1264
1265 self.selection_only = False # can be NULL, too
1266
1267 mp = gmMatchProvider.cMatchProvider_FixedList (
1268 aSeq = [
1269 {'data': 'A', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str('A'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str('A'), 'weight': 1},
1270 {'data': 'B', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str('B'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str('B'), 'weight': 1},
1271 {'data': 'C', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str('C'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str('C'), 'weight': 1},
1272 {'data': 'D', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str('D'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str('D'), 'weight': 1}
1273 ]
1274 )
1275 mp.setThresholds(1, 2, 4)
1276 self.matcher = mp
1277
1278 self.SetToolTip(_(
1279 "The diagnostic classification or grading of this assessment.\n"
1280 "\n"
1281 "This documents how certain one is about this being a true diagnosis."
1282 ))
1283
1284 #================================================================
1285 # MAIN
1286 #----------------------------------------------------------------
1287 if __name__ == '__main__':
1288
1289 if len(sys.argv) < 2:
1290 sys.exit()
1291
1292 if sys.argv[1] != 'test':
1293 sys.exit()
1294
1295 from Gnumed.business import gmPersonSearch
1296 from Gnumed.wxpython import gmPatSearchWidgets
1297
1298 #================================================================
1300 """
1301 Test application for testing EMR struct widgets
1302 """
1303 #--------------------------------------------------------
1305 """
1306 Create test application UI
1307 """
1308 frame = wx.Frame (
1309 None,
1310 -4,
1311 'Testing EMR struct widgets',
1312 size=wx.Size(600, 400),
1313 style=wx.DEFAULT_FRAME_STYLE | wx.NO_FULL_REPAINT_ON_RESIZE
1314 )
1315 filemenu = wx.Menu()
1316 filemenu.AppendSeparator()
1317 item = filemenu.Append(ID_EXIT, "E&xit"," Terminate test application")
1318 self.Bind(wx.EVT_MENU, self.OnCloseWindow, item)
1319
1320 # Creating the menubar.
1321 menuBar = wx.MenuBar()
1322 menuBar.Append(filemenu,"&File")
1323
1324 frame.SetMenuBar(menuBar)
1325
1326 txt = wx.StaticText( frame, -1, _("Select desired test option from the 'File' menu"),
1327 wx.DefaultPosition, wx.DefaultSize, 0 )
1328
1329 # patient EMR
1330 self.__pat = gmPerson.gmCurrentPatient()
1331
1332 frame.Show(1)
1333 return 1
1334 #--------------------------------------------------------
1340
1341 #----------------------------------------------------------------
1343 app = wx.PyWidgetTester(size = (200, 300))
1344 emr = pat.emr
1345 epi = emr.get_episodes()[0]
1346 pnl = cEpisodeEditAreaPnl(app.frame, -1, episode=epi)
1347 app.frame.Show(True)
1348 app.MainLoop()
1349 #----------------------------------------------------------------
1351 app = wx.PyWidgetTester(size = (200, 300))
1352 emr = pat.emr
1353 epi = emr.get_episodes()[0]
1354 edit_episode(parent=app.frame, episode=epi)
1355
1356 #----------------------------------------------------------------
1358 frame = wx.Frame()
1359 wx.GetApp().SetTopWindow(frame)
1360 prw = cEpisodeSelectionPhraseWheel(frame)
1361 #app.SetWidget()
1362 # app.SetWidget(cEpisodeSelectionPhraseWheel, id=-1, size=(350,20), pos=(10,20), patient_id=pat.ID)
1363 frame.Show(True)
1364 wx.GetApp().MainLoop()
1365
1366 #----------------------------------------------------------------
1368 app = wx.PyWidgetTester(size = (200, 300))
1369 edit_health_issue(parent=app.frame, issue=None)
1370
1371 #----------------------------------------------------------------
1373 app = wx.PyWidgetTester(size = (200, 300))
1374 app.SetWidget(cHealthIssueEditAreaPnl, id=-1, size = (400,400))
1375 app.MainLoop()
1376
1377 #================================================================
1378
1379 branch = gmPraxis.get_praxis_branches()[0]
1380 prax = gmPraxis.gmCurrentPraxisBranch(branch)
1381 print(prax)
1382
1383 #app = wx.PyWidgetTester(size = (400, 40))
1384 app = wx.App()#size = (400, 40))
1385
1386 # obtain patient
1387 pat = gmPersonSearch.ask_for_patient()
1388 if pat is None:
1389 print("No patient. Exiting gracefully...")
1390 sys.exit(0)
1391 gmPatSearchWidgets.set_active_patient(patient=pat)
1392
1393 #test_epsiode_edit_area_pnl()
1394 #test_episode_edit_area_dialog()
1395 #test_health_issue_edit_area_dlg()
1396 test_episode_selection_prw()
1397
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Sat Feb 29 02:55:27 2020 | http://epydoc.sourceforge.net |