| 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 from Gnumed.pycommon import gmI18N
28 from Gnumed.pycommon import gmExceptions
29 from Gnumed.pycommon import gmCfg
30 from Gnumed.pycommon import gmDateTime
31 from Gnumed.pycommon import gmTools
32 from Gnumed.pycommon import gmDispatcher
33 from Gnumed.pycommon import gmMatchProvider
34
35 from Gnumed.business import gmEMRStructItems
36 from Gnumed.business import gmPraxis
37 from Gnumed.business import gmPerson
38
39 from Gnumed.wxpython import gmPhraseWheel
40 from Gnumed.wxpython import gmGuiHelpers
41 from Gnumed.wxpython import gmListWidgets
42 from Gnumed.wxpython import gmEditArea
43
44
45 _log = logging.getLogger('gm.ui')
46 #================================================================
47 # EMR access helper functions
48 #----------------------------------------------------------------
50 """Spin time in seconds."""
51 if time2spin == 0:
52 return
53 sleep_time = 0.1
54 total_rounds = int(time2spin / sleep_time)
55 if total_rounds < 1:
56 return
57 rounds = 0
58 while rounds < total_rounds:
59 wx.Yield()
60 time.sleep(sleep_time)
61 rounds += 1
62 #================================================================
63 # performed procedure related widgets/functions
64 #----------------------------------------------------------------
66
67 pat = gmPerson.gmCurrentPatient()
68 emr = pat.get_emr()
69
70 if parent is None:
71 parent = wx.GetApp().GetTopWindow()
72 #-----------------------------------------
73 def edit(procedure=None):
74 return edit_procedure(parent = parent, procedure = procedure)
75 #-----------------------------------------
76 def delete(procedure=None):
77 if gmEMRStructItems.delete_performed_procedure(procedure = procedure['pk_procedure']):
78 return True
79
80 gmDispatcher.send (
81 signal = u'statustext',
82 msg = _('Cannot delete performed procedure.'),
83 beep = True
84 )
85 return False
86 #-----------------------------------------
87 def refresh(lctrl):
88 procs = emr.get_performed_procedures()
89
90 items = [
91 [
92 u'%s%s' % (
93 p['clin_when'].strftime('%Y-%m-%d'),
94 gmTools.bool2subst (
95 p['is_ongoing'],
96 _(' (ongoing)'),
97 gmTools.coalesce (
98 initial = p['clin_end'],
99 instead = u'',
100 template_initial = u' - %s',
101 function_initial = ('strftime', u'%Y-%m-%d')
102 )
103 )
104 ),
105 p['clin_where'],
106 p['episode'],
107 p['performed_procedure']
108 ] for p in procs
109 ]
110 lctrl.set_string_items(items = items)
111 lctrl.set_data(data = procs)
112 #-----------------------------------------
113 gmListWidgets.get_choices_from_list (
114 parent = parent,
115 msg = _('\nSelect the procedure you want to edit !\n'),
116 caption = _('Editing performed procedures ...'),
117 columns = [_('When'), _('Where'), _('Episode'), _('Procedure')],
118 single_selection = True,
119 edit_callback = edit,
120 new_callback = edit,
121 delete_callback = delete,
122 refresh_callback = refresh
123 )
124 #----------------------------------------------------------------
126 ea = cProcedureEAPnl(parent = parent, id = -1)
127 ea.data = procedure
128 ea.mode = gmTools.coalesce(procedure, 'new', 'edit')
129 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = True)
130 dlg.SetTitle(gmTools.coalesce(procedure, _('Adding a procedure'), _('Editing a procedure')))
131 if dlg.ShowModal() == wx.ID_OK:
132 dlg.Destroy()
133 return True
134 dlg.Destroy()
135 return False
136 #----------------------------------------------------------------
137 from Gnumed.wxGladeWidgets import wxgProcedureEAPnl
138
140
142 wxgProcedureEAPnl.wxgProcedureEAPnl.__init__(self, *args, **kwargs)
143 gmEditArea.cGenericEditAreaMixin.__init__(self)
144
145 self.mode = 'new'
146 self.data = None
147
148 self.__init_ui()
149 #----------------------------------------------------------------
151 self._PRW_hospital_stay.add_callback_on_lose_focus(callback = self._on_hospital_stay_lost_focus)
152 self._PRW_hospital_stay.set_context(context = 'pat', val = gmPerson.gmCurrentPatient().ID)
153 self._PRW_location.add_callback_on_lose_focus(callback = self._on_location_lost_focus)
154 self._DPRW_date.add_callback_on_lose_focus(callback = self._on_start_lost_focus)
155 self._DPRW_end.add_callback_on_lose_focus(callback = self._on_end_lost_focus)
156
157 # location
158 mp = gmMatchProvider.cMatchProvider_SQL2 (
159 queries = [
160 u"""
161 SELECT DISTINCT ON (data) data, location
162 FROM (
163 SELECT
164 clin_where as data,
165 clin_where as location
166 FROM
167 clin.procedure
168 WHERE
169 clin_where %(fragment_condition)s
170
171 UNION ALL
172
173 SELECT
174 narrative as data,
175 narrative as location
176 FROM
177 clin.hospital_stay
178 WHERE
179 narrative %(fragment_condition)s
180 ) as union_result
181 ORDER BY data
182 LIMIT 25"""
183 ]
184 )
185 mp.setThresholds(2, 4, 6)
186 self._PRW_location.matcher = mp
187
188 # procedure
189 mp = gmMatchProvider.cMatchProvider_SQL2 (
190 queries = [
191 u"""
192 select distinct on (narrative) narrative, narrative
193 from clin.procedure
194 where narrative %(fragment_condition)s
195 order by narrative
196 limit 25
197 """ ]
198 )
199 mp.setThresholds(2, 4, 6)
200 self._PRW_procedure.matcher = mp
201 #----------------------------------------------------------------
203 stay = self._PRW_hospital_stay.GetData()
204 if stay is None:
205 self._PRW_hospital_stay.SetText()
206 self._PRW_location.Enable(True)
207 self._PRW_episode.Enable(True)
208 self._LBL_hospital_details.SetLabel(u'')
209 else:
210 self._PRW_location.SetText()
211 self._PRW_location.Enable(False)
212 self._PRW_episode.SetText()
213 self._PRW_episode.Enable(False)
214 self._LBL_hospital_details.SetLabel(gmEMRStructItems.cHospitalStay(aPK_obj = stay).format())
215 #----------------------------------------------------------------
217 if self._PRW_location.GetValue().strip() == u'':
218 self._PRW_hospital_stay.Enable(True)
219 # self._PRW_episode.Enable(False)
220 else:
221 self._PRW_hospital_stay.SetText()
222 self._PRW_hospital_stay.Enable(False)
223 self._PRW_hospital_stay.display_as_valid(True)
224 # self._PRW_episode.Enable(True)
225 #----------------------------------------------------------------
227 if not self._DPRW_date.is_valid_timestamp():
228 return
229 end = self._DPRW_end.GetData()
230 if end is None:
231 return
232 end = end.get_pydt()
233 start = self._DPRW_date.GetData().get_pydt()
234 if start < end:
235 return
236 self._DPRW_date.display_as_valid(False)
237 #----------------------------------------------------------------
239 end = self._DPRW_end.GetData()
240 if end is None:
241 self._CHBOX_ongoing.Enable(True)
242 self._DPRW_end.display_as_valid(True)
243 else:
244 self._CHBOX_ongoing.Enable(False)
245 end = end.get_pydt()
246 now = gmDateTime.pydt_now_here()
247 if end > now:
248 self._CHBOX_ongoing.SetValue(True)
249 else:
250 self._CHBOX_ongoing.SetValue(False)
251 start = self._DPRW_date.GetData()
252 if start is None:
253 self._DPRW_end.display_as_valid(True)
254 else:
255 start = start.get_pydt()
256 if end > start:
257 self._DPRW_end.display_as_valid(True)
258 else:
259 self._DPRW_end.display_as_valid(False)
260 #----------------------------------------------------------------
261 # generic Edit Area mixin API
262 #----------------------------------------------------------------
264
265 has_errors = False
266
267 if not self._DPRW_date.is_valid_timestamp():
268 self._DPRW_date.display_as_valid(False)
269 has_errors = True
270 else:
271 self._DPRW_date.display_as_valid(True)
272
273 start = self._DPRW_date.GetData()
274 end = self._DPRW_end.GetData()
275 self._DPRW_end.display_as_valid(True)
276 if end is not None:
277 end = end.get_pydt()
278 if start is not None:
279 start = start.get_pydt()
280 if end < start:
281 has_errors = True
282 self._DPRW_end.display_as_valid(False)
283 if self._CHBOX_ongoing.IsChecked():
284 now = gmDateTime.pydt_now_here()
285 if end < now:
286 has_errors = True
287 self._DPRW_end.display_as_valid(False)
288
289 if self._PRW_hospital_stay.GetData() is None:
290 if self._PRW_episode.GetData() is None:
291 self._PRW_episode.display_as_valid(False)
292 has_errors = True
293 else:
294 self._PRW_episode.display_as_valid(True)
295 else:
296 self._PRW_episode.display_as_valid(True)
297
298 if (self._PRW_procedure.GetValue() is None) or (self._PRW_procedure.GetValue().strip() == u''):
299 self._PRW_procedure.display_as_valid(False)
300 has_errors = True
301 else:
302 self._PRW_procedure.display_as_valid(True)
303
304 invalid_location = (
305 (self._PRW_hospital_stay.GetData() is None) and (self._PRW_location.GetValue().strip() == u'')
306 or
307 (self._PRW_hospital_stay.GetData() is not None) and (self._PRW_location.GetValue().strip() != u'')
308 )
309 if invalid_location:
310 self._PRW_hospital_stay.display_as_valid(False)
311 self._PRW_location.display_as_valid(False)
312 has_errors = True
313 else:
314 self._PRW_hospital_stay.display_as_valid(True)
315 self._PRW_location.display_as_valid(True)
316
317 gmDispatcher.send(signal = 'statustext', msg = _('Cannot save procedure.'), beep = True)
318
319 return (has_errors is False)
320 #----------------------------------------------------------------
322
323 pat = gmPerson.gmCurrentPatient()
324 emr = pat.get_emr()
325
326 if self._PRW_hospital_stay.GetData() is None:
327 stay = None
328 epi = self._PRW_episode.GetData()
329 loc = self._PRW_location.GetValue().strip()
330 else:
331 stay = self._PRW_hospital_stay.GetData()
332 epi = gmEMRStructItems.cHospitalStay(aPK_obj = stay)['pk_episode']
333 loc = None
334
335 proc = emr.add_performed_procedure (
336 episode = epi,
337 location = loc,
338 hospital_stay = stay,
339 procedure = self._PRW_procedure.GetValue().strip()
340 )
341
342 proc['clin_when'] = self._DPRW_date.GetData().get_pydt()
343 if self._DPRW_end.GetData() is None:
344 proc['clin_end'] = None
345 else:
346 proc['clin_end'] = self._DPRW_end.GetData().get_pydt()
347 proc['is_ongoing'] = self._CHBOX_ongoing.IsChecked()
348 proc.save()
349
350 proc.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
351
352 self.data = proc
353
354 return True
355 #----------------------------------------------------------------
357 self.data['clin_when'] = self._DPRW_date.GetData().get_pydt()
358
359 if self._DPRW_end.GetData() is None:
360 self.data['clin_end'] = None
361 else:
362 self.data['clin_end'] = self._DPRW_end.GetData().get_pydt()
363
364 self.data['is_ongoing'] = self._CHBOX_ongoing.IsChecked()
365
366 if self._PRW_hospital_stay.GetData() is None:
367 self.data['pk_hospital_stay'] = None
368 self.data['clin_where'] = self._PRW_location.GetValue().strip()
369 self.data['pk_episode'] = self._PRW_episode.GetData()
370 else:
371 self.data['pk_hospital_stay'] = self._PRW_hospital_stay.GetData()
372 self.data['clin_where'] = None
373 stay = gmEMRStructItems.cHospitalStay(aPK_obj = self._PRW_hospital_stay.GetData())
374 self.data['pk_episode'] = stay['pk_episode']
375
376 self.data['performed_procedure'] = self._PRW_procedure.GetValue().strip()
377
378 self.data.save()
379 self.data.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
380
381 return True
382 #----------------------------------------------------------------
384 self._DPRW_date.SetText()
385 self._DPRW_end.SetText()
386 self._CHBOX_ongoing.SetValue(False)
387 self._CHBOX_ongoing.Enable(True)
388 self._PRW_hospital_stay.SetText()
389 self._PRW_location.SetText()
390 self._PRW_episode.SetText()
391 self._PRW_procedure.SetText()
392 self._PRW_codes.SetText()
393
394 self._PRW_procedure.SetFocus()
395 #----------------------------------------------------------------
397 self._DPRW_date.SetData(data = self.data['clin_when'])
398 if self.data['clin_end'] is None:
399 self._DPRW_end.SetText()
400 self._CHBOX_ongoing.Enable(True)
401 self._CHBOX_ongoing.SetValue(self.data['is_ongoing'])
402 else:
403 self._DPRW_end.SetData(data = self.data['clin_end'])
404 self._CHBOX_ongoing.Enable(False)
405 now = gmDateTime.pydt_now_here()
406 if self.data['clin_end'] > now:
407 self._CHBOX_ongoing.SetValue(True)
408 else:
409 self._CHBOX_ongoing.SetValue(False)
410 self._PRW_episode.SetText(value = self.data['episode'], data = self.data['pk_episode'])
411 self._PRW_procedure.SetText(value = self.data['performed_procedure'], data = self.data['performed_procedure'])
412
413 if self.data['pk_hospital_stay'] is None:
414 self._PRW_hospital_stay.SetText()
415 self._LBL_hospital_details.SetLabel(u'')
416 self._PRW_location.SetText(value = self.data['clin_where'], data = self.data['clin_where'])
417 else:
418 self._PRW_hospital_stay.SetText(value = self.data['clin_where'], data = self.data['pk_hospital_stay'])
419 self._LBL_hospital_details.SetLabel(gmEMRStructItems.cHospitalStay(aPK_obj = self.data['pk_hospital_stay']).format())
420 self._PRW_location.SetText()
421
422 val, data = self._PRW_codes.generic_linked_codes2item_dict(self.data.generic_codes)
423 self._PRW_codes.SetText(val, data)
424
425 self._PRW_procedure.SetFocus()
426 #----------------------------------------------------------------
428 self._refresh_as_new()
429 self._PRW_episode.SetText(value = self.data['episode'], data = self.data['pk_episode'])
430 if self.data['pk_hospital_stay'] is None:
431 self._PRW_hospital_stay.SetText()
432 self._PRW_location.SetText(value = self.data['clin_where'], data = self.data['clin_where'])
433 else:
434 self._PRW_hospital_stay.SetText(value = self.data['clin_where'], data = self.data['pk_hospital_stay'])
435 self._PRW_location.SetText()
436
437 self._PRW_procedure.SetFocus()
438 #----------------------------------------------------------------
439 # event handlers
440 #----------------------------------------------------------------
445 #----------------------------------------------------------------
447 if self._CHBOX_ongoing.IsChecked():
448 end = self._DPRW_end.GetData()
449 if end is None:
450 self._DPRW_end.display_as_valid(True)
451 else:
452 end = end.get_pydt()
453 now = gmDateTime.pydt_now_here()
454 if end > now:
455 self._DPRW_end.display_as_valid(True)
456 else:
457 self._DPRW_end.display_as_valid(False)
458 else:
459 self._DPRW_end.is_valid_timestamp()
460 event.Skip()
461 #================================================================
462 # hospitalizations related widgets/functions
463 #----------------------------------------------------------------
465
466 pat = gmPerson.gmCurrentPatient()
467 emr = pat.get_emr()
468
469 if parent is None:
470 parent = wx.GetApp().GetTopWindow()
471 #-----------------------------------------
472 def get_tooltip(stay=None):
473 if stay is None:
474 return None
475 return stay.format (
476 include_procedures = True,
477 include_docs = True
478 )
479 #-----------------------------------------
480 def edit(stay=None):
481 return edit_hospital_stay(parent = parent, hospital_stay = stay)
482 #-----------------------------------------
483 def delete(stay=None):
484 if gmEMRStructItems.delete_hospital_stay(stay = stay['pk_hospital_stay']):
485 return True
486 gmDispatcher.send (
487 signal = u'statustext',
488 msg = _('Cannot delete hospitalization.'),
489 beep = True
490 )
491 return False
492 #-----------------------------------------
493 def refresh(lctrl):
494 stays = emr.get_hospital_stays()
495 items = [
496 [
497 s['admission'].strftime('%Y-%m-%d'),
498 gmTools.coalesce(s['discharge'], u'', function_initial = ('strftime', '%Y-%m-%d')),
499 s['episode'],
500 gmTools.coalesce(s['hospital'], u'')
501 ] for s in stays
502 ]
503 lctrl.set_string_items(items = items)
504 lctrl.set_data(data = stays)
505 #-----------------------------------------
506 gmListWidgets.get_choices_from_list (
507 parent = parent,
508 msg = _("The patient's hospitalizations:\n"),
509 caption = _('Editing hospitalizations ...'),
510 columns = [_('Admission'), _('Discharge'), _('Reason'), _('Hospital')],
511 single_selection = True,
512 edit_callback = edit,
513 new_callback = edit,
514 delete_callback = delete,
515 refresh_callback = refresh,
516 list_tooltip_callback = get_tooltip
517 )
518
519 #----------------------------------------------------------------
521 ea = cHospitalStayEditAreaPnl(parent = parent, id = -1)
522 ea.data = hospital_stay
523 ea.mode = gmTools.coalesce(hospital_stay, 'new', 'edit')
524 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = True)
525 dlg.SetTitle(gmTools.coalesce(hospital_stay, _('Adding a hospitalization'), _('Editing a hospitalization')))
526 if dlg.ShowModal() == wx.ID_OK:
527 dlg.Destroy()
528 return True
529 dlg.Destroy()
530 return False
531 #----------------------------------------------------------------
533 """Phrasewheel to allow selection of a hospitalization."""
535
536 gmPhraseWheel.cPhraseWheel.__init__ (self, *args, **kwargs)
537
538 ctxt = {'ctxt_pat': {'where_part': u'pk_patient = %(pat)s and', 'placeholder': u'pat'}}
539
540 mp = gmMatchProvider.cMatchProvider_SQL2 (
541 queries = [
542 u"""
543 select
544 pk_hospital_stay,
545 descr
546 from (
547 select distinct on (pk_hospital_stay)
548 pk_hospital_stay,
549 descr
550 from
551 (select
552 pk_hospital_stay,
553 (
554 to_char(admission, 'YYYY-Mon-DD')
555 || coalesce((' (' || hospital || '):'), ': ')
556 || episode
557 || coalesce((' (' || health_issue || ')'), '')
558 ) as descr
559 from
560 clin.v_pat_hospital_stays
561 where
562 %(ctxt_pat)s
563
564 hospital %(fragment_condition)s
565 or
566 episode %(fragment_condition)s
567 or
568 health_issue %(fragment_condition)s
569 ) as the_stays
570 ) as distinct_stays
571 order by descr
572 limit 25
573 """ ],
574 context = ctxt
575 )
576 mp.setThresholds(3, 4, 6)
577 mp.set_context('pat', gmPerson.gmCurrentPatient().ID)
578
579 self.matcher = mp
580 self.selection_only = True
581 #----------------------------------------------------------------
582 from Gnumed.wxGladeWidgets import wxgHospitalStayEditAreaPnl
583
584 -class cHospitalStayEditAreaPnl(wxgHospitalStayEditAreaPnl.wxgHospitalStayEditAreaPnl, gmEditArea.cGenericEditAreaMixin):
585
587 wxgHospitalStayEditAreaPnl.wxgHospitalStayEditAreaPnl.__init__(self, *args, **kwargs)
588 gmEditArea.cGenericEditAreaMixin.__init__(self)
589 #----------------------------------------------------------------
590 # generic Edit Area mixin API
591 #----------------------------------------------------------------
593
594 valid = True
595
596 if self._PRW_episode.GetValue().strip() == u'':
597 valid = False
598 self._PRW_episode.display_as_valid(False)
599 gmDispatcher.send(signal = 'statustext', msg = _('Must select an episode or enter a name for a new one. Cannot save hospitalization.'), beep = True)
600 self._PRW_episode.SetFocus()
601
602 if not self._PRW_admission.is_valid_timestamp(allow_empty = False):
603 valid = False
604 gmDispatcher.send(signal = 'statustext', msg = _('Missing admission data. Cannot save hospitalization.'), beep = True)
605 self._PRW_admission.SetFocus()
606
607 if self._PRW_discharge.is_valid_timestamp(allow_empty = True):
608 if self._PRW_discharge.date is not None:
609 adm = self._PRW_admission.date
610 discharge = self._PRW_discharge.date
611 # normalize for comparison
612 discharge = discharge.replace (
613 hour = adm.hour,
614 minute = adm.minute,
615 second = adm.second,
616 microsecond = adm.microsecond
617 )
618 if adm is not None:
619 if discharge == adm:
620 self._PRW_discharge.SetData(discharge + pydt.timedelta(seconds = 1))
621 elif not self._PRW_discharge.date > self._PRW_admission.date:
622 valid = False
623 self._PRW_discharge.display_as_valid(False)
624 gmDispatcher.send(signal = 'statustext', msg = _('Discharge date must be empty or later than admission. Cannot save hospitalization.'), beep = True)
625 self._PRW_discharge.SetFocus()
626
627 return (valid is True)
628 #----------------------------------------------------------------
630
631 pat = gmPerson.gmCurrentPatient()
632 emr = pat.get_emr()
633 stay = emr.add_hospital_stay(episode = self._PRW_episode.GetData(can_create = True))
634 stay['hospital'] = gmTools.none_if(self._PRW_hospital.GetValue().strip(), u'')
635 stay['admission'] = self._PRW_admission.GetData()
636 stay['discharge'] = self._PRW_discharge.GetData()
637 stay.save_payload()
638
639 self.data = stay
640 return True
641 #----------------------------------------------------------------
643
644 self.data['pk_episode'] = self._PRW_episode.GetData(can_create = True)
645 self.data['hospital'] = gmTools.none_if(self._PRW_hospital.GetValue().strip(), u'')
646 self.data['admission'] = self._PRW_admission.GetData()
647 self.data['discharge'] = self._PRW_discharge.GetData()
648 self.data.save_payload()
649
650 return True
651 #----------------------------------------------------------------
653 self._PRW_hospital.SetText(value = u'')
654 self._PRW_episode.SetText(value = u'')
655 self._PRW_admission.SetText(data = gmDateTime.pydt_now_here())
656 self._PRW_discharge.SetText()
657 self._PRW_hospital.SetFocus()
658 #----------------------------------------------------------------
660 if self.data['hospital'] is not None:
661 self._PRW_hospital.SetText(value = self.data['hospital'])
662
663 if self.data['pk_episode'] is not None:
664 self._PRW_episode.SetText(value = self.data['episode'], data = self.data['pk_episode'])
665
666 self._PRW_admission.SetText(data = self.data['admission'])
667 self._PRW_discharge.SetText(data = self.data['discharge'])
668
669 self._PRW_hospital.SetFocus()
670 #----------------------------------------------------------------
673
674 #================================================================
675 # encounter related widgets/functions
676 #----------------------------------------------------------------
678 emr.start_new_encounter()
679 gmDispatcher.send(signal = 'statustext', msg = _('Started a new encounter for the active patient.'), beep = True)
680 time.sleep(0.5)
681 gmGuiHelpers.gm_show_info (
682 _('\nA new encounter was started for the active patient.\n'),
683 _('Start of new encounter')
684 )
685 #----------------------------------------------------------------
686 from Gnumed.wxGladeWidgets import wxgEncounterEditAreaDlg
687
689 if parent is None:
690 parent = wx.GetApp().GetTopWindow()
691
692 # FIXME: use generic dialog 2
693 dlg = cEncounterEditAreaDlg(parent = parent, encounter = encounter, msg = msg)
694 if dlg.ShowModal() == wx.ID_OK:
695 dlg.Destroy()
696 return True
697 dlg.Destroy()
698 return False
699 #----------------------------------------------------------------
701 return select_encounters(**kwargs)
702
703 -def select_encounters(parent=None, patient=None, single_selection=True, encounters=None, ignore_OK_button=False):
704
705 if patient is None:
706 patient = gmPerson.gmCurrentPatient()
707
708 if not patient.connected:
709 gmDispatcher.send(signal = 'statustext', msg = _('Cannot list encounters. No active patient.'))
710 return False
711
712 if parent is None:
713 parent = wx.GetApp().GetTopWindow()
714
715 emr = patient.get_emr()
716
717 #--------------------
718 def refresh(lctrl):
719 if encounters is None:
720 encs = emr.get_encounters()
721 else:
722 encs = encounters
723
724 items = [
725 [
726 gmDateTime.pydt_strftime(e['started'], '%Y %b %d %H:%M'),
727 e['last_affirmed'].strftime('%H:%M'),
728 e['l10n_type'],
729 gmTools.coalesce(e['reason_for_encounter'], u''),
730 gmTools.coalesce(e['assessment_of_encounter'], u''),
731 gmTools.bool2subst(e.has_clinical_data(), u'', gmTools.u_checkmark_thin),
732 e['pk_encounter']
733 ] for e in encs
734 ]
735 lctrl.set_string_items(items = items)
736 lctrl.set_data(data = encs)
737 active_pk = emr.active_encounter['pk_encounter']
738 for idx in range(len(encs)):
739 e = encs[idx]
740 if e['pk_encounter'] == active_pk:
741 lctrl.SetItemTextColour(idx, col=wx.NamedColour('RED'))
742 #--------------------
743 def new():
744 cfg_db = gmCfg.cCfgSQL()
745 # FIXME: look for MRU/MCU encounter type config here
746 enc_type = cfg_db.get2 (
747 option = u'encounter.default_type',
748 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
749 bias = u'user',
750 default = u'in surgery'
751 )
752 enc = gmEMRStructItems.create_encounter(fk_patient = patient.ID, enc_type = enc_type)
753 return edit_encounter(parent = parent, encounter = enc)
754 #--------------------
755 def edit(enc=None):
756 return edit_encounter(parent = parent, encounter = enc)
757 #--------------------
758 def edit_active(enc=None):
759 return edit_encounter(parent = parent, encounter = emr.active_encounter)
760 #--------------------
761 def start_new(enc=None):
762 start_new_encounter(emr = emr)
763 return True
764 #--------------------
765 return gmListWidgets.get_choices_from_list (
766 parent = parent,
767 msg = _("The patient's encounters.\n"),
768 caption = _('Encounters ...'),
769 columns = [_('Started'), _('Ended'), _('Type'), _('Reason for Encounter'), _('Assessment of Encounter'), _('Empty'), '#'],
770 can_return_empty = False,
771 single_selection = single_selection,
772 refresh_callback = refresh,
773 edit_callback = edit,
774 new_callback = new,
775 ignore_OK_button = ignore_OK_button,
776 left_extra_button = (_('Edit active'), _('Edit the active encounter'), edit_active),
777 middle_extra_button = (_('Start new'), _('Start new active encounter for the current patient.'), start_new)
778 )
779 #----------------------------------------------------------------
781 """This is used as the callback when the EMR detects that the
782 patient was here rather recently and wants to ask the
783 provider whether to continue the recent encounter.
784 """
785 if parent is None:
786 parent = wx.GetApp().GetTopWindow()
787
788 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
789 parent = None,
790 id = -1,
791 caption = caption,
792 question = msg,
793 button_defs = [
794 {'label': _('Continue'), 'tooltip': _('Continue the existing recent encounter.'), 'default': False},
795 {'label': _('Start new'), 'tooltip': _('Start a new encounter. The existing one will be closed.'), 'default': True}
796 ],
797 show_checkbox = False
798 )
799
800 result = dlg.ShowModal()
801 dlg.Destroy()
802
803 if result == wx.ID_YES:
804 return True
805
806 return False
807 #----------------------------------------------------------------
809
810 if parent is None:
811 parent = wx.GetApp().GetTopWindow()
812
813 #--------------------
814 def edit(enc_type=None):
815 return edit_encounter_type(parent = parent, encounter_type = enc_type)
816 #--------------------
817 def delete(enc_type=None):
818 if gmEMRStructItems.delete_encounter_type(description = enc_type['description']):
819 return True
820 gmDispatcher.send (
821 signal = u'statustext',
822 msg = _('Cannot delete encounter type [%s]. It is in use.') % enc_type['l10n_description'],
823 beep = True
824 )
825 return False
826 #--------------------
827 def refresh(lctrl):
828 enc_types = gmEMRStructItems.get_encounter_types()
829 lctrl.set_string_items(items = enc_types)
830 #--------------------
831 gmListWidgets.get_choices_from_list (
832 parent = parent,
833 msg = _('\nSelect the encounter type you want to edit !\n'),
834 caption = _('Managing encounter types ...'),
835 columns = [_('Local name'), _('Encounter type')],
836 single_selection = True,
837 edit_callback = edit,
838 new_callback = edit,
839 delete_callback = delete,
840 refresh_callback = refresh
841 )
842 #----------------------------------------------------------------
844 ea = cEncounterTypeEditAreaPnl(parent = parent, id = -1)
845 ea.data = encounter_type
846 ea.mode = gmTools.coalesce(encounter_type, 'new', 'edit')
847 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea)
848 dlg.SetTitle(gmTools.coalesce(encounter_type, _('Adding new encounter type'), _('Editing local encounter type name')))
849 if dlg.ShowModal() == wx.ID_OK:
850 return True
851 return False
852 #----------------------------------------------------------------
854
856 gmPhraseWheel.cPhraseWheel.__init__ (self, *args, **kwargs)
857
858 cmd = u"""
859 SELECT DISTINCT ON (list_label)
860 pk_encounter
861 AS data,
862 to_char(started, 'YYYY Mon DD (HH24:MI)') || ': ' || l10n_type || ' [#' || pk_encounter || ']'
863 AS list_label,
864 to_char(started, 'YYYY Mon DD') || ': ' || l10n_type
865 AS field_label
866 FROM
867 clin.v_pat_encounters
868 WHERE
869 (
870 to_char(started, 'YYYY-MM-DD') %(fragment_condition)s
871 OR
872 l10n_type %(fragment_condition)s
873 OR
874 type %(fragment_condition)s
875 ) %(ctxt_patient)s
876 ORDER BY
877 list_label
878 LIMIT
879 30
880 """
881 context = {'ctxt_patient': {
882 'where_part': u'AND pk_patient = %(patient)s',
883 'placeholder': u'patient'
884 }}
885
886 self.matcher = gmMatchProvider.cMatchProvider_SQL2(queries = [cmd], context = context)
887 self.matcher._SQL_data2match = u"""
888 SELECT
889 pk_encounter
890 AS data,
891 to_char(started, 'YYYY Mon DD (HH24:MI)') || ': ' || l10n_type
892 AS list_label,
893 to_char(started, 'YYYY Mon DD') || ': ' || l10n_type
894 AS field_label
895 FROM
896 clin.v_pat_encounters
897 WHERE
898 pk_encounter = %(pk)s
899 """
900 self.matcher.setThresholds(1, 3, 5)
901 #self.matcher.print_queries = True
902 self.selection_only = True
903 # outside code MUST bind this to a patient
904 self.set_context(context = 'patient', val = None)
905 #--------------------------------------------------------
907 val = u'%s: %s' % (
908 gmDateTime.pydt_strftime(instance['started'], '%Y %b %d'),
909 instance['l10n_type']
910 )
911 self.SetText(value = val, data = instance['pk_encounter'])
912 #------------------------------------------------------------
914 if self.GetData() is None:
915 return None
916 enc = gmEMRStructItems.cEncounter(aPK_obj = self._data.values()[0]['data'])
917 return enc.format (
918 with_docs = False,
919 with_tests = False,
920 with_vaccinations = False,
921 with_family_history = False
922 )
923 #----------------------------------------------------------------
925 """Phrasewheel to allow selection of encounter type.
926
927 - user input interpreted as encounter type in English or local language
928 - data returned is pk of corresponding encounter type or None
929 """
931
932 gmPhraseWheel.cPhraseWheel.__init__ (self, *args, **kwargs)
933
934 mp = gmMatchProvider.cMatchProvider_SQL2 (
935 queries = [
936 u"""
937 SELECT
938 data,
939 field_label,
940 list_label
941 FROM (
942 SELECT DISTINCT ON (data) *
943 FROM (
944 SELECT
945 pk AS data,
946 _(description) AS field_label,
947 case
948 when _(description) = description then _(description)
949 else _(description) || ' (' || description || ')'
950 end AS list_label
951 FROM
952 clin.encounter_type
953 WHERE
954 _(description) %(fragment_condition)s
955 OR
956 description %(fragment_condition)s
957 ) AS q_distinct_pk
958 ) AS q_ordered
959 ORDER BY
960 list_label
961 """ ]
962 )
963 mp.setThresholds(2, 4, 6)
964
965 self.matcher = mp
966 self.selection_only = True
967 self.picklist_delay = 50
968 #----------------------------------------------------------------
969 from Gnumed.wxGladeWidgets import wxgEncounterTypeEditAreaPnl
970
971 -class cEncounterTypeEditAreaPnl(wxgEncounterTypeEditAreaPnl.wxgEncounterTypeEditAreaPnl, gmEditArea.cGenericEditAreaMixin):
972
974
975 wxgEncounterTypeEditAreaPnl.wxgEncounterTypeEditAreaPnl.__init__(self, *args, **kwargs)
976 gmEditArea.cGenericEditAreaMixin.__init__(self)
977
978 # self.__register_interests()
979 #-------------------------------------------------------
980 # generic edit area API
981 #-------------------------------------------------------
983 if self.mode == 'edit':
984 if self._TCTRL_l10n_name.GetValue().strip() == u'':
985 self.display_tctrl_as_valid(tctrl = self._TCTRL_l10n_name, valid = False)
986 return False
987 self.display_tctrl_as_valid(tctrl = self._TCTRL_l10n_name, valid = True)
988 return True
989
990 no_errors = True
991
992 if self._TCTRL_l10n_name.GetValue().strip() == u'':
993 if self._TCTRL_name.GetValue().strip() == u'':
994 self.display_tctrl_as_valid(tctrl = self._TCTRL_l10n_name, valid = False)
995 no_errors = False
996 else:
997 self.display_tctrl_as_valid(tctrl = self._TCTRL_l10n_name, valid = True)
998 else:
999 self.display_tctrl_as_valid(tctrl = self._TCTRL_l10n_name, valid = True)
1000
1001 if self._TCTRL_name.GetValue().strip() == u'':
1002 if self._TCTRL_l10n_name.GetValue().strip() == u'':
1003 self.display_tctrl_as_valid(tctrl = self._TCTRL_name, valid = False)
1004 no_errors = False
1005 else:
1006 self.display_tctrl_as_valid(tctrl = self._TCTRL_name, valid = True)
1007 else:
1008 self.display_tctrl_as_valid(tctrl = self._TCTRL_name, valid = True)
1009
1010 return no_errors
1011 #-------------------------------------------------------
1013 enc_type = gmEMRStructItems.create_encounter_type (
1014 description = gmTools.none_if(self._TCTRL_name.GetValue().strip(), u''),
1015 l10n_description = gmTools.coalesce (
1016 gmTools.none_if(self._TCTRL_l10n_name.GetValue().strip(), u''),
1017 self._TCTRL_name.GetValue().strip()
1018 )
1019 )
1020 if enc_type is None:
1021 return False
1022 self.data = enc_type
1023 return True
1024 #-------------------------------------------------------
1026 enc_type = gmEMRStructItems.update_encounter_type (
1027 description = self._TCTRL_name.GetValue().strip(),
1028 l10n_description = self._TCTRL_l10n_name.GetValue().strip()
1029 )
1030 if enc_type is None:
1031 return False
1032 self.data = enc_type
1033 return True
1034 #-------------------------------------------------------
1036 self._TCTRL_l10n_name.SetValue(u'')
1037 self._TCTRL_name.SetValue(u'')
1038 self._TCTRL_name.Enable(True)
1039 #-------------------------------------------------------
1041 self._TCTRL_l10n_name.SetValue(self.data['l10n_description'])
1042 self._TCTRL_name.SetValue(self.data['description'])
1043 # disallow changing type on all encounters by editing system name
1044 self._TCTRL_name.Enable(False)
1045 #-------------------------------------------------------
1050 #-------------------------------------------------------
1051 # internal API
1052 #-------------------------------------------------------
1053 # def __register_interests(self):
1054 # return
1055 #----------------------------------------------------------------
1056 from Gnumed.wxGladeWidgets import wxgEncounterEditAreaPnl
1057
1059
1061 try:
1062 self.__encounter = kwargs['encounter']
1063 del kwargs['encounter']
1064 except KeyError:
1065 self.__encounter = None
1066
1067 try:
1068 msg = kwargs['msg']
1069 del kwargs['msg']
1070 except KeyError:
1071 msg = None
1072
1073 wxgEncounterEditAreaPnl.wxgEncounterEditAreaPnl.__init__(self, *args, **kwargs)
1074
1075 self.refresh(msg = msg)
1076 #--------------------------------------------------------
1077 # external API
1078 #--------------------------------------------------------
1080
1081 if msg is not None:
1082 self._LBL_instructions.SetLabel(msg)
1083
1084 if encounter is not None:
1085 self.__encounter = encounter
1086
1087 if self.__encounter is None:
1088 return True
1089
1090 # getting the patient via the encounter allows us to act
1091 # on any encounter regardless of the currently active patient
1092 pat = gmPerson.cPatient(aPK_obj = self.__encounter['pk_patient'])
1093 self._LBL_patient.SetLabel(pat.get_description_gender().strip())
1094 curr_pat = gmPerson.gmCurrentPatient()
1095 if curr_pat.connected:
1096 if curr_pat.ID == self.__encounter['pk_patient']:
1097 self._LBL_patient.SetForegroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOWTEXT))
1098 else:
1099 self._LBL_patient.SetForegroundColour('red')
1100
1101 self._PRW_encounter_type.SetText(self.__encounter['l10n_type'], data=self.__encounter['pk_type'])
1102
1103 fts = gmDateTime.cFuzzyTimestamp (
1104 timestamp = self.__encounter['started'],
1105 accuracy = gmDateTime.acc_minutes
1106 )
1107 self._PRW_start.SetText(fts.format_accurately(), data=fts)
1108
1109 fts = gmDateTime.cFuzzyTimestamp (
1110 timestamp = self.__encounter['last_affirmed'],
1111 accuracy = gmDateTime.acc_minutes
1112 )
1113 self._PRW_end.SetText(fts.format_accurately(), data=fts)
1114
1115 # RFE
1116 self._TCTRL_rfe.SetValue(gmTools.coalesce(self.__encounter['reason_for_encounter'], ''))
1117 val, data = self._PRW_rfe_codes.generic_linked_codes2item_dict(self.__encounter.generic_codes_rfe)
1118 self._PRW_rfe_codes.SetText(val, data)
1119
1120 # AOE
1121 self._TCTRL_aoe.SetValue(gmTools.coalesce(self.__encounter['assessment_of_encounter'], ''))
1122 val, data = self._PRW_aoe_codes.generic_linked_codes2item_dict(self.__encounter.generic_codes_aoe)
1123 self._PRW_aoe_codes.SetText(val, data)
1124
1125 # last affirmed
1126 if self.__encounter['last_affirmed'] == self.__encounter['started']:
1127 self._PRW_end.SetFocus()
1128 else:
1129 self._TCTRL_aoe.SetFocus()
1130
1131 return True
1132 #--------------------------------------------------------
1134
1135 if self._PRW_encounter_type.GetData() is None:
1136 self._PRW_encounter_type.SetBackgroundColour('pink')
1137 self._PRW_encounter_type.Refresh()
1138 self._PRW_encounter_type.SetFocus()
1139 return False
1140 self._PRW_encounter_type.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1141 self._PRW_encounter_type.Refresh()
1142
1143 # start
1144 if self._PRW_start.GetValue().strip() == u'':
1145 self._PRW_start.SetBackgroundColour('pink')
1146 self._PRW_start.Refresh()
1147 self._PRW_start.SetFocus()
1148 return False
1149 if not self._PRW_start.is_valid_timestamp(empty_is_valid = False):
1150 self._PRW_start.SetBackgroundColour('pink')
1151 self._PRW_start.Refresh()
1152 self._PRW_start.SetFocus()
1153 return False
1154 self._PRW_start.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1155 self._PRW_start.Refresh()
1156
1157 # last_affirmed
1158 # if self._PRW_end.GetValue().strip() == u'':
1159 # self._PRW_end.SetBackgroundColour('pink')
1160 # self._PRW_end.Refresh()
1161 # self._PRW_end.SetFocus()
1162 # return False
1163 if not self._PRW_end.is_valid_timestamp(empty_is_valid = False):
1164 self._PRW_end.SetBackgroundColour('pink')
1165 self._PRW_end.Refresh()
1166 self._PRW_end.SetFocus()
1167 return False
1168 self._PRW_end.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1169 self._PRW_end.Refresh()
1170
1171 return True
1172 #--------------------------------------------------------
1174 if not self.__is_valid_for_save():
1175 return False
1176
1177 self.__encounter['pk_type'] = self._PRW_encounter_type.GetData()
1178 self.__encounter['started'] = self._PRW_start.GetData().get_pydt()
1179 self.__encounter['last_affirmed'] = self._PRW_end.GetData().get_pydt()
1180 self.__encounter['reason_for_encounter'] = gmTools.none_if(self._TCTRL_rfe.GetValue().strip(), u'')
1181 self.__encounter['assessment_of_encounter'] = gmTools.none_if(self._TCTRL_aoe.GetValue().strip(), u'')
1182 self.__encounter.save_payload() # FIXME: error checking
1183
1184 self.__encounter.generic_codes_rfe = [ c['data'] for c in self._PRW_rfe_codes.GetData() ]
1185 self.__encounter.generic_codes_aoe = [ c['data'] for c in self._PRW_aoe_codes.GetData() ]
1186
1187 return True
1188 #----------------------------------------------------------------
1189 # FIXME: use generic dialog 2
1191
1193 encounter = kwargs['encounter']
1194 del kwargs['encounter']
1195
1196 try:
1197 button_defs = kwargs['button_defs']
1198 del kwargs['button_defs']
1199 except KeyError:
1200 button_defs = None
1201
1202 try:
1203 msg = kwargs['msg']
1204 del kwargs['msg']
1205 except KeyError:
1206 msg = None
1207
1208 wxgEncounterEditAreaDlg.wxgEncounterEditAreaDlg.__init__(self, *args, **kwargs)
1209 self.SetSize((450, 280))
1210 self.SetMinSize((450, 280))
1211
1212 if button_defs is not None:
1213 self._BTN_save.SetLabel(button_defs[0][0])
1214 self._BTN_save.SetToolTipString(button_defs[0][1])
1215 self._BTN_close.SetLabel(button_defs[1][0])
1216 self._BTN_close.SetToolTipString(button_defs[1][1])
1217 self.Refresh()
1218
1219 self._PNL_edit_area.refresh(encounter = encounter, msg = msg)
1220
1221 self.Fit()
1222 #--------------------------------------------------------
1229 #--------------------------------------------------------
1231 start = self._PRW_encounter_start.GetData()
1232 if start is None:
1233 return
1234 start = start.get_pydt()
1235
1236 end = self._PRW_encounter_end.GetData()
1237 if end is None:
1238 fts = gmDateTime.cFuzzyTimestamp (
1239 timestamp = start,
1240 accuracy = gmDateTime.acc_minutes
1241 )
1242 self._PRW_encounter_end.SetText(fts.format_accurately(), data = fts)
1243 return
1244 end = end.get_pydt()
1245
1246 if start > end:
1247 end = end.replace (
1248 year = start.year,
1249 month = start.month,
1250 day = start.day
1251 )
1252 fts = gmDateTime.cFuzzyTimestamp (
1253 timestamp = end,
1254 accuracy = gmDateTime.acc_minutes
1255 )
1256 self._PRW_encounter_end.SetText(fts.format_accurately(), data = fts)
1257 return
1258
1259 emr = self.__pat.get_emr()
1260 if start != emr.active_encounter['started']:
1261 end = end.replace (
1262 year = start.year,
1263 month = start.month,
1264 day = start.day
1265 )
1266 fts = gmDateTime.cFuzzyTimestamp (
1267 timestamp = end,
1268 accuracy = gmDateTime.acc_minutes
1269 )
1270 self._PRW_encounter_end.SetText(fts.format_accurately(), data = fts)
1271 return
1272
1273 return
1274
1275 #----------------------------------------------------------------
1276 from Gnumed.wxGladeWidgets import wxgActiveEncounterPnl
1277
1279
1281 wxgActiveEncounterPnl.wxgActiveEncounterPnl.__init__(self, *args, **kwargs)
1282 self.__register_events()
1283 self.refresh()
1284 #------------------------------------------------------------
1286 self._TCTRL_encounter.SetValue(u'')
1287 self._TCTRL_encounter.SetToolTipString(u'')
1288 self._BTN_new.Enable(False)
1289 self._BTN_list.Enable(False)
1290 #------------------------------------------------------------
1292 pat = gmPerson.gmCurrentPatient()
1293 if not pat.connected:
1294 self.clear()
1295 return
1296
1297 enc = pat.get_emr().active_encounter
1298 self._TCTRL_encounter.SetValue(enc.format(with_docs = False, with_tests = False, fancy_header = False, with_vaccinations = False, with_family_history = False).strip('\n'))
1299 self._TCTRL_encounter.SetToolTipString (
1300 _('The active encounter of the current patient:\n\n%s') %
1301 enc.format(with_docs = False, with_tests = False, fancy_header = True, with_vaccinations = False, with_rfe_aoe = True, with_family_history = False).strip('\n')
1302 )
1303 self._BTN_new.Enable(True)
1304 self._BTN_list.Enable(True)
1305 #------------------------------------------------------------
1307 self._TCTRL_encounter.Bind(wx.EVT_LEFT_DCLICK, self._on_ldclick)
1308
1309 gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._schedule_clear)
1310 # this would throw an exception due to concurrency issues:
1311 #gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._schedule_refresh)
1312 gmDispatcher.connect(signal = u'episode_mod_db', receiver = self._schedule_refresh)
1313 gmDispatcher.connect(signal = u'current_encounter_modified', receiver = self._schedule_refresh)
1314 gmDispatcher.connect(signal = u'current_encounter_switched', receiver = self._schedule_refresh)
1315 #------------------------------------------------------------
1316 # event handler
1317 #------------------------------------------------------------
1319 wx.CallAfter(self.clear)
1320 #------------------------------------------------------------
1324 #------------------------------------------------------------
1326 pat = gmPerson.gmCurrentPatient()
1327 if not pat.connected:
1328 return
1329 edit_encounter(encounter = pat.get_emr().active_encounter)
1330 #------------------------------------------------------------
1336 #------------------------------------------------------------
1341 #================================================================
1342 # episode related widgets/functions
1343 #----------------------------------------------------------------
1345 ea = cEpisodeEditAreaPnl(parent = parent, id = -1)
1346 ea.data = episode
1347 ea.mode = gmTools.coalesce(episode, 'new', 'edit')
1348 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = True)
1349 dlg.SetTitle(gmTools.coalesce(episode, _('Adding a new episode'), _('Editing an episode')))
1350 if dlg.ShowModal() == wx.ID_OK:
1351 return True
1352 return False
1353 #----------------------------------------------------------------
1355
1356 created_new_issue = False
1357
1358 try:
1359 issue = gmEMRStructItems.cHealthIssue(name = episode['description'], patient = episode['pk_patient'])
1360 except gmExceptions.NoSuchBusinessObjectError:
1361 issue = None
1362
1363 if issue is None:
1364 issue = emr.add_health_issue(issue_name = episode['description'])
1365 created_new_issue = True
1366 else:
1367 # issue exists already, so ask user
1368 dlg = gmGuiHelpers.c3ButtonQuestionDlg (
1369 parent,
1370 -1,
1371 caption = _('Promoting episode to health issue'),
1372 question = _(
1373 'There already is a health issue\n'
1374 '\n'
1375 ' %s\n'
1376 '\n'
1377 'What do you want to do ?'
1378 ) % issue['description'],
1379 button_defs = [
1380 {'label': _('Use existing'), 'tooltip': _('Move episode into existing health issue'), 'default': False},
1381 {'label': _('Create new'), 'tooltip': _('Create a new health issue with another name'), 'default': True}
1382 ]
1383 )
1384 use_existing = dlg.ShowModal()
1385 dlg.Destroy()
1386
1387 if use_existing == wx.ID_CANCEL:
1388 return
1389
1390 # user wants to create new issue with alternate name
1391 if use_existing == wx.ID_NO:
1392 # loop until name modified but non-empty or cancelled
1393 issue_name = episode['description']
1394 while issue_name == episode['description']:
1395 dlg = wx.TextEntryDialog (
1396 parent = parent,
1397 message = _('Enter a short descriptive name for the new health issue:'),
1398 caption = _('Creating a new health issue ...'),
1399 defaultValue = issue_name,
1400 style = wx.OK | wx.CANCEL | wx.CENTRE
1401 )
1402 decision = dlg.ShowModal()
1403 if decision != wx.ID_OK:
1404 dlg.Destroy()
1405 return
1406 issue_name = dlg.GetValue().strip()
1407 dlg.Destroy()
1408 if issue_name == u'':
1409 issue_name = episode['description']
1410
1411 issue = emr.add_health_issue(issue_name = issue_name)
1412 created_new_issue = True
1413
1414 # eventually move the episode to the issue
1415 if not move_episode_to_issue(episode = episode, target_issue = issue, save_to_backend = True):
1416 # user cancelled the move so delete just-created issue
1417 if created_new_issue:
1418 # shouldn't fail as it is completely new
1419 gmEMRStructItems.delete_health_issue(health_issue = issue)
1420 return
1421
1422 return
1423 #----------------------------------------------------------------
1425 """Prepare changing health issue for an episode.
1426
1427 Checks for two-open-episodes conflict. When this
1428 function succeeds, the pk_health_issue has been set
1429 on the episode instance and the episode should - for
1430 all practical purposes - be ready for save_payload().
1431 """
1432 # episode is closed: should always work
1433 if not episode['episode_open']:
1434 episode['pk_health_issue'] = target_issue['pk_health_issue']
1435 if save_to_backend:
1436 episode.save_payload()
1437 return True
1438
1439 # un-associate: should always work, too
1440 if target_issue is None:
1441 episode['pk_health_issue'] = None
1442 if save_to_backend:
1443 episode.save_payload()
1444 return True
1445
1446 # try closing possibly expired episode on target issue if any
1447 db_cfg = gmCfg.cCfgSQL()
1448 epi_ttl = int(db_cfg.get2 (
1449 option = u'episode.ttl',
1450 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
1451 bias = 'user',
1452 default = 60 # 2 months
1453 ))
1454 if target_issue.close_expired_episode(ttl=epi_ttl) is True:
1455 gmDispatcher.send(signal='statustext', msg=_('Closed episodes older than %s days on health issue [%s]') % (epi_ttl, target_issue['description']))
1456 existing_epi = target_issue.get_open_episode()
1457
1458 # no more open episode on target issue: should work now
1459 if existing_epi is None:
1460 episode['pk_health_issue'] = target_issue['pk_health_issue']
1461 if save_to_backend:
1462 episode.save_payload()
1463 return True
1464
1465 # don't conflict on SELF ;-)
1466 if existing_epi['pk_episode'] == episode['pk_episode']:
1467 episode['pk_health_issue'] = target_issue['pk_health_issue']
1468 if save_to_backend:
1469 episode.save_payload()
1470 return True
1471
1472 # we got two open episodes at once, ask user
1473 move_range = episode.get_access_range()
1474 exist_range = existing_epi.get_access_range()
1475 question = _(
1476 'You want to associate the running episode:\n\n'
1477 ' "%(new_epi_name)s" (%(new_epi_start)s - %(new_epi_end)s)\n\n'
1478 'with the health issue:\n\n'
1479 ' "%(issue_name)s"\n\n'
1480 'There already is another episode running\n'
1481 'for this health issue:\n\n'
1482 ' "%(old_epi_name)s" (%(old_epi_start)s - %(old_epi_end)s)\n\n'
1483 'However, there can only be one running\n'
1484 'episode per health issue.\n\n'
1485 'Which episode do you want to close ?'
1486 ) % {
1487 'new_epi_name': episode['description'],
1488 'new_epi_start': move_range[0].strftime('%m/%y'),
1489 'new_epi_end': move_range[1].strftime('%m/%y'),
1490 'issue_name': target_issue['description'],
1491 'old_epi_name': existing_epi['description'],
1492 'old_epi_start': exist_range[0].strftime('%m/%y'),
1493 'old_epi_end': exist_range[1].strftime('%m/%y')
1494 }
1495 dlg = gmGuiHelpers.c3ButtonQuestionDlg (
1496 parent = None,
1497 id = -1,
1498 caption = _('Resolving two-running-episodes conflict'),
1499 question = question,
1500 button_defs = [
1501 {'label': _('old episode'), 'default': True, 'tooltip': _('close existing episode "%s"') % existing_epi['description']},
1502 {'label': _('new episode'), 'default': False, 'tooltip': _('close moving (new) episode "%s"') % episode['description']}
1503 ]
1504 )
1505 decision = dlg.ShowModal()
1506
1507 if decision == wx.ID_CANCEL:
1508 # button 3: move cancelled by user
1509 return False
1510
1511 elif decision == wx.ID_YES:
1512 # button 1: close old episode
1513 existing_epi['episode_open'] = False
1514 existing_epi.save_payload()
1515
1516 elif decision == wx.ID_NO:
1517 # button 2: close new episode
1518 episode['episode_open'] = False
1519
1520 else:
1521 raise ValueError('invalid result from c3ButtonQuestionDlg: [%s]' % decision)
1522
1523 episode['pk_health_issue'] = target_issue['pk_health_issue']
1524 if save_to_backend:
1525 episode.save_payload()
1526 return True
1527 #----------------------------------------------------------------
1529
1530 # FIXME: support pre-selection
1531
1533
1534 episodes = kwargs['episodes']
1535 del kwargs['episodes']
1536
1537 gmListWidgets.cGenericListSelectorDlg.__init__(self, *args, **kwargs)
1538
1539 self.SetTitle(_('Select the episodes you are interested in ...'))
1540 self._LCTRL_items.set_columns([_('Episode'), _('Status'), _('Health Issue')])
1541 self._LCTRL_items.set_string_items (
1542 items = [
1543 [ epi['description'],
1544 gmTools.bool2str(epi['episode_open'], _('ongoing'), u''),
1545 gmTools.coalesce(epi['health_issue'], u'')
1546 ]
1547 for epi in episodes ]
1548 )
1549 self._LCTRL_items.set_column_widths()
1550 self._LCTRL_items.set_data(data = episodes)
1551 #----------------------------------------------------------------
1553 """Let user select an episode *description*.
1554
1555 The user can select an episode description from the previously
1556 used descriptions across all episodes across all patients.
1557
1558 Selection is done with a phrasewheel so the user can
1559 type the episode name and matches will be shown. Typing
1560 "*" will show the entire list of episodes.
1561
1562 If the user types a description not existing yet a
1563 new episode description will be returned.
1564 """
1566
1567 mp = gmMatchProvider.cMatchProvider_SQL2 (
1568 queries = [
1569 u"""
1570 SELECT DISTINCT ON (description)
1571 description
1572 AS data,
1573 description
1574 AS field_label,
1575 description || ' ('
1576 || CASE
1577 WHEN is_open IS TRUE THEN _('ongoing')
1578 ELSE _('closed')
1579 END
1580 || ')'
1581 AS list_label
1582 FROM
1583 clin.episode
1584 WHERE
1585 description %(fragment_condition)s
1586 ORDER BY description
1587 LIMIT 30
1588 """
1589 ]
1590 )
1591 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
1592 self.matcher = mp
1593 #----------------------------------------------------------------
1595 """Let user select an episode.
1596
1597 The user can select an episode from the existing episodes of a
1598 patient. Selection is done with a phrasewheel so the user
1599 can type the episode name and matches will be shown. Typing
1600 "*" will show the entire list of episodes. Closed episodes
1601 will be marked as such. If the user types an episode name not
1602 in the list of existing episodes a new episode can be created
1603 from it if the programmer activated that feature.
1604
1605 If keyword <patient_id> is set to None or left out the control
1606 will listen to patient change signals and therefore act on
1607 gmPerson.gmCurrentPatient() changes.
1608 """
1610
1611 ctxt = {'ctxt_pat': {'where_part': u'and pk_patient = %(pat)s', 'placeholder': u'pat'}}
1612
1613 mp = gmMatchProvider.cMatchProvider_SQL2 (
1614 queries = [
1615 u"""(
1616
1617 select
1618 pk_episode
1619 as data,
1620 description
1621 as field_label,
1622 coalesce (
1623 description || ' - ' || health_issue,
1624 description
1625 ) as list_label,
1626 1 as rank
1627 from
1628 clin.v_pat_episodes
1629 where
1630 episode_open is true and
1631 description %(fragment_condition)s
1632 %(ctxt_pat)s
1633
1634 ) union all (
1635
1636 select
1637 pk_episode
1638 as data,
1639 description
1640 as field_label,
1641 coalesce (
1642 description || _(' (closed)') || ' - ' || health_issue,
1643 description || _(' (closed)')
1644 ) as list_label,
1645 2 as rank
1646 from
1647 clin.v_pat_episodes
1648 where
1649 description %(fragment_condition)s and
1650 episode_open is false
1651 %(ctxt_pat)s
1652
1653 )
1654
1655 order by rank, list_label
1656 limit 30"""
1657 ],
1658 context = ctxt
1659 )
1660
1661 try:
1662 kwargs['patient_id']
1663 except KeyError:
1664 kwargs['patient_id'] = None
1665
1666 if kwargs['patient_id'] is None:
1667 self.use_current_patient = True
1668 self.__register_patient_change_signals()
1669 pat = gmPerson.gmCurrentPatient()
1670 if pat.connected:
1671 mp.set_context('pat', pat.ID)
1672 else:
1673 self.use_current_patient = False
1674 self.__patient_id = int(kwargs['patient_id'])
1675 mp.set_context('pat', self.__patient_id)
1676
1677 del kwargs['patient_id']
1678
1679 gmPhraseWheel.cPhraseWheel.__init__ (
1680 self,
1681 *args,
1682 **kwargs
1683 )
1684 self.matcher = mp
1685 #--------------------------------------------------------
1686 # external API
1687 #--------------------------------------------------------
1689 if self.use_current_patient:
1690 return False
1691 self.__patient_id = int(patient_id)
1692 self.set_context('pat', self.__patient_id)
1693 return True
1694 #--------------------------------------------------------
1696 self.__is_open_for_create_data = is_open # used (only) in _create_data()
1697 return gmPhraseWheel.cPhraseWheel.GetData(self, can_create = can_create, as_instance = as_instance)
1698 #--------------------------------------------------------
1700
1701 epi_name = self.GetValue().strip()
1702 if epi_name == u'':
1703 gmDispatcher.send(signal = u'statustext', msg = _('Cannot create episode without name.'), beep = True)
1704 _log.debug('cannot create episode without name')
1705 return
1706
1707 if self.use_current_patient:
1708 pat = gmPerson.gmCurrentPatient()
1709 else:
1710 pat = gmPerson.cPatient(aPK_obj = self.__patient_id)
1711
1712 emr = pat.get_emr()
1713 epi = emr.add_episode(episode_name = epi_name, is_open = self.__is_open_for_create_data)
1714 if epi is None:
1715 self.data = {}
1716 else:
1717 self.SetText (
1718 value = epi_name,
1719 data = epi['pk_episode']
1720 )
1721 #--------------------------------------------------------
1724 #--------------------------------------------------------
1725 # internal API
1726 #--------------------------------------------------------
1728 gmDispatcher.connect(self._pre_patient_selection, u'pre_patient_selection')
1729 gmDispatcher.connect(self._post_patient_selection, u'post_patient_selection')
1730 #--------------------------------------------------------
1733 #--------------------------------------------------------
1735 if self.use_current_patient:
1736 patient = gmPerson.gmCurrentPatient()
1737 self.set_context('pat', patient.ID)
1738 return True
1739 #----------------------------------------------------------------
1740 from Gnumed.wxGladeWidgets import wxgEpisodeEditAreaPnl
1741
1742 -class cEpisodeEditAreaPnl(gmEditArea.cGenericEditAreaMixin, wxgEpisodeEditAreaPnl.wxgEpisodeEditAreaPnl):
1743
1745
1746 try:
1747 episode = kwargs['episode']
1748 del kwargs['episode']
1749 except KeyError:
1750 episode = None
1751
1752 wxgEpisodeEditAreaPnl.wxgEpisodeEditAreaPnl.__init__(self, *args, **kwargs)
1753 gmEditArea.cGenericEditAreaMixin.__init__(self)
1754
1755 self.data = episode
1756 #----------------------------------------------------------------
1757 # generic Edit Area mixin API
1758 #----------------------------------------------------------------
1760
1761 errors = False
1762
1763 if len(self._PRW_description.GetValue().strip()) == 0:
1764 errors = True
1765 self._PRW_description.display_as_valid(False)
1766 self._PRW_description.SetFocus()
1767 else:
1768 self._PRW_description.display_as_valid(True)
1769 self._PRW_description.Refresh()
1770
1771 return not errors
1772 #----------------------------------------------------------------
1774
1775 pat = gmPerson.gmCurrentPatient()
1776 emr = pat.get_emr()
1777
1778 epi = emr.add_episode(episode_name = self._PRW_description.GetValue().strip())
1779 epi['summary'] = self._TCTRL_status.GetValue().strip()
1780 epi['episode_open'] = not self._CHBOX_closed.IsChecked()
1781 epi['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
1782
1783 issue_name = self._PRW_issue.GetValue().strip()
1784 if len(issue_name) != 0:
1785 epi['pk_health_issue'] = self._PRW_issue.GetData(can_create = True)
1786 issue = gmEMRStructItems.cHealthIssue(aPK_obj = epi['pk_health_issue'])
1787
1788 if not move_episode_to_issue(episode = epi, target_issue = issue, save_to_backend = False):
1789 gmDispatcher.send (
1790 signal = 'statustext',
1791 msg = _('Cannot attach episode [%s] to health issue [%s] because it already has a running episode.') % (
1792 epi['description'],
1793 issue['description']
1794 )
1795 )
1796 gmEMRStructItems.delete_episode(episode = epi)
1797 return False
1798
1799 epi.save()
1800
1801 epi.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
1802
1803 self.data = epi
1804 return True
1805 #----------------------------------------------------------------
1807
1808 self.data['description'] = self._PRW_description.GetValue().strip()
1809 self.data['summary'] = self._TCTRL_status.GetValue().strip()
1810 self.data['episode_open'] = not self._CHBOX_closed.IsChecked()
1811 self.data['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
1812
1813 issue_name = self._PRW_issue.GetValue().strip()
1814 if len(issue_name) == 0:
1815 self.data['pk_health_issue'] = None
1816 else:
1817 self.data['pk_health_issue'] = self._PRW_issue.GetData(can_create = True)
1818 issue = gmEMRStructItems.cHealthIssue(aPK_obj = self.data['pk_health_issue'])
1819
1820 if not move_episode_to_issue(episode = self.data, target_issue = issue, save_to_backend = False):
1821 gmDispatcher.send (
1822 signal = 'statustext',
1823 msg = _('Cannot attach episode [%s] to health issue [%s] because it already has a running episode.') % (
1824 self.data['description'],
1825 issue['description']
1826 )
1827 )
1828 return False
1829
1830 self.data.save()
1831 self.data.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
1832
1833 return True
1834 #----------------------------------------------------------------
1836 if self.data is None:
1837 ident = gmPerson.gmCurrentPatient()
1838 else:
1839 ident = gmPerson.cIdentity(aPK_obj = self.data['pk_patient'])
1840 self._TCTRL_patient.SetValue(ident.get_description_gender())
1841 self._PRW_issue.SetText()
1842 self._PRW_description.SetText()
1843 self._TCTRL_status.SetValue(u'')
1844 self._PRW_certainty.SetText()
1845 self._CHBOX_closed.SetValue(False)
1846 self._PRW_codes.SetText()
1847 #----------------------------------------------------------------
1849 ident = gmPerson.cIdentity(aPK_obj = self.data['pk_patient'])
1850 self._TCTRL_patient.SetValue(ident.get_description_gender())
1851
1852 if self.data['pk_health_issue'] is not None:
1853 self._PRW_issue.SetText(self.data['health_issue'], data=self.data['pk_health_issue'])
1854
1855 self._PRW_description.SetText(self.data['description'], data=self.data['description'])
1856
1857 self._TCTRL_status.SetValue(gmTools.coalesce(self.data['summary'], u''))
1858
1859 if self.data['diagnostic_certainty_classification'] is not None:
1860 self._PRW_certainty.SetData(data = self.data['diagnostic_certainty_classification'])
1861
1862 self._CHBOX_closed.SetValue(not self.data['episode_open'])
1863
1864 val, data = self._PRW_codes.generic_linked_codes2item_dict(self.data.generic_codes)
1865 self._PRW_codes.SetText(val, data)
1866 #----------------------------------------------------------------
1869 #================================================================
1870 # health issue related widgets/functions
1871 #----------------------------------------------------------------
1873 ea = cHealthIssueEditAreaPnl(parent = parent, id = -1)
1874 ea.data = issue
1875 ea.mode = gmTools.coalesce(issue, 'new', 'edit')
1876 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = (issue is not None))
1877 dlg.SetTitle(gmTools.coalesce(issue, _('Adding a new health issue'), _('Editing a health issue')))
1878 if dlg.ShowModal() == wx.ID_OK:
1879 return True
1880 return False
1881 #----------------------------------------------------------------
1883
1884 if parent is None:
1885 parent = wx.GetApp().GetTopWindow()
1886 #-----------------------------------------
1887 def refresh(lctrl):
1888 issues = emr.get_health_issues()
1889 items = [
1890 [
1891 gmTools.bool2subst(i['is_confidential'], _('CONFIDENTIAL'), u'', u''),
1892 i['description'],
1893 gmTools.bool2subst(i['clinically_relevant'], _('relevant'), u'', u''),
1894 gmTools.bool2subst(i['is_active'], _('active'), u'', u''),
1895 gmTools.bool2subst(i['is_cause_of_death'], _('fatal'), u'', u'')
1896 ] for i in issues
1897 ]
1898 lctrl.set_string_items(items = items)
1899 lctrl.set_data(data = issues)
1900 #-----------------------------------------
1901 return gmListWidgets.get_choices_from_list (
1902 parent = parent,
1903 msg = _('\nSelect the health issues !\n'),
1904 caption = _('Showing health issues ...'),
1905 columns = [u'', _('Health issue'), u'', u'', u''],
1906 single_selection = False,
1907 #edit_callback = edit,
1908 #new_callback = edit,
1909 #delete_callback = delete,
1910 refresh_callback = refresh
1911 )
1912 #----------------------------------------------------------------
1914
1915 # FIXME: support pre-selection
1916
1918
1919 issues = kwargs['issues']
1920 del kwargs['issues']
1921
1922 gmListWidgets.cGenericListSelectorDlg.__init__(self, *args, **kwargs)
1923
1924 self.SetTitle(_('Select the health issues you are interested in ...'))
1925 self._LCTRL_items.set_columns([u'', _('Health Issue'), u'', u'', u''])
1926
1927 for issue in issues:
1928 if issue['is_confidential']:
1929 row_num = self._LCTRL_items.InsertStringItem(sys.maxint, label = _('confidential'))
1930 self._LCTRL_items.SetItemTextColour(row_num, col=wx.NamedColour('RED'))
1931 else:
1932 row_num = self._LCTRL_items.InsertStringItem(sys.maxint, label = u'')
1933
1934 self._LCTRL_items.SetStringItem(index = row_num, col = 1, label = issue['description'])
1935 if issue['clinically_relevant']:
1936 self._LCTRL_items.SetStringItem(index = row_num, col = 2, label = _('relevant'))
1937 if issue['is_active']:
1938 self._LCTRL_items.SetStringItem(index = row_num, col = 3, label = _('active'))
1939 if issue['is_cause_of_death']:
1940 self._LCTRL_items.SetStringItem(index = row_num, col = 4, label = _('fatal'))
1941
1942 self._LCTRL_items.set_column_widths()
1943 self._LCTRL_items.set_data(data = issues)
1944 #----------------------------------------------------------------
1946 """Let the user select a health issue.
1947
1948 The user can select a health issue from the existing issues
1949 of a patient. Selection is done with a phrasewheel so the user
1950 can type the issue name and matches will be shown. Typing
1951 "*" will show the entire list of issues. Inactive issues
1952 will be marked as such. If the user types an issue name not
1953 in the list of existing issues a new issue can be created
1954 from it if the programmer activated that feature.
1955
1956 If keyword <patient_id> is set to None or left out the control
1957 will listen to patient change signals and therefore act on
1958 gmPerson.gmCurrentPatient() changes.
1959 """
1961
1962 ctxt = {'ctxt_pat': {'where_part': u'pk_patient=%(pat)s', 'placeholder': u'pat'}}
1963
1964 mp = gmMatchProvider.cMatchProvider_SQL2 (
1965 # FIXME: consider clin.health_issue.clinically_relevant
1966 queries = [
1967 u"""
1968 SELECT
1969 data,
1970 field_label,
1971 list_label
1972 FROM ((
1973 SELECT
1974 pk_health_issue AS data,
1975 description AS field_label,
1976 description AS list_label
1977 FROM clin.v_health_issues
1978 WHERE
1979 is_active IS true
1980 AND
1981 description %(fragment_condition)s
1982 AND
1983 %(ctxt_pat)s
1984
1985 ) UNION (
1986
1987 SELECT
1988 pk_health_issue AS data,
1989 description AS field_label,
1990 description || _(' (inactive)') AS list_label
1991 FROM clin.v_health_issues
1992 WHERE
1993 is_active IS false
1994 AND
1995 description %(fragment_condition)s
1996 AND
1997 %(ctxt_pat)s
1998 )) AS union_query
1999 ORDER BY
2000 list_label"""],
2001 context = ctxt
2002 )
2003
2004 try: kwargs['patient_id']
2005 except KeyError: kwargs['patient_id'] = None
2006
2007 if kwargs['patient_id'] is None:
2008 self.use_current_patient = True
2009 self.__register_patient_change_signals()
2010 pat = gmPerson.gmCurrentPatient()
2011 if pat.connected:
2012 mp.set_context('pat', pat.ID)
2013 else:
2014 self.use_current_patient = False
2015 self.__patient_id = int(kwargs['patient_id'])
2016 mp.set_context('pat', self.__patient_id)
2017
2018 del kwargs['patient_id']
2019
2020 gmPhraseWheel.cPhraseWheel.__init__ (
2021 self,
2022 *args,
2023 **kwargs
2024 )
2025 self.matcher = mp
2026 #--------------------------------------------------------
2027 # external API
2028 #--------------------------------------------------------
2030 if self.use_current_patient:
2031 return False
2032 self.__patient_id = int(patient_id)
2033 self.set_context('pat', self.__patient_id)
2034 return True
2035 #--------------------------------------------------------
2037 issue_name = self.GetValue().strip()
2038 if issue_name == u'':
2039 gmDispatcher.send(signal = u'statustext', msg = _('Cannot create health issue without name.'), beep = True)
2040 _log.debug('cannot create health issue without name')
2041 return
2042
2043 if self.use_current_patient:
2044 pat = gmPerson.gmCurrentPatient()
2045 else:
2046 pat = gmPerson.cPatient(aPK_obj = self.__patient_id)
2047
2048 emr = pat.get_emr()
2049 issue = emr.add_health_issue(issue_name = issue_name)
2050
2051 if issue is None:
2052 self.data = {}
2053 else:
2054 self.SetText (
2055 value = issue_name,
2056 data = issue['pk_health_issue']
2057 )
2058 #--------------------------------------------------------
2061 #--------------------------------------------------------
2062 # internal API
2063 #--------------------------------------------------------
2065 gmDispatcher.connect(self._pre_patient_selection, u'pre_patient_selection')
2066 gmDispatcher.connect(self._post_patient_selection, u'post_patient_selection')
2067 #--------------------------------------------------------
2070 #--------------------------------------------------------
2072 if self.use_current_patient:
2073 patient = gmPerson.gmCurrentPatient()
2074 self.set_context('pat', patient.ID)
2075 return True
2076 #------------------------------------------------------------
2077 from Gnumed.wxGladeWidgets import wxgIssueSelectionDlg
2078
2080
2082 try:
2083 msg = kwargs['message']
2084 except KeyError:
2085 msg = None
2086 del kwargs['message']
2087 wxgIssueSelectionDlg.wxgIssueSelectionDlg.__init__(self, *args, **kwargs)
2088 if msg is not None:
2089 self._lbl_message.SetLabel(label=msg)
2090 #--------------------------------------------------------
2101 #------------------------------------------------------------
2102 from Gnumed.wxGladeWidgets import wxgHealthIssueEditAreaPnl
2103
2104 -class cHealthIssueEditAreaPnl(gmEditArea.cGenericEditAreaMixin, wxgHealthIssueEditAreaPnl.wxgHealthIssueEditAreaPnl):
2105 """Panel encapsulating health issue edit area functionality."""
2106
2108
2109 try:
2110 issue = kwargs['issue']
2111 except KeyError:
2112 issue = None
2113
2114 wxgHealthIssueEditAreaPnl.wxgHealthIssueEditAreaPnl.__init__(self, *args, **kwargs)
2115
2116 gmEditArea.cGenericEditAreaMixin.__init__(self)
2117
2118 # FIXME: include more sources: coding systems/other database columns
2119 mp = gmMatchProvider.cMatchProvider_SQL2 (
2120 queries = [u"SELECT DISTINCT ON (description) description, description FROM clin.health_issue WHERE description %(fragment_condition)s LIMIT 50"]
2121 )
2122 mp.setThresholds(1, 3, 5)
2123 self._PRW_condition.matcher = mp
2124
2125 mp = gmMatchProvider.cMatchProvider_SQL2 (
2126 queries = [u"""
2127 select distinct on (grouping) grouping, grouping from (
2128
2129 select rank, grouping from ((
2130
2131 select
2132 grouping,
2133 1 as rank
2134 from
2135 clin.health_issue
2136 where
2137 grouping %%(fragment_condition)s
2138 and
2139 (select True from clin.encounter where fk_patient = %s and pk = clin.health_issue.fk_encounter)
2140
2141 ) union (
2142
2143 select
2144 grouping,
2145 2 as rank
2146 from
2147 clin.health_issue
2148 where
2149 grouping %%(fragment_condition)s
2150
2151 )) as union_result
2152
2153 order by rank
2154
2155 ) as order_result
2156
2157 limit 50""" % gmPerson.gmCurrentPatient().ID
2158 ]
2159 )
2160 mp.setThresholds(1, 3, 5)
2161 self._PRW_grouping.matcher = mp
2162
2163 self._PRW_age_noted.add_callback_on_lose_focus(self._on_leave_age_noted)
2164 self._PRW_year_noted.add_callback_on_lose_focus(self._on_leave_year_noted)
2165
2166 self._PRW_age_noted.add_callback_on_modified(self._on_modified_age_noted)
2167 self._PRW_year_noted.add_callback_on_modified(self._on_modified_year_noted)
2168
2169 self._PRW_year_noted.Enable(True)
2170
2171 self._PRW_codes.add_callback_on_lose_focus(self._on_leave_codes)
2172
2173 self.data = issue
2174 #----------------------------------------------------------------
2175 # generic Edit Area mixin API
2176 #----------------------------------------------------------------
2178
2179 if self._PRW_condition.GetValue().strip() == '':
2180 self._PRW_condition.display_as_valid(False)
2181 self._PRW_condition.SetFocus()
2182 return False
2183 self._PRW_condition.display_as_valid(True)
2184 self._PRW_condition.Refresh()
2185
2186 # FIXME: sanity check age/year diagnosed
2187 age_noted = self._PRW_age_noted.GetValue().strip()
2188 if age_noted != '':
2189 if gmDateTime.str2interval(str_interval = age_noted) is None:
2190 self._PRW_age_noted.display_as_valid(False)
2191 self._PRW_age_noted.SetFocus()
2192 return False
2193 self._PRW_age_noted.display_as_valid(True)
2194
2195 return True
2196 #----------------------------------------------------------------
2198 pat = gmPerson.gmCurrentPatient()
2199 emr = pat.get_emr()
2200
2201 issue = emr.add_health_issue(issue_name = self._PRW_condition.GetValue().strip())
2202
2203 side = u''
2204 if self._ChBOX_left.GetValue():
2205 side += u's'
2206 if self._ChBOX_right.GetValue():
2207 side += u'd'
2208 issue['laterality'] = side
2209
2210 issue['summary'] = self._TCTRL_status.GetValue().strip()
2211 issue['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
2212 issue['grouping'] = self._PRW_grouping.GetValue().strip()
2213 issue['is_active'] = self._ChBOX_active.GetValue()
2214 issue['clinically_relevant'] = self._ChBOX_relevant.GetValue()
2215 issue['is_confidential'] = self._ChBOX_confidential.GetValue()
2216 issue['is_cause_of_death'] = self._ChBOX_caused_death.GetValue()
2217
2218 age_noted = self._PRW_age_noted.GetData()
2219 if age_noted is not None:
2220 issue['age_noted'] = age_noted
2221
2222 issue.save()
2223
2224 issue.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
2225
2226 self.data = issue
2227 return True
2228 #----------------------------------------------------------------
2230
2231 self.data['description'] = self._PRW_condition.GetValue().strip()
2232
2233 side = u''
2234 if self._ChBOX_left.GetValue():
2235 side += u's'
2236 if self._ChBOX_right.GetValue():
2237 side += u'd'
2238 self.data['laterality'] = side
2239
2240 self.data['summary'] = self._TCTRL_status.GetValue().strip()
2241 self.data['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
2242 self.data['grouping'] = self._PRW_grouping.GetValue().strip()
2243 self.data['is_active'] = bool(self._ChBOX_active.GetValue())
2244 self.data['clinically_relevant'] = bool(self._ChBOX_relevant.GetValue())
2245 self.data['is_confidential'] = bool(self._ChBOX_confidential.GetValue())
2246 self.data['is_cause_of_death'] = bool(self._ChBOX_caused_death.GetValue())
2247
2248 age_noted = self._PRW_age_noted.GetData()
2249 if age_noted is not None:
2250 self.data['age_noted'] = age_noted
2251
2252 self.data.save()
2253 self.data.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
2254
2255 return True
2256 #----------------------------------------------------------------
2258 self._PRW_condition.SetText()
2259 self._ChBOX_left.SetValue(0)
2260 self._ChBOX_right.SetValue(0)
2261 self._PRW_codes.SetText()
2262 self._on_leave_codes()
2263 self._PRW_certainty.SetText()
2264 self._PRW_grouping.SetText()
2265 self._TCTRL_status.SetValue(u'')
2266 self._PRW_age_noted.SetText()
2267 self._PRW_year_noted.SetText()
2268 self._ChBOX_active.SetValue(1)
2269 self._ChBOX_relevant.SetValue(1)
2270 self._ChBOX_confidential.SetValue(0)
2271 self._ChBOX_caused_death.SetValue(0)
2272
2273 return True
2274 #----------------------------------------------------------------
2276 self._PRW_condition.SetText(self.data['description'])
2277
2278 lat = gmTools.coalesce(self.data['laterality'], '')
2279 if lat.find('s') == -1:
2280 self._ChBOX_left.SetValue(0)
2281 else:
2282 self._ChBOX_left.SetValue(1)
2283 if lat.find('d') == -1:
2284 self._ChBOX_right.SetValue(0)
2285 else:
2286 self._ChBOX_right.SetValue(1)
2287
2288 val, data = self._PRW_codes.generic_linked_codes2item_dict(self.data.generic_codes)
2289 self._PRW_codes.SetText(val, data)
2290 self._on_leave_codes()
2291
2292 if self.data['diagnostic_certainty_classification'] is not None:
2293 self._PRW_certainty.SetData(data = self.data['diagnostic_certainty_classification'])
2294 self._PRW_grouping.SetText(gmTools.coalesce(self.data['grouping'], u''))
2295 self._TCTRL_status.SetValue(gmTools.coalesce(self.data['summary'], u''))
2296
2297 if self.data['age_noted'] is None:
2298 self._PRW_age_noted.SetText()
2299 else:
2300 self._PRW_age_noted.SetText (
2301 value = '%sd' % self.data['age_noted'].days,
2302 data = self.data['age_noted']
2303 )
2304
2305 self._ChBOX_active.SetValue(self.data['is_active'])
2306 self._ChBOX_relevant.SetValue(self.data['clinically_relevant'])
2307 self._ChBOX_confidential.SetValue(self.data['is_confidential'])
2308 self._ChBOX_caused_death.SetValue(self.data['is_cause_of_death'])
2309
2310 # this dance should assure self._PRW_year_noted gets set -- but it doesn't ...
2311 # self._PRW_age_noted.SetFocus()
2312 # self._PRW_condition.SetFocus()
2313
2314 return True
2315 #----------------------------------------------------------------
2318 #--------------------------------------------------------
2319 # internal helpers
2320 #--------------------------------------------------------
2322 if not self._PRW_codes.IsModified():
2323 return True
2324
2325 self._TCTRL_code_details.SetValue(u'- ' + u'\n- '.join([ c['list_label'] for c in self._PRW_codes.GetData() ]))
2326 #--------------------------------------------------------
2328
2329 if not self._PRW_age_noted.IsModified():
2330 return True
2331
2332 str_age = self._PRW_age_noted.GetValue().strip()
2333
2334 if str_age == u'':
2335 wx.CallAfter(self._PRW_year_noted.SetText, u'', None, True)
2336 return True
2337
2338 age = gmDateTime.str2interval(str_interval = str_age)
2339
2340 if age is None:
2341 gmDispatcher.send(signal='statustext', msg=_('Cannot parse [%s] into valid interval.') % str_age)
2342 self._PRW_age_noted.SetBackgroundColour('pink')
2343 self._PRW_age_noted.Refresh()
2344 wx.CallAfter(self._PRW_year_noted.SetText, u'', None, True)
2345 return True
2346
2347 pat = gmPerson.gmCurrentPatient()
2348 if pat['dob'] is not None:
2349 max_age = pydt.datetime.now(tz=pat['dob'].tzinfo) - pat['dob']
2350
2351 if age >= max_age:
2352 gmDispatcher.send (
2353 signal = 'statustext',
2354 msg = _(
2355 'Health issue cannot have been noted at age %s. Patient is only %s old.'
2356 ) % (age, pat.get_medical_age())
2357 )
2358 self._PRW_age_noted.SetBackgroundColour('pink')
2359 self._PRW_age_noted.Refresh()
2360 wx.CallAfter(self._PRW_year_noted.SetText, u'', None, True)
2361 return True
2362
2363 self._PRW_age_noted.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2364 self._PRW_age_noted.Refresh()
2365 self._PRW_age_noted.SetData(data=age)
2366
2367 if pat['dob'] is not None:
2368 fts = gmDateTime.cFuzzyTimestamp (
2369 timestamp = pat['dob'] + age,
2370 accuracy = gmDateTime.acc_months
2371 )
2372 wx.CallAfter(self._PRW_year_noted.SetText, str(fts), fts)
2373 # if we do this we will *always* navigate there, regardless of TAB vs ALT-TAB
2374 #wx.CallAfter(self._ChBOX_active.SetFocus)
2375 # if we do the following instead it will take us to the save/update button ...
2376 #wx.CallAfter(self.Navigate)
2377
2378 return True
2379 #--------------------------------------------------------
2381
2382 if not self._PRW_year_noted.IsModified():
2383 return True
2384
2385 year_noted = self._PRW_year_noted.GetData()
2386
2387 if year_noted is None:
2388 if self._PRW_year_noted.GetValue().strip() == u'':
2389 wx.CallAfter(self._PRW_age_noted.SetText, u'', None, True)
2390 return True
2391 self._PRW_year_noted.SetBackgroundColour('pink')
2392 self._PRW_year_noted.Refresh()
2393 wx.CallAfter(self._PRW_age_noted.SetText, u'', None, True)
2394 return True
2395
2396 year_noted = year_noted.get_pydt()
2397
2398 if year_noted >= pydt.datetime.now(tz=year_noted.tzinfo):
2399 gmDispatcher.send(signal='statustext', msg=_('Condition diagnosed in the future.'))
2400 self._PRW_year_noted.SetBackgroundColour('pink')
2401 self._PRW_year_noted.Refresh()
2402 wx.CallAfter(self._PRW_age_noted.SetText, u'', None, True)
2403 return True
2404
2405 self._PRW_year_noted.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2406 self._PRW_year_noted.Refresh()
2407
2408 pat = gmPerson.gmCurrentPatient()
2409 if pat['dob'] is not None:
2410 issue_age = year_noted - pat['dob']
2411 str_age = gmDateTime.format_interval_medically(interval = issue_age)
2412 wx.CallAfter(self._PRW_age_noted.SetText, str_age, issue_age)
2413
2414 return True
2415 #--------------------------------------------------------
2419 #--------------------------------------------------------
2423 #================================================================
2424 # diagnostic certainty related widgets/functions
2425 #----------------------------------------------------------------
2427
2429
2430 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
2431
2432 self.selection_only = False # can be NULL, too
2433
2434 mp = gmMatchProvider.cMatchProvider_FixedList (
2435 aSeq = [
2436 {'data': u'A', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'A'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'A'), 'weight': 1},
2437 {'data': u'B', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'B'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'B'), 'weight': 1},
2438 {'data': u'C', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'C'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'C'), 'weight': 1},
2439 {'data': u'D', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'D'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'D'), 'weight': 1}
2440 ]
2441 )
2442 mp.setThresholds(1, 2, 4)
2443 self.matcher = mp
2444
2445 self.SetToolTipString(_(
2446 "The diagnostic classification or grading of this assessment.\n"
2447 "\n"
2448 "This documents how certain one is about this being a true diagnosis."
2449 ))
2450 #================================================================
2451 # MAIN
2452 #----------------------------------------------------------------
2453 if __name__ == '__main__':
2454
2455 from Gnumed.business import gmPersonSearch
2456 from Gnumed.wxpython import gmPatSearchWidgets
2457
2458 #================================================================
2460 """
2461 Test application for testing EMR struct widgets
2462 """
2463 #--------------------------------------------------------
2465 """
2466 Create test application UI
2467 """
2468 frame = wx.Frame (
2469 None,
2470 -4,
2471 'Testing EMR struct widgets',
2472 size=wx.Size(600, 400),
2473 style=wx.DEFAULT_FRAME_STYLE | wx.NO_FULL_REPAINT_ON_RESIZE
2474 )
2475 filemenu= wx.Menu()
2476 filemenu.AppendSeparator()
2477 filemenu.Append(ID_EXIT,"E&xit"," Terminate test application")
2478
2479 # Creating the menubar.
2480 menuBar = wx.MenuBar()
2481 menuBar.Append(filemenu,"&File")
2482
2483 frame.SetMenuBar(menuBar)
2484
2485 txt = wx.StaticText( frame, -1, _("Select desired test option from the 'File' menu"),
2486 wx.DefaultPosition, wx.DefaultSize, 0 )
2487
2488 # event handlers
2489 wx.EVT_MENU(frame, ID_EXIT, self.OnCloseWindow)
2490
2491 # patient EMR
2492 self.__pat = gmPerson.gmCurrentPatient()
2493
2494 frame.Show(1)
2495 return 1
2496 #--------------------------------------------------------
2502 #----------------------------------------------------------------
2504 app = wx.PyWidgetTester(size = (200, 300))
2505 emr = pat.get_emr()
2506 enc = emr.active_encounter
2507 #enc = gmEMRStructItems.cEncounter(1)
2508 pnl = cEncounterEditAreaPnl(app.frame, -1, encounter=enc)
2509 app.frame.Show(True)
2510 app.MainLoop()
2511 return
2512 #----------------------------------------------------------------
2514 app = wx.PyWidgetTester(size = (200, 300))
2515 emr = pat.get_emr()
2516 enc = emr.active_encounter
2517 #enc = gmEMRStructItems.cEncounter(1)
2518
2519 dlg = cEncounterEditAreaDlg(parent=app.frame, id=-1, size = (400,400), encounter=enc)
2520 dlg.ShowModal()
2521
2522 # pnl = cEncounterEditAreaDlg(app.frame, -1, encounter=enc)
2523 # app.frame.Show(True)
2524 # app.MainLoop()
2525 #----------------------------------------------------------------
2527 app = wx.PyWidgetTester(size = (200, 300))
2528 emr = pat.get_emr()
2529 epi = emr.get_episodes()[0]
2530 pnl = cEpisodeEditAreaPnl(app.frame, -1, episode=epi)
2531 app.frame.Show(True)
2532 app.MainLoop()
2533 #----------------------------------------------------------------
2535 app = wx.PyWidgetTester(size = (200, 300))
2536 emr = pat.get_emr()
2537 epi = emr.get_episodes()[0]
2538 edit_episode(parent=app.frame, episode=epi)
2539 #----------------------------------------------------------------
2541 app = wx.PyWidgetTester(size = (400, 40))
2542 app.SetWidget(cHospitalStayPhraseWheel, id=-1, size=(180,20), pos=(10,20))
2543 app.MainLoop()
2544 #----------------------------------------------------------------
2546 app = wx.PyWidgetTester(size = (400, 40))
2547 app.SetWidget(cEpisodeSelectionPhraseWheel, id=-1, size=(180,20), pos=(10,20))
2548 # app.SetWidget(cEpisodeSelectionPhraseWheel, id=-1, size=(350,20), pos=(10,20), patient_id=pat.ID)
2549 app.MainLoop()
2550 #----------------------------------------------------------------
2552 app = wx.PyWidgetTester(size = (200, 300))
2553 edit_health_issue(parent=app.frame, issue=None)
2554 #----------------------------------------------------------------
2556 app = wx.PyWidgetTester(size = (200, 300))
2557 app.SetWidget(cHealthIssueEditAreaPnl, id=-1, size = (400,400))
2558 app.MainLoop()
2559 #----------------------------------------------------------------
2563 #================================================================
2564
2565 if (len(sys.argv) > 1) and (sys.argv[1] == 'test'):
2566
2567 gmI18N.activate_locale()
2568 gmI18N.install_domain()
2569 gmDateTime.init()
2570
2571 # obtain patient
2572 pat = gmPersonSearch.ask_for_patient()
2573 if pat is None:
2574 print "No patient. Exiting gracefully..."
2575 sys.exit(0)
2576 gmPatSearchWidgets.set_active_patient(patient=pat)
2577
2578 # try:
2579 # lauch emr dialogs test application
2580 # app = testapp(0)
2581 # app.MainLoop()
2582 # except StandardError:
2583 # _log.exception("unhandled exception caught !")
2584 # but re-raise them
2585 # raise
2586
2587 #test_encounter_edit_area_panel()
2588 #test_encounter_edit_area_dialog()
2589 #test_epsiode_edit_area_pnl()
2590 #test_episode_edit_area_dialog()
2591 #test_health_issue_edit_area_dlg()
2592 #test_episode_selection_prw()
2593 #test_hospital_stay_prw()
2594 test_edit_procedure()
2595
2596 #================================================================
2597
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Mon Jun 10 03:56:09 2013 | http://epydoc.sourceforge.net |