| 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 new():
719 cfg_db = gmCfg.cCfgSQL()
720 enc_type = cfg_db.get2 (
721 option = u'encounter.default_type',
722 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
723 bias = u'user'
724 )
725 if enc_type is None:
726 enc_type = gmEMRStructItems.get_most_commonly_used_encounter_type()
727 if enc_type is None:
728 enc_type = u'in surgery'
729 enc = gmEMRStructItems.create_encounter(fk_patient = patient.ID, enc_type = enc_type)
730 return edit_encounter(parent = parent, encounter = enc)
731 #--------------------
732 def edit(enc=None):
733 return edit_encounter(parent = parent, encounter = enc)
734 #--------------------
735 def edit_active(enc=None):
736 return edit_encounter(parent = parent, encounter = emr.active_encounter)
737 #--------------------
738 def start_new(enc=None):
739 start_new_encounter(emr = emr)
740 return True
741 #--------------------
742 def get_tooltip(data):
743 if data is None:
744 return None
745 return data.format (
746 patient = patient,
747 with_soap = False,
748 with_docs = False,
749 with_tests = False,
750 with_vaccinations = False,
751 with_rfe_aoe = True,
752 with_family_history = False,
753 by_episode=False,
754 fancy_header = True,
755 )
756 #--------------------
757 def refresh(lctrl):
758 if encounters is None:
759 encs = emr.get_encounters()
760 else:
761 encs = encounters
762
763 items = [
764 [
765 u'%s - %s' % (gmDateTime.pydt_strftime(e['started'], '%Y %b %d %H:%M'), e['last_affirmed'].strftime('%H:%M')),
766 e['l10n_type'],
767 gmTools.coalesce(e['praxis_branch'], u''),
768 gmTools.coalesce(e['reason_for_encounter'], u''),
769 gmTools.coalesce(e['assessment_of_encounter'], u''),
770 gmTools.bool2subst(e.has_clinical_data(), u'', gmTools.u_checkmark_thin),
771 e['pk_encounter']
772 ] for e in encs
773 ]
774 lctrl.set_string_items(items = items)
775 lctrl.set_data(data = encs)
776 active_pk = emr.active_encounter['pk_encounter']
777 for idx in range(len(encs)):
778 e = encs[idx]
779 if e['pk_encounter'] == active_pk:
780 lctrl.SetItemTextColour(idx, col=wx.NamedColour('RED'))
781 #--------------------
782 return gmListWidgets.get_choices_from_list (
783 parent = parent,
784 msg = _("The patient's encounters.\n"),
785 caption = _('Encounters ...'),
786 columns = [_('When'), _('Type'), _('Where'), _('Reason for Encounter'), _('Assessment of Encounter'), _('Empty'), '#'],
787 can_return_empty = False,
788 single_selection = single_selection,
789 refresh_callback = refresh,
790 edit_callback = edit,
791 new_callback = new,
792 list_tooltip_callback = get_tooltip,
793 ignore_OK_button = ignore_OK_button,
794 left_extra_button = (_('Edit active'), _('Edit the active encounter'), edit_active),
795 middle_extra_button = (_('Start new'), _('Start new active encounter for the current patient.'), start_new)
796 )
797
798 #----------------------------------------------------------------
800 """This is used as the callback when the EMR detects that the
801 patient was here rather recently and wants to ask the
802 provider whether to continue the recent encounter.
803 """
804 if parent is None:
805 parent = wx.GetApp().GetTopWindow()
806
807 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
808 parent = None,
809 id = -1,
810 caption = caption,
811 question = msg,
812 button_defs = [
813 {'label': _('Continue'), 'tooltip': _('Continue the existing recent encounter.'), 'default': False},
814 {'label': _('Start new'), 'tooltip': _('Start a new encounter. The existing one will be closed.'), 'default': True}
815 ],
816 show_checkbox = False
817 )
818
819 result = dlg.ShowModal()
820 dlg.Destroy()
821
822 if result == wx.ID_YES:
823 return True
824
825 return False
826 #----------------------------------------------------------------
828
829 if parent is None:
830 parent = wx.GetApp().GetTopWindow()
831
832 #--------------------
833 def edit(enc_type=None):
834 return edit_encounter_type(parent = parent, encounter_type = enc_type)
835 #--------------------
836 def delete(enc_type=None):
837 if gmEMRStructItems.delete_encounter_type(description = enc_type['description']):
838 return True
839 gmDispatcher.send (
840 signal = u'statustext',
841 msg = _('Cannot delete encounter type [%s]. It is in use.') % enc_type['l10n_description'],
842 beep = True
843 )
844 return False
845 #--------------------
846 def refresh(lctrl):
847 enc_types = gmEMRStructItems.get_encounter_types()
848 lctrl.set_string_items(items = enc_types)
849 #--------------------
850 gmListWidgets.get_choices_from_list (
851 parent = parent,
852 msg = _('\nSelect the encounter type you want to edit !\n'),
853 caption = _('Managing encounter types ...'),
854 columns = [_('Local name'), _('Encounter type')],
855 single_selection = True,
856 edit_callback = edit,
857 new_callback = edit,
858 delete_callback = delete,
859 refresh_callback = refresh
860 )
861 #----------------------------------------------------------------
863 ea = cEncounterTypeEditAreaPnl(parent = parent, id = -1)
864 ea.data = encounter_type
865 ea.mode = gmTools.coalesce(encounter_type, 'new', 'edit')
866 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea)
867 dlg.SetTitle(gmTools.coalesce(encounter_type, _('Adding new encounter type'), _('Editing local encounter type name')))
868 if dlg.ShowModal() == wx.ID_OK:
869 return True
870 return False
871 #----------------------------------------------------------------
873
875 gmPhraseWheel.cPhraseWheel.__init__ (self, *args, **kwargs)
876
877 cmd = u"""
878 SELECT DISTINCT ON (list_label)
879 pk_encounter
880 AS data,
881 to_char(started, 'YYYY Mon DD (HH24:MI)') || ': ' || l10n_type || ' [#' || pk_encounter || ']'
882 AS list_label,
883 to_char(started, 'YYYY Mon DD') || ': ' || l10n_type
884 AS field_label
885 FROM
886 clin.v_pat_encounters
887 WHERE
888 (
889 to_char(started, 'YYYY-MM-DD') %(fragment_condition)s
890 OR
891 l10n_type %(fragment_condition)s
892 OR
893 type %(fragment_condition)s
894 ) %(ctxt_patient)s
895 ORDER BY
896 list_label
897 LIMIT
898 30
899 """
900 context = {'ctxt_patient': {
901 'where_part': u'AND pk_patient = %(patient)s',
902 'placeholder': u'patient'
903 }}
904
905 self.matcher = gmMatchProvider.cMatchProvider_SQL2(queries = [cmd], context = context)
906 self.matcher._SQL_data2match = u"""
907 SELECT
908 pk_encounter
909 AS data,
910 to_char(started, 'YYYY Mon DD (HH24:MI)') || ': ' || l10n_type
911 AS list_label,
912 to_char(started, 'YYYY Mon DD') || ': ' || l10n_type
913 AS field_label
914 FROM
915 clin.v_pat_encounters
916 WHERE
917 pk_encounter = %(pk)s
918 """
919 self.matcher.setThresholds(1, 3, 5)
920 #self.matcher.print_queries = True
921 self.selection_only = True
922 # outside code MUST bind this to a patient
923 self.set_context(context = 'patient', val = None)
924 #--------------------------------------------------------
926 val = u'%s: %s' % (
927 gmDateTime.pydt_strftime(instance['started'], '%Y %b %d'),
928 instance['l10n_type']
929 )
930 self.SetText(value = val, data = instance['pk_encounter'])
931 #------------------------------------------------------------
933 if self.GetData() is None:
934 return None
935 enc = gmEMRStructItems.cEncounter(aPK_obj = self._data.values()[0]['data'])
936 return enc.format (
937 with_docs = False,
938 with_tests = False,
939 with_vaccinations = False,
940 with_family_history = False
941 )
942 #----------------------------------------------------------------
944 """Phrasewheel to allow selection of encounter type.
945
946 - user input interpreted as encounter type in English or local language
947 - data returned is pk of corresponding encounter type or None
948 """
950
951 gmPhraseWheel.cPhraseWheel.__init__ (self, *args, **kwargs)
952
953 mp = gmMatchProvider.cMatchProvider_SQL2 (
954 queries = [
955 u"""
956 SELECT
957 data,
958 field_label,
959 list_label
960 FROM (
961 SELECT DISTINCT ON (data) *
962 FROM (
963 SELECT
964 pk AS data,
965 _(description) AS field_label,
966 case
967 when _(description) = description then _(description)
968 else _(description) || ' (' || description || ')'
969 end AS list_label
970 FROM
971 clin.encounter_type
972 WHERE
973 _(description) %(fragment_condition)s
974 OR
975 description %(fragment_condition)s
976 ) AS q_distinct_pk
977 ) AS q_ordered
978 ORDER BY
979 list_label
980 """ ]
981 )
982 mp.setThresholds(2, 4, 6)
983
984 self.matcher = mp
985 self.selection_only = True
986 self.picklist_delay = 50
987 #----------------------------------------------------------------
988 from Gnumed.wxGladeWidgets import wxgEncounterTypeEditAreaPnl
989
990 -class cEncounterTypeEditAreaPnl(wxgEncounterTypeEditAreaPnl.wxgEncounterTypeEditAreaPnl, gmEditArea.cGenericEditAreaMixin):
991
993
994 wxgEncounterTypeEditAreaPnl.wxgEncounterTypeEditAreaPnl.__init__(self, *args, **kwargs)
995 gmEditArea.cGenericEditAreaMixin.__init__(self)
996
997 # self.__register_interests()
998 #-------------------------------------------------------
999 # generic edit area API
1000 #-------------------------------------------------------
1002 if self.mode == 'edit':
1003 if self._TCTRL_l10n_name.GetValue().strip() == u'':
1004 self.display_tctrl_as_valid(tctrl = self._TCTRL_l10n_name, valid = False)
1005 return False
1006 self.display_tctrl_as_valid(tctrl = self._TCTRL_l10n_name, valid = True)
1007 return True
1008
1009 no_errors = True
1010
1011 if self._TCTRL_l10n_name.GetValue().strip() == u'':
1012 if self._TCTRL_name.GetValue().strip() == u'':
1013 self.display_tctrl_as_valid(tctrl = self._TCTRL_l10n_name, valid = False)
1014 no_errors = False
1015 else:
1016 self.display_tctrl_as_valid(tctrl = self._TCTRL_l10n_name, valid = True)
1017 else:
1018 self.display_tctrl_as_valid(tctrl = self._TCTRL_l10n_name, valid = True)
1019
1020 if self._TCTRL_name.GetValue().strip() == u'':
1021 if self._TCTRL_l10n_name.GetValue().strip() == u'':
1022 self.display_tctrl_as_valid(tctrl = self._TCTRL_name, valid = False)
1023 no_errors = False
1024 else:
1025 self.display_tctrl_as_valid(tctrl = self._TCTRL_name, valid = True)
1026 else:
1027 self.display_tctrl_as_valid(tctrl = self._TCTRL_name, valid = True)
1028
1029 return no_errors
1030 #-------------------------------------------------------
1032 enc_type = gmEMRStructItems.create_encounter_type (
1033 description = gmTools.none_if(self._TCTRL_name.GetValue().strip(), u''),
1034 l10n_description = gmTools.coalesce (
1035 gmTools.none_if(self._TCTRL_l10n_name.GetValue().strip(), u''),
1036 self._TCTRL_name.GetValue().strip()
1037 )
1038 )
1039 if enc_type is None:
1040 return False
1041 self.data = enc_type
1042 return True
1043 #-------------------------------------------------------
1045 enc_type = gmEMRStructItems.update_encounter_type (
1046 description = self._TCTRL_name.GetValue().strip(),
1047 l10n_description = self._TCTRL_l10n_name.GetValue().strip()
1048 )
1049 if enc_type is None:
1050 return False
1051 self.data = enc_type
1052 return True
1053 #-------------------------------------------------------
1055 self._TCTRL_l10n_name.SetValue(u'')
1056 self._TCTRL_name.SetValue(u'')
1057 self._TCTRL_name.Enable(True)
1058 #-------------------------------------------------------
1060 self._TCTRL_l10n_name.SetValue(self.data['l10n_description'])
1061 self._TCTRL_name.SetValue(self.data['description'])
1062 # disallow changing type on all encounters by editing system name
1063 self._TCTRL_name.Enable(False)
1064 #-------------------------------------------------------
1069 #-------------------------------------------------------
1070 # internal API
1071 #-------------------------------------------------------
1072 # def __register_interests(self):
1073 # return
1074 #----------------------------------------------------------------
1075 from Gnumed.wxGladeWidgets import wxgEncounterEditAreaPnl
1076
1078
1080 try:
1081 self.__encounter = kwargs['encounter']
1082 del kwargs['encounter']
1083 except KeyError:
1084 self.__encounter = None
1085
1086 try:
1087 msg = kwargs['msg']
1088 del kwargs['msg']
1089 except KeyError:
1090 msg = None
1091
1092 wxgEncounterEditAreaPnl.wxgEncounterEditAreaPnl.__init__(self, *args, **kwargs)
1093
1094 self.refresh(msg = msg)
1095 #--------------------------------------------------------
1096 # external API
1097 #--------------------------------------------------------
1099
1100 if msg is not None:
1101 self._LBL_instructions.SetLabel(msg)
1102
1103 if encounter is not None:
1104 self.__encounter = encounter
1105
1106 if self.__encounter is None:
1107 return True
1108
1109 # getting the patient via the encounter allows us to act
1110 # on any encounter regardless of the currently active patient
1111 pat = gmPerson.cPatient(aPK_obj = self.__encounter['pk_patient'])
1112 self._LBL_patient.SetLabel(pat.get_description_gender().strip())
1113 curr_pat = gmPerson.gmCurrentPatient()
1114 if curr_pat.connected:
1115 if curr_pat.ID == self.__encounter['pk_patient']:
1116 self._LBL_patient.SetForegroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOWTEXT))
1117 else:
1118 self._LBL_patient.SetForegroundColour('red')
1119
1120 self._PRW_encounter_type.SetText(self.__encounter['l10n_type'], data = self.__encounter['pk_type'])
1121 self._PRW_location.Enable(True)
1122 self._PRW_location.display_as_disabled(False)
1123 branch = self.__encounter.praxis_branch
1124 if branch is None: # None or old entry because praxis has been re-configured
1125 unit = self.__encounter.org_unit
1126 if unit is None: # None
1127 self._PRW_location.SetText(u'', data = None)
1128 else: # old entry
1129 self._PRW_location.Enable(False)
1130 self._PRW_location.display_as_disabled(True)
1131 self._PRW_location.SetText(_('old praxis branch: %s (%s)') % (unit['unit'], unit['organization']), data = None)
1132 else:
1133 self._PRW_location.SetText(self.__encounter['praxis_branch'], data = branch['pk_praxis_branch'])
1134
1135 fts = gmDateTime.cFuzzyTimestamp (
1136 timestamp = self.__encounter['started'],
1137 accuracy = gmDateTime.acc_minutes
1138 )
1139 self._PRW_start.SetText(fts.format_accurately(), data=fts)
1140
1141 fts = gmDateTime.cFuzzyTimestamp (
1142 timestamp = self.__encounter['last_affirmed'],
1143 accuracy = gmDateTime.acc_minutes
1144 )
1145 self._PRW_end.SetText(fts.format_accurately(), data=fts)
1146
1147 # RFE
1148 self._TCTRL_rfe.SetValue(gmTools.coalesce(self.__encounter['reason_for_encounter'], ''))
1149 val, data = self._PRW_rfe_codes.generic_linked_codes2item_dict(self.__encounter.generic_codes_rfe)
1150 self._PRW_rfe_codes.SetText(val, data)
1151
1152 # AOE
1153 self._TCTRL_aoe.SetValue(gmTools.coalesce(self.__encounter['assessment_of_encounter'], ''))
1154 val, data = self._PRW_aoe_codes.generic_linked_codes2item_dict(self.__encounter.generic_codes_aoe)
1155 self._PRW_aoe_codes.SetText(val, data)
1156
1157 # last affirmed
1158 if self.__encounter['last_affirmed'] == self.__encounter['started']:
1159 self._PRW_end.SetFocus()
1160 else:
1161 self._TCTRL_aoe.SetFocus()
1162
1163 return True
1164 #--------------------------------------------------------
1166
1167 if self._PRW_encounter_type.GetData() is None:
1168 self._PRW_encounter_type.SetBackgroundColour('pink')
1169 self._PRW_encounter_type.Refresh()
1170 self._PRW_encounter_type.SetFocus()
1171 return False
1172 self._PRW_encounter_type.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1173 self._PRW_encounter_type.Refresh()
1174
1175 # start
1176 if self._PRW_start.GetValue().strip() == u'':
1177 self._PRW_start.SetBackgroundColour('pink')
1178 self._PRW_start.Refresh()
1179 self._PRW_start.SetFocus()
1180 return False
1181 if not self._PRW_start.is_valid_timestamp(empty_is_valid = False):
1182 self._PRW_start.SetBackgroundColour('pink')
1183 self._PRW_start.Refresh()
1184 self._PRW_start.SetFocus()
1185 return False
1186 self._PRW_start.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1187 self._PRW_start.Refresh()
1188
1189 # last_affirmed
1190 # if self._PRW_end.GetValue().strip() == u'':
1191 # self._PRW_end.SetBackgroundColour('pink')
1192 # self._PRW_end.Refresh()
1193 # self._PRW_end.SetFocus()
1194 # return False
1195 if not self._PRW_end.is_valid_timestamp(empty_is_valid = False):
1196 self._PRW_end.SetBackgroundColour('pink')
1197 self._PRW_end.Refresh()
1198 self._PRW_end.SetFocus()
1199 return False
1200 self._PRW_end.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1201 self._PRW_end.Refresh()
1202
1203 return True
1204 #--------------------------------------------------------
1206 if not self.__is_valid_for_save():
1207 return False
1208
1209 self.__encounter['pk_type'] = self._PRW_encounter_type.GetData()
1210 self.__encounter['started'] = self._PRW_start.GetData().get_pydt()
1211 self.__encounter['last_affirmed'] = self._PRW_end.GetData().get_pydt()
1212 self.__encounter['reason_for_encounter'] = gmTools.none_if(self._TCTRL_rfe.GetValue().strip(), u'')
1213 self.__encounter['assessment_of_encounter'] = gmTools.none_if(self._TCTRL_aoe.GetValue().strip(), u'')
1214 self.__encounter.save_payload() # FIXME: error checking
1215
1216 self.__encounter.generic_codes_rfe = [ c['data'] for c in self._PRW_rfe_codes.GetData() ]
1217 self.__encounter.generic_codes_aoe = [ c['data'] for c in self._PRW_aoe_codes.GetData() ]
1218
1219 return True
1220 #----------------------------------------------------------------
1221 # FIXME: use generic dialog 2
1223
1225 encounter = kwargs['encounter']
1226 del kwargs['encounter']
1227
1228 try:
1229 button_defs = kwargs['button_defs']
1230 del kwargs['button_defs']
1231 except KeyError:
1232 button_defs = None
1233
1234 try:
1235 msg = kwargs['msg']
1236 del kwargs['msg']
1237 except KeyError:
1238 msg = None
1239
1240 wxgEncounterEditAreaDlg.wxgEncounterEditAreaDlg.__init__(self, *args, **kwargs)
1241 self.SetSize((450, 280))
1242 self.SetMinSize((450, 280))
1243
1244 if button_defs is not None:
1245 self._BTN_save.SetLabel(button_defs[0][0])
1246 self._BTN_save.SetToolTipString(button_defs[0][1])
1247 self._BTN_close.SetLabel(button_defs[1][0])
1248 self._BTN_close.SetToolTipString(button_defs[1][1])
1249 self.Refresh()
1250
1251 self._PNL_edit_area.refresh(encounter = encounter, msg = msg)
1252
1253 self.Fit()
1254 #--------------------------------------------------------
1261 #--------------------------------------------------------
1263 start = self._PRW_encounter_start.GetData()
1264 if start is None:
1265 return
1266 start = start.get_pydt()
1267
1268 end = self._PRW_encounter_end.GetData()
1269 if end is None:
1270 fts = gmDateTime.cFuzzyTimestamp (
1271 timestamp = start,
1272 accuracy = gmDateTime.acc_minutes
1273 )
1274 self._PRW_encounter_end.SetText(fts.format_accurately(), data = fts)
1275 return
1276 end = end.get_pydt()
1277
1278 if start > end:
1279 end = end.replace (
1280 year = start.year,
1281 month = start.month,
1282 day = start.day
1283 )
1284 fts = gmDateTime.cFuzzyTimestamp (
1285 timestamp = end,
1286 accuracy = gmDateTime.acc_minutes
1287 )
1288 self._PRW_encounter_end.SetText(fts.format_accurately(), data = fts)
1289 return
1290
1291 emr = self.__pat.get_emr()
1292 if start != emr.active_encounter['started']:
1293 end = end.replace (
1294 year = start.year,
1295 month = start.month,
1296 day = start.day
1297 )
1298 fts = gmDateTime.cFuzzyTimestamp (
1299 timestamp = end,
1300 accuracy = gmDateTime.acc_minutes
1301 )
1302 self._PRW_encounter_end.SetText(fts.format_accurately(), data = fts)
1303 return
1304
1305 return
1306
1307 #----------------------------------------------------------------
1308 from Gnumed.wxGladeWidgets import wxgActiveEncounterPnl
1309
1311
1313 wxgActiveEncounterPnl.wxgActiveEncounterPnl.__init__(self, *args, **kwargs)
1314 self.__register_events()
1315 self.refresh()
1316 #------------------------------------------------------------
1318 self._TCTRL_encounter.SetValue(u'')
1319 self._TCTRL_encounter.SetToolTipString(u'')
1320 self._BTN_new.Enable(False)
1321 self._BTN_list.Enable(False)
1322 #------------------------------------------------------------
1324 pat = gmPerson.gmCurrentPatient()
1325 if not pat.connected:
1326 self.clear()
1327 return
1328
1329 enc = pat.get_emr().active_encounter
1330 self._TCTRL_encounter.SetValue(enc.format(with_docs = False, with_tests = False, fancy_header = False, with_vaccinations = False, with_family_history = False).strip('\n'))
1331 self._TCTRL_encounter.SetToolTipString (
1332 _('The active encounter of the current patient:\n\n%s') %
1333 enc.format(with_docs = False, with_tests = False, fancy_header = True, with_vaccinations = False, with_rfe_aoe = True, with_family_history = False).strip('\n')
1334 )
1335 self._BTN_new.Enable(True)
1336 self._BTN_list.Enable(True)
1337 #------------------------------------------------------------
1339 self._TCTRL_encounter.Bind(wx.EVT_LEFT_DCLICK, self._on_ldclick)
1340
1341 gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._schedule_clear)
1342 # this would throw an exception due to concurrency issues:
1343 #gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._schedule_refresh)
1344 gmDispatcher.connect(signal = u'episode_mod_db', receiver = self._schedule_refresh)
1345 gmDispatcher.connect(signal = u'current_encounter_modified', receiver = self._schedule_refresh)
1346 gmDispatcher.connect(signal = u'current_encounter_switched', receiver = self._schedule_refresh)
1347 #------------------------------------------------------------
1348 # event handler
1349 #------------------------------------------------------------
1351 wx.CallAfter(self.clear)
1352 #------------------------------------------------------------
1356 #------------------------------------------------------------
1358 pat = gmPerson.gmCurrentPatient()
1359 if not pat.connected:
1360 return
1361 edit_encounter(encounter = pat.get_emr().active_encounter)
1362 #------------------------------------------------------------
1368 #------------------------------------------------------------
1373 #================================================================
1374 # episode related widgets/functions
1375 #----------------------------------------------------------------
1377 ea = cEpisodeEditAreaPnl(parent = parent, id = -1)
1378 ea.data = episode
1379 ea.mode = gmTools.coalesce(episode, 'new', 'edit')
1380 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = True)
1381 dlg.SetTitle(gmTools.coalesce(episode, _('Adding a new episode'), _('Editing an episode')))
1382 if dlg.ShowModal() == wx.ID_OK:
1383 return True
1384 return False
1385 #----------------------------------------------------------------
1387
1388 created_new_issue = False
1389
1390 try:
1391 issue = gmEMRStructItems.cHealthIssue(name = episode['description'], patient = episode['pk_patient'])
1392 except gmExceptions.NoSuchBusinessObjectError:
1393 issue = None
1394
1395 if issue is None:
1396 issue = emr.add_health_issue(issue_name = episode['description'])
1397 created_new_issue = True
1398 else:
1399 # issue exists already, so ask user
1400 dlg = gmGuiHelpers.c3ButtonQuestionDlg (
1401 parent,
1402 -1,
1403 caption = _('Promoting episode to health issue'),
1404 question = _(
1405 'There already is a health issue\n'
1406 '\n'
1407 ' %s\n'
1408 '\n'
1409 'What do you want to do ?'
1410 ) % issue['description'],
1411 button_defs = [
1412 {'label': _('Use existing'), 'tooltip': _('Move episode into existing health issue'), 'default': False},
1413 {'label': _('Create new'), 'tooltip': _('Create a new health issue with another name'), 'default': True}
1414 ]
1415 )
1416 use_existing = dlg.ShowModal()
1417 dlg.Destroy()
1418
1419 if use_existing == wx.ID_CANCEL:
1420 return
1421
1422 # user wants to create new issue with alternate name
1423 if use_existing == wx.ID_NO:
1424 # loop until name modified but non-empty or cancelled
1425 issue_name = episode['description']
1426 while issue_name == episode['description']:
1427 dlg = wx.TextEntryDialog (
1428 parent = parent,
1429 message = _('Enter a short descriptive name for the new health issue:'),
1430 caption = _('Creating a new health issue ...'),
1431 defaultValue = issue_name,
1432 style = wx.OK | wx.CANCEL | wx.CENTRE
1433 )
1434 decision = dlg.ShowModal()
1435 if decision != wx.ID_OK:
1436 dlg.Destroy()
1437 return
1438 issue_name = dlg.GetValue().strip()
1439 dlg.Destroy()
1440 if issue_name == u'':
1441 issue_name = episode['description']
1442
1443 issue = emr.add_health_issue(issue_name = issue_name)
1444 created_new_issue = True
1445
1446 # eventually move the episode to the issue
1447 if not move_episode_to_issue(episode = episode, target_issue = issue, save_to_backend = True):
1448 # user cancelled the move so delete just-created issue
1449 if created_new_issue:
1450 # shouldn't fail as it is completely new
1451 gmEMRStructItems.delete_health_issue(health_issue = issue)
1452 return
1453
1454 return
1455 #----------------------------------------------------------------
1457 """Prepare changing health issue for an episode.
1458
1459 Checks for two-open-episodes conflict. When this
1460 function succeeds, the pk_health_issue has been set
1461 on the episode instance and the episode should - for
1462 all practical purposes - be ready for save_payload().
1463 """
1464 # episode is closed: should always work
1465 if not episode['episode_open']:
1466 episode['pk_health_issue'] = target_issue['pk_health_issue']
1467 if save_to_backend:
1468 episode.save_payload()
1469 return True
1470
1471 # un-associate: should always work, too
1472 if target_issue is None:
1473 episode['pk_health_issue'] = None
1474 if save_to_backend:
1475 episode.save_payload()
1476 return True
1477
1478 # try closing possibly expired episode on target issue if any
1479 db_cfg = gmCfg.cCfgSQL()
1480 epi_ttl = int(db_cfg.get2 (
1481 option = u'episode.ttl',
1482 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
1483 bias = 'user',
1484 default = 60 # 2 months
1485 ))
1486 if target_issue.close_expired_episode(ttl=epi_ttl) is True:
1487 gmDispatcher.send(signal='statustext', msg=_('Closed episodes older than %s days on health issue [%s]') % (epi_ttl, target_issue['description']))
1488 existing_epi = target_issue.get_open_episode()
1489
1490 # no more open episode on target issue: should work now
1491 if existing_epi is None:
1492 episode['pk_health_issue'] = target_issue['pk_health_issue']
1493 if save_to_backend:
1494 episode.save_payload()
1495 return True
1496
1497 # don't conflict on SELF ;-)
1498 if existing_epi['pk_episode'] == episode['pk_episode']:
1499 episode['pk_health_issue'] = target_issue['pk_health_issue']
1500 if save_to_backend:
1501 episode.save_payload()
1502 return True
1503
1504 # we got two open episodes at once, ask user
1505 move_range = episode.get_access_range()
1506 exist_range = existing_epi.get_access_range()
1507 question = _(
1508 'You want to associate the running episode:\n\n'
1509 ' "%(new_epi_name)s" (%(new_epi_start)s - %(new_epi_end)s)\n\n'
1510 'with the health issue:\n\n'
1511 ' "%(issue_name)s"\n\n'
1512 'There already is another episode running\n'
1513 'for this health issue:\n\n'
1514 ' "%(old_epi_name)s" (%(old_epi_start)s - %(old_epi_end)s)\n\n'
1515 'However, there can only be one running\n'
1516 'episode per health issue.\n\n'
1517 'Which episode do you want to close ?'
1518 ) % {
1519 'new_epi_name': episode['description'],
1520 'new_epi_start': move_range[0].strftime('%m/%y'),
1521 'new_epi_end': move_range[1].strftime('%m/%y'),
1522 'issue_name': target_issue['description'],
1523 'old_epi_name': existing_epi['description'],
1524 'old_epi_start': exist_range[0].strftime('%m/%y'),
1525 'old_epi_end': exist_range[1].strftime('%m/%y')
1526 }
1527 dlg = gmGuiHelpers.c3ButtonQuestionDlg (
1528 parent = None,
1529 id = -1,
1530 caption = _('Resolving two-running-episodes conflict'),
1531 question = question,
1532 button_defs = [
1533 {'label': _('old episode'), 'default': True, 'tooltip': _('close existing episode "%s"') % existing_epi['description']},
1534 {'label': _('new episode'), 'default': False, 'tooltip': _('close moving (new) episode "%s"') % episode['description']}
1535 ]
1536 )
1537 decision = dlg.ShowModal()
1538
1539 if decision == wx.ID_CANCEL:
1540 # button 3: move cancelled by user
1541 return False
1542
1543 elif decision == wx.ID_YES:
1544 # button 1: close old episode
1545 existing_epi['episode_open'] = False
1546 existing_epi.save_payload()
1547
1548 elif decision == wx.ID_NO:
1549 # button 2: close new episode
1550 episode['episode_open'] = False
1551
1552 else:
1553 raise ValueError('invalid result from c3ButtonQuestionDlg: [%s]' % decision)
1554
1555 episode['pk_health_issue'] = target_issue['pk_health_issue']
1556 if save_to_backend:
1557 episode.save_payload()
1558 return True
1559 #----------------------------------------------------------------
1561
1562 # FIXME: support pre-selection
1563
1565
1566 episodes = kwargs['episodes']
1567 del kwargs['episodes']
1568
1569 gmListWidgets.cGenericListSelectorDlg.__init__(self, *args, **kwargs)
1570
1571 self.SetTitle(_('Select the episodes you are interested in ...'))
1572 self._LCTRL_items.set_columns([_('Episode'), _('Status'), _('Health Issue')])
1573 self._LCTRL_items.set_string_items (
1574 items = [
1575 [ epi['description'],
1576 gmTools.bool2str(epi['episode_open'], _('ongoing'), u''),
1577 gmTools.coalesce(epi['health_issue'], u'')
1578 ]
1579 for epi in episodes ]
1580 )
1581 self._LCTRL_items.set_column_widths()
1582 self._LCTRL_items.set_data(data = episodes)
1583 #----------------------------------------------------------------
1585 """Let user select an episode *description*.
1586
1587 The user can select an episode description from the previously
1588 used descriptions across all episodes across all patients.
1589
1590 Selection is done with a phrasewheel so the user can
1591 type the episode name and matches will be shown. Typing
1592 "*" will show the entire list of episodes.
1593
1594 If the user types a description not existing yet a
1595 new episode description will be returned.
1596 """
1598
1599 mp = gmMatchProvider.cMatchProvider_SQL2 (
1600 queries = [
1601 u"""
1602 SELECT DISTINCT ON (description)
1603 description
1604 AS data,
1605 description
1606 AS field_label,
1607 description || ' ('
1608 || CASE
1609 WHEN is_open IS TRUE THEN _('ongoing')
1610 ELSE _('closed')
1611 END
1612 || ')'
1613 AS list_label
1614 FROM
1615 clin.episode
1616 WHERE
1617 description %(fragment_condition)s
1618 ORDER BY description
1619 LIMIT 30
1620 """
1621 ]
1622 )
1623 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
1624 self.matcher = mp
1625 #----------------------------------------------------------------
1627 """Let user select an episode.
1628
1629 The user can select an episode from the existing episodes of a
1630 patient. Selection is done with a phrasewheel so the user
1631 can type the episode name and matches will be shown. Typing
1632 "*" will show the entire list of episodes. Closed episodes
1633 will be marked as such. If the user types an episode name not
1634 in the list of existing episodes a new episode can be created
1635 from it if the programmer activated that feature.
1636
1637 If keyword <patient_id> is set to None or left out the control
1638 will listen to patient change signals and therefore act on
1639 gmPerson.gmCurrentPatient() changes.
1640 """
1642
1643 ctxt = {'ctxt_pat': {'where_part': u'and pk_patient = %(pat)s', 'placeholder': u'pat'}}
1644
1645 mp = gmMatchProvider.cMatchProvider_SQL2 (
1646 queries = [
1647 u"""(
1648
1649 select
1650 pk_episode
1651 as data,
1652 description
1653 as field_label,
1654 coalesce (
1655 description || ' - ' || health_issue,
1656 description
1657 ) as list_label,
1658 1 as rank
1659 from
1660 clin.v_pat_episodes
1661 where
1662 episode_open is true and
1663 description %(fragment_condition)s
1664 %(ctxt_pat)s
1665
1666 ) union all (
1667
1668 select
1669 pk_episode
1670 as data,
1671 description
1672 as field_label,
1673 coalesce (
1674 description || _(' (closed)') || ' - ' || health_issue,
1675 description || _(' (closed)')
1676 ) as list_label,
1677 2 as rank
1678 from
1679 clin.v_pat_episodes
1680 where
1681 description %(fragment_condition)s and
1682 episode_open is false
1683 %(ctxt_pat)s
1684
1685 )
1686
1687 order by rank, list_label
1688 limit 30"""
1689 ],
1690 context = ctxt
1691 )
1692
1693 try:
1694 kwargs['patient_id']
1695 except KeyError:
1696 kwargs['patient_id'] = None
1697
1698 if kwargs['patient_id'] is None:
1699 self.use_current_patient = True
1700 self.__register_patient_change_signals()
1701 pat = gmPerson.gmCurrentPatient()
1702 if pat.connected:
1703 mp.set_context('pat', pat.ID)
1704 else:
1705 self.use_current_patient = False
1706 self.__patient_id = int(kwargs['patient_id'])
1707 mp.set_context('pat', self.__patient_id)
1708
1709 del kwargs['patient_id']
1710
1711 gmPhraseWheel.cPhraseWheel.__init__ (
1712 self,
1713 *args,
1714 **kwargs
1715 )
1716 self.matcher = mp
1717 #--------------------------------------------------------
1718 # external API
1719 #--------------------------------------------------------
1721 if self.use_current_patient:
1722 return False
1723 self.__patient_id = int(patient_id)
1724 self.set_context('pat', self.__patient_id)
1725 return True
1726 #--------------------------------------------------------
1728 self.__is_open_for_create_data = is_open # used (only) in _create_data()
1729 return gmPhraseWheel.cPhraseWheel.GetData(self, can_create = can_create, as_instance = as_instance)
1730 #--------------------------------------------------------
1732
1733 epi_name = self.GetValue().strip()
1734 if epi_name == u'':
1735 gmDispatcher.send(signal = u'statustext', msg = _('Cannot create episode without name.'), beep = True)
1736 _log.debug('cannot create episode without name')
1737 return
1738
1739 if self.use_current_patient:
1740 pat = gmPerson.gmCurrentPatient()
1741 else:
1742 pat = gmPerson.cPatient(aPK_obj = self.__patient_id)
1743
1744 emr = pat.get_emr()
1745 epi = emr.add_episode(episode_name = epi_name, is_open = self.__is_open_for_create_data)
1746 if epi is None:
1747 self.data = {}
1748 else:
1749 self.SetText (
1750 value = epi_name,
1751 data = epi['pk_episode']
1752 )
1753 #--------------------------------------------------------
1756 #--------------------------------------------------------
1757 # internal API
1758 #--------------------------------------------------------
1760 gmDispatcher.connect(self._pre_patient_selection, u'pre_patient_selection')
1761 gmDispatcher.connect(self._post_patient_selection, u'post_patient_selection')
1762 #--------------------------------------------------------
1765 #--------------------------------------------------------
1767 if self.use_current_patient:
1768 patient = gmPerson.gmCurrentPatient()
1769 self.set_context('pat', patient.ID)
1770 return True
1771 #----------------------------------------------------------------
1772 from Gnumed.wxGladeWidgets import wxgEpisodeEditAreaPnl
1773
1774 -class cEpisodeEditAreaPnl(gmEditArea.cGenericEditAreaMixin, wxgEpisodeEditAreaPnl.wxgEpisodeEditAreaPnl):
1775
1777
1778 try:
1779 episode = kwargs['episode']
1780 del kwargs['episode']
1781 except KeyError:
1782 episode = None
1783
1784 wxgEpisodeEditAreaPnl.wxgEpisodeEditAreaPnl.__init__(self, *args, **kwargs)
1785 gmEditArea.cGenericEditAreaMixin.__init__(self)
1786
1787 self.data = episode
1788 #----------------------------------------------------------------
1789 # generic Edit Area mixin API
1790 #----------------------------------------------------------------
1792
1793 errors = False
1794
1795 if len(self._PRW_description.GetValue().strip()) == 0:
1796 errors = True
1797 self._PRW_description.display_as_valid(False)
1798 self._PRW_description.SetFocus()
1799 else:
1800 self._PRW_description.display_as_valid(True)
1801 self._PRW_description.Refresh()
1802
1803 return not errors
1804 #----------------------------------------------------------------
1806
1807 pat = gmPerson.gmCurrentPatient()
1808 emr = pat.get_emr()
1809
1810 epi = emr.add_episode(episode_name = self._PRW_description.GetValue().strip())
1811 epi['summary'] = self._TCTRL_status.GetValue().strip()
1812 epi['episode_open'] = not self._CHBOX_closed.IsChecked()
1813 epi['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
1814
1815 issue_name = self._PRW_issue.GetValue().strip()
1816 if len(issue_name) != 0:
1817 epi['pk_health_issue'] = self._PRW_issue.GetData(can_create = True)
1818 issue = gmEMRStructItems.cHealthIssue(aPK_obj = epi['pk_health_issue'])
1819
1820 if not move_episode_to_issue(episode = epi, 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 epi['description'],
1825 issue['description']
1826 )
1827 )
1828 gmEMRStructItems.delete_episode(episode = epi)
1829 return False
1830
1831 epi.save()
1832
1833 epi.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
1834
1835 self.data = epi
1836 return True
1837 #----------------------------------------------------------------
1839
1840 self.data['description'] = self._PRW_description.GetValue().strip()
1841 self.data['summary'] = self._TCTRL_status.GetValue().strip()
1842 self.data['episode_open'] = not self._CHBOX_closed.IsChecked()
1843 self.data['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
1844
1845 issue_name = self._PRW_issue.GetValue().strip()
1846 if len(issue_name) == 0:
1847 self.data['pk_health_issue'] = None
1848 else:
1849 self.data['pk_health_issue'] = self._PRW_issue.GetData(can_create = True)
1850 issue = gmEMRStructItems.cHealthIssue(aPK_obj = self.data['pk_health_issue'])
1851
1852 if not move_episode_to_issue(episode = self.data, target_issue = issue, save_to_backend = False):
1853 gmDispatcher.send (
1854 signal = 'statustext',
1855 msg = _('Cannot attach episode [%s] to health issue [%s] because it already has a running episode.') % (
1856 self.data['description'],
1857 issue['description']
1858 )
1859 )
1860 return False
1861
1862 self.data.save()
1863 self.data.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
1864
1865 return True
1866 #----------------------------------------------------------------
1868 if self.data is None:
1869 ident = gmPerson.gmCurrentPatient()
1870 else:
1871 ident = gmPerson.cIdentity(aPK_obj = self.data['pk_patient'])
1872 self._TCTRL_patient.SetValue(ident.get_description_gender())
1873 self._PRW_issue.SetText()
1874 self._PRW_description.SetText()
1875 self._TCTRL_status.SetValue(u'')
1876 self._PRW_certainty.SetText()
1877 self._CHBOX_closed.SetValue(False)
1878 self._PRW_codes.SetText()
1879 #----------------------------------------------------------------
1881 ident = gmPerson.cIdentity(aPK_obj = self.data['pk_patient'])
1882 self._TCTRL_patient.SetValue(ident.get_description_gender())
1883
1884 if self.data['pk_health_issue'] is not None:
1885 self._PRW_issue.SetText(self.data['health_issue'], data=self.data['pk_health_issue'])
1886
1887 self._PRW_description.SetText(self.data['description'], data=self.data['description'])
1888
1889 self._TCTRL_status.SetValue(gmTools.coalesce(self.data['summary'], u''))
1890
1891 if self.data['diagnostic_certainty_classification'] is not None:
1892 self._PRW_certainty.SetData(data = self.data['diagnostic_certainty_classification'])
1893
1894 self._CHBOX_closed.SetValue(not self.data['episode_open'])
1895
1896 val, data = self._PRW_codes.generic_linked_codes2item_dict(self.data.generic_codes)
1897 self._PRW_codes.SetText(val, data)
1898 #----------------------------------------------------------------
1901 #================================================================
1902 # health issue related widgets/functions
1903 #----------------------------------------------------------------
1905 ea = cHealthIssueEditAreaPnl(parent = parent, id = -1)
1906 ea.data = issue
1907 ea.mode = gmTools.coalesce(issue, 'new', 'edit')
1908 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = (issue is not None))
1909 dlg.SetTitle(gmTools.coalesce(issue, _('Adding a new health issue'), _('Editing a health issue')))
1910 if dlg.ShowModal() == wx.ID_OK:
1911 return True
1912 return False
1913 #----------------------------------------------------------------
1915
1916 if parent is None:
1917 parent = wx.GetApp().GetTopWindow()
1918 #-----------------------------------------
1919 def refresh(lctrl):
1920 issues = emr.get_health_issues()
1921 items = [
1922 [
1923 gmTools.bool2subst(i['is_confidential'], _('CONFIDENTIAL'), u'', u''),
1924 i['description'],
1925 gmTools.bool2subst(i['clinically_relevant'], _('relevant'), u'', u''),
1926 gmTools.bool2subst(i['is_active'], _('active'), u'', u''),
1927 gmTools.bool2subst(i['is_cause_of_death'], _('fatal'), u'', u'')
1928 ] for i in issues
1929 ]
1930 lctrl.set_string_items(items = items)
1931 lctrl.set_data(data = issues)
1932 #-----------------------------------------
1933 return gmListWidgets.get_choices_from_list (
1934 parent = parent,
1935 msg = _('\nSelect the health issues !\n'),
1936 caption = _('Showing health issues ...'),
1937 columns = [u'', _('Health issue'), u'', u'', u''],
1938 single_selection = False,
1939 #edit_callback = edit,
1940 #new_callback = edit,
1941 #delete_callback = delete,
1942 refresh_callback = refresh
1943 )
1944 #----------------------------------------------------------------
1946
1947 # FIXME: support pre-selection
1948
1950
1951 issues = kwargs['issues']
1952 del kwargs['issues']
1953
1954 gmListWidgets.cGenericListSelectorDlg.__init__(self, *args, **kwargs)
1955
1956 self.SetTitle(_('Select the health issues you are interested in ...'))
1957 self._LCTRL_items.set_columns([u'', _('Health Issue'), u'', u'', u''])
1958
1959 for issue in issues:
1960 if issue['is_confidential']:
1961 row_num = self._LCTRL_items.InsertStringItem(sys.maxint, label = _('confidential'))
1962 self._LCTRL_items.SetItemTextColour(row_num, col=wx.NamedColour('RED'))
1963 else:
1964 row_num = self._LCTRL_items.InsertStringItem(sys.maxint, label = u'')
1965
1966 self._LCTRL_items.SetStringItem(index = row_num, col = 1, label = issue['description'])
1967 if issue['clinically_relevant']:
1968 self._LCTRL_items.SetStringItem(index = row_num, col = 2, label = _('relevant'))
1969 if issue['is_active']:
1970 self._LCTRL_items.SetStringItem(index = row_num, col = 3, label = _('active'))
1971 if issue['is_cause_of_death']:
1972 self._LCTRL_items.SetStringItem(index = row_num, col = 4, label = _('fatal'))
1973
1974 self._LCTRL_items.set_column_widths()
1975 self._LCTRL_items.set_data(data = issues)
1976 #----------------------------------------------------------------
1978 """Let the user select a health issue.
1979
1980 The user can select a health issue from the existing issues
1981 of a patient. Selection is done with a phrasewheel so the user
1982 can type the issue name and matches will be shown. Typing
1983 "*" will show the entire list of issues. Inactive issues
1984 will be marked as such. If the user types an issue name not
1985 in the list of existing issues a new issue can be created
1986 from it if the programmer activated that feature.
1987
1988 If keyword <patient_id> is set to None or left out the control
1989 will listen to patient change signals and therefore act on
1990 gmPerson.gmCurrentPatient() changes.
1991 """
1993
1994 ctxt = {'ctxt_pat': {'where_part': u'pk_patient=%(pat)s', 'placeholder': u'pat'}}
1995
1996 mp = gmMatchProvider.cMatchProvider_SQL2 (
1997 # FIXME: consider clin.health_issue.clinically_relevant
1998 queries = [
1999 u"""
2000 SELECT
2001 data,
2002 field_label,
2003 list_label
2004 FROM ((
2005 SELECT
2006 pk_health_issue AS data,
2007 description AS field_label,
2008 description AS list_label
2009 FROM clin.v_health_issues
2010 WHERE
2011 is_active IS true
2012 AND
2013 description %(fragment_condition)s
2014 AND
2015 %(ctxt_pat)s
2016
2017 ) UNION (
2018
2019 SELECT
2020 pk_health_issue AS data,
2021 description AS field_label,
2022 description || _(' (inactive)') AS list_label
2023 FROM clin.v_health_issues
2024 WHERE
2025 is_active IS false
2026 AND
2027 description %(fragment_condition)s
2028 AND
2029 %(ctxt_pat)s
2030 )) AS union_query
2031 ORDER BY
2032 list_label"""],
2033 context = ctxt
2034 )
2035
2036 try: kwargs['patient_id']
2037 except KeyError: kwargs['patient_id'] = None
2038
2039 if kwargs['patient_id'] is None:
2040 self.use_current_patient = True
2041 self.__register_patient_change_signals()
2042 pat = gmPerson.gmCurrentPatient()
2043 if pat.connected:
2044 mp.set_context('pat', pat.ID)
2045 else:
2046 self.use_current_patient = False
2047 self.__patient_id = int(kwargs['patient_id'])
2048 mp.set_context('pat', self.__patient_id)
2049
2050 del kwargs['patient_id']
2051
2052 gmPhraseWheel.cPhraseWheel.__init__ (
2053 self,
2054 *args,
2055 **kwargs
2056 )
2057 self.matcher = mp
2058 #--------------------------------------------------------
2059 # external API
2060 #--------------------------------------------------------
2062 if self.use_current_patient:
2063 return False
2064 self.__patient_id = int(patient_id)
2065 self.set_context('pat', self.__patient_id)
2066 return True
2067 #--------------------------------------------------------
2069 issue_name = self.GetValue().strip()
2070 if issue_name == u'':
2071 gmDispatcher.send(signal = u'statustext', msg = _('Cannot create health issue without name.'), beep = True)
2072 _log.debug('cannot create health issue without name')
2073 return
2074
2075 if self.use_current_patient:
2076 pat = gmPerson.gmCurrentPatient()
2077 else:
2078 pat = gmPerson.cPatient(aPK_obj = self.__patient_id)
2079
2080 emr = pat.get_emr()
2081 issue = emr.add_health_issue(issue_name = issue_name)
2082
2083 if issue is None:
2084 self.data = {}
2085 else:
2086 self.SetText (
2087 value = issue_name,
2088 data = issue['pk_health_issue']
2089 )
2090 #--------------------------------------------------------
2093 #--------------------------------------------------------
2094 # internal API
2095 #--------------------------------------------------------
2097 gmDispatcher.connect(self._pre_patient_selection, u'pre_patient_selection')
2098 gmDispatcher.connect(self._post_patient_selection, u'post_patient_selection')
2099 #--------------------------------------------------------
2102 #--------------------------------------------------------
2104 if self.use_current_patient:
2105 patient = gmPerson.gmCurrentPatient()
2106 self.set_context('pat', patient.ID)
2107 return True
2108 #------------------------------------------------------------
2109 from Gnumed.wxGladeWidgets import wxgIssueSelectionDlg
2110
2112
2114 try:
2115 msg = kwargs['message']
2116 except KeyError:
2117 msg = None
2118 del kwargs['message']
2119 wxgIssueSelectionDlg.wxgIssueSelectionDlg.__init__(self, *args, **kwargs)
2120 if msg is not None:
2121 self._lbl_message.SetLabel(label=msg)
2122 #--------------------------------------------------------
2133 #------------------------------------------------------------
2134 from Gnumed.wxGladeWidgets import wxgHealthIssueEditAreaPnl
2135
2136 -class cHealthIssueEditAreaPnl(gmEditArea.cGenericEditAreaMixin, wxgHealthIssueEditAreaPnl.wxgHealthIssueEditAreaPnl):
2137 """Panel encapsulating health issue edit area functionality."""
2138
2140
2141 try:
2142 issue = kwargs['issue']
2143 except KeyError:
2144 issue = None
2145
2146 wxgHealthIssueEditAreaPnl.wxgHealthIssueEditAreaPnl.__init__(self, *args, **kwargs)
2147
2148 gmEditArea.cGenericEditAreaMixin.__init__(self)
2149
2150 # FIXME: include more sources: coding systems/other database columns
2151 mp = gmMatchProvider.cMatchProvider_SQL2 (
2152 queries = [u"SELECT DISTINCT ON (description) description, description FROM clin.health_issue WHERE description %(fragment_condition)s LIMIT 50"]
2153 )
2154 mp.setThresholds(1, 3, 5)
2155 self._PRW_condition.matcher = mp
2156
2157 mp = gmMatchProvider.cMatchProvider_SQL2 (
2158 queries = [u"""
2159 select distinct on (grouping) grouping, grouping from (
2160
2161 select rank, grouping from ((
2162
2163 select
2164 grouping,
2165 1 as rank
2166 from
2167 clin.health_issue
2168 where
2169 grouping %%(fragment_condition)s
2170 and
2171 (select True from clin.encounter where fk_patient = %s and pk = clin.health_issue.fk_encounter)
2172
2173 ) union (
2174
2175 select
2176 grouping,
2177 2 as rank
2178 from
2179 clin.health_issue
2180 where
2181 grouping %%(fragment_condition)s
2182
2183 )) as union_result
2184
2185 order by rank
2186
2187 ) as order_result
2188
2189 limit 50""" % gmPerson.gmCurrentPatient().ID
2190 ]
2191 )
2192 mp.setThresholds(1, 3, 5)
2193 self._PRW_grouping.matcher = mp
2194
2195 self._PRW_age_noted.add_callback_on_lose_focus(self._on_leave_age_noted)
2196 self._PRW_year_noted.add_callback_on_lose_focus(self._on_leave_year_noted)
2197
2198 self._PRW_age_noted.add_callback_on_modified(self._on_modified_age_noted)
2199 self._PRW_year_noted.add_callback_on_modified(self._on_modified_year_noted)
2200
2201 self._PRW_year_noted.Enable(True)
2202
2203 self._PRW_codes.add_callback_on_lose_focus(self._on_leave_codes)
2204
2205 self.data = issue
2206 #----------------------------------------------------------------
2207 # generic Edit Area mixin API
2208 #----------------------------------------------------------------
2210
2211 if self._PRW_condition.GetValue().strip() == '':
2212 self._PRW_condition.display_as_valid(False)
2213 self._PRW_condition.SetFocus()
2214 return False
2215 self._PRW_condition.display_as_valid(True)
2216 self._PRW_condition.Refresh()
2217
2218 # FIXME: sanity check age/year diagnosed
2219 age_noted = self._PRW_age_noted.GetValue().strip()
2220 if age_noted != '':
2221 if gmDateTime.str2interval(str_interval = age_noted) is None:
2222 self._PRW_age_noted.display_as_valid(False)
2223 self._PRW_age_noted.SetFocus()
2224 return False
2225 self._PRW_age_noted.display_as_valid(True)
2226
2227 return True
2228 #----------------------------------------------------------------
2230 pat = gmPerson.gmCurrentPatient()
2231 emr = pat.get_emr()
2232
2233 issue = emr.add_health_issue(issue_name = self._PRW_condition.GetValue().strip())
2234
2235 side = u''
2236 if self._ChBOX_left.GetValue():
2237 side += u's'
2238 if self._ChBOX_right.GetValue():
2239 side += u'd'
2240 issue['laterality'] = side
2241
2242 issue['summary'] = self._TCTRL_status.GetValue().strip()
2243 issue['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
2244 issue['grouping'] = self._PRW_grouping.GetValue().strip()
2245 issue['is_active'] = self._ChBOX_active.GetValue()
2246 issue['clinically_relevant'] = self._ChBOX_relevant.GetValue()
2247 issue['is_confidential'] = self._ChBOX_confidential.GetValue()
2248 issue['is_cause_of_death'] = self._ChBOX_caused_death.GetValue()
2249
2250 age_noted = self._PRW_age_noted.GetData()
2251 if age_noted is not None:
2252 issue['age_noted'] = age_noted
2253
2254 issue.save()
2255
2256 issue.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
2257
2258 self.data = issue
2259 return True
2260 #----------------------------------------------------------------
2262
2263 self.data['description'] = self._PRW_condition.GetValue().strip()
2264
2265 side = u''
2266 if self._ChBOX_left.GetValue():
2267 side += u's'
2268 if self._ChBOX_right.GetValue():
2269 side += u'd'
2270 self.data['laterality'] = side
2271
2272 self.data['summary'] = self._TCTRL_status.GetValue().strip()
2273 self.data['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
2274 self.data['grouping'] = self._PRW_grouping.GetValue().strip()
2275 self.data['is_active'] = bool(self._ChBOX_active.GetValue())
2276 self.data['clinically_relevant'] = bool(self._ChBOX_relevant.GetValue())
2277 self.data['is_confidential'] = bool(self._ChBOX_confidential.GetValue())
2278 self.data['is_cause_of_death'] = bool(self._ChBOX_caused_death.GetValue())
2279
2280 age_noted = self._PRW_age_noted.GetData()
2281 if age_noted is not None:
2282 self.data['age_noted'] = age_noted
2283
2284 self.data.save()
2285 self.data.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
2286
2287 return True
2288 #----------------------------------------------------------------
2290 self._PRW_condition.SetText()
2291 self._ChBOX_left.SetValue(0)
2292 self._ChBOX_right.SetValue(0)
2293 self._PRW_codes.SetText()
2294 self._on_leave_codes()
2295 self._PRW_certainty.SetText()
2296 self._PRW_grouping.SetText()
2297 self._TCTRL_status.SetValue(u'')
2298 self._PRW_age_noted.SetText()
2299 self._PRW_year_noted.SetText()
2300 self._ChBOX_active.SetValue(1)
2301 self._ChBOX_relevant.SetValue(1)
2302 self._ChBOX_confidential.SetValue(0)
2303 self._ChBOX_caused_death.SetValue(0)
2304
2305 return True
2306 #----------------------------------------------------------------
2308 self._PRW_condition.SetText(self.data['description'])
2309
2310 lat = gmTools.coalesce(self.data['laterality'], '')
2311 if lat.find('s') == -1:
2312 self._ChBOX_left.SetValue(0)
2313 else:
2314 self._ChBOX_left.SetValue(1)
2315 if lat.find('d') == -1:
2316 self._ChBOX_right.SetValue(0)
2317 else:
2318 self._ChBOX_right.SetValue(1)
2319
2320 val, data = self._PRW_codes.generic_linked_codes2item_dict(self.data.generic_codes)
2321 self._PRW_codes.SetText(val, data)
2322 self._on_leave_codes()
2323
2324 if self.data['diagnostic_certainty_classification'] is not None:
2325 self._PRW_certainty.SetData(data = self.data['diagnostic_certainty_classification'])
2326 self._PRW_grouping.SetText(gmTools.coalesce(self.data['grouping'], u''))
2327 self._TCTRL_status.SetValue(gmTools.coalesce(self.data['summary'], u''))
2328
2329 if self.data['age_noted'] is None:
2330 self._PRW_age_noted.SetText()
2331 else:
2332 self._PRW_age_noted.SetText (
2333 value = '%sd' % self.data['age_noted'].days,
2334 data = self.data['age_noted']
2335 )
2336
2337 self._ChBOX_active.SetValue(self.data['is_active'])
2338 self._ChBOX_relevant.SetValue(self.data['clinically_relevant'])
2339 self._ChBOX_confidential.SetValue(self.data['is_confidential'])
2340 self._ChBOX_caused_death.SetValue(self.data['is_cause_of_death'])
2341
2342 # this dance should assure self._PRW_year_noted gets set -- but it doesn't ...
2343 # self._PRW_age_noted.SetFocus()
2344 # self._PRW_condition.SetFocus()
2345
2346 return True
2347 #----------------------------------------------------------------
2350 #--------------------------------------------------------
2351 # internal helpers
2352 #--------------------------------------------------------
2354 if not self._PRW_codes.IsModified():
2355 return True
2356
2357 self._TCTRL_code_details.SetValue(u'- ' + u'\n- '.join([ c['list_label'] for c in self._PRW_codes.GetData() ]))
2358 #--------------------------------------------------------
2360
2361 if not self._PRW_age_noted.IsModified():
2362 return True
2363
2364 str_age = self._PRW_age_noted.GetValue().strip()
2365
2366 if str_age == u'':
2367 wx.CallAfter(self._PRW_year_noted.SetText, u'', None, True)
2368 return True
2369
2370 age = gmDateTime.str2interval(str_interval = str_age)
2371
2372 if age is None:
2373 gmDispatcher.send(signal='statustext', msg=_('Cannot parse [%s] into valid interval.') % str_age)
2374 self._PRW_age_noted.SetBackgroundColour('pink')
2375 self._PRW_age_noted.Refresh()
2376 wx.CallAfter(self._PRW_year_noted.SetText, u'', None, True)
2377 return True
2378
2379 pat = gmPerson.gmCurrentPatient()
2380 if pat['dob'] is not None:
2381 max_age = pydt.datetime.now(tz=pat['dob'].tzinfo) - pat['dob']
2382
2383 if age >= max_age:
2384 gmDispatcher.send (
2385 signal = 'statustext',
2386 msg = _(
2387 'Health issue cannot have been noted at age %s. Patient is only %s old.'
2388 ) % (age, pat.get_medical_age())
2389 )
2390 self._PRW_age_noted.SetBackgroundColour('pink')
2391 self._PRW_age_noted.Refresh()
2392 wx.CallAfter(self._PRW_year_noted.SetText, u'', None, True)
2393 return True
2394
2395 self._PRW_age_noted.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2396 self._PRW_age_noted.Refresh()
2397 self._PRW_age_noted.SetData(data=age)
2398
2399 if pat['dob'] is not None:
2400 fts = gmDateTime.cFuzzyTimestamp (
2401 timestamp = pat['dob'] + age,
2402 accuracy = gmDateTime.acc_months
2403 )
2404 wx.CallAfter(self._PRW_year_noted.SetText, str(fts), fts)
2405 # if we do this we will *always* navigate there, regardless of TAB vs ALT-TAB
2406 #wx.CallAfter(self._ChBOX_active.SetFocus)
2407 # if we do the following instead it will take us to the save/update button ...
2408 #wx.CallAfter(self.Navigate)
2409
2410 return True
2411 #--------------------------------------------------------
2413
2414 if not self._PRW_year_noted.IsModified():
2415 return True
2416
2417 year_noted = self._PRW_year_noted.GetData()
2418
2419 if year_noted is None:
2420 if self._PRW_year_noted.GetValue().strip() == u'':
2421 wx.CallAfter(self._PRW_age_noted.SetText, u'', None, True)
2422 return True
2423 self._PRW_year_noted.SetBackgroundColour('pink')
2424 self._PRW_year_noted.Refresh()
2425 wx.CallAfter(self._PRW_age_noted.SetText, u'', None, True)
2426 return True
2427
2428 year_noted = year_noted.get_pydt()
2429
2430 if year_noted >= pydt.datetime.now(tz=year_noted.tzinfo):
2431 gmDispatcher.send(signal='statustext', msg=_('Condition diagnosed in the future.'))
2432 self._PRW_year_noted.SetBackgroundColour('pink')
2433 self._PRW_year_noted.Refresh()
2434 wx.CallAfter(self._PRW_age_noted.SetText, u'', None, True)
2435 return True
2436
2437 self._PRW_year_noted.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
2438 self._PRW_year_noted.Refresh()
2439
2440 pat = gmPerson.gmCurrentPatient()
2441 if pat['dob'] is not None:
2442 issue_age = year_noted - pat['dob']
2443 str_age = gmDateTime.format_interval_medically(interval = issue_age)
2444 wx.CallAfter(self._PRW_age_noted.SetText, str_age, issue_age)
2445
2446 return True
2447 #--------------------------------------------------------
2451 #--------------------------------------------------------
2455 #================================================================
2456 # diagnostic certainty related widgets/functions
2457 #----------------------------------------------------------------
2459
2461
2462 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
2463
2464 self.selection_only = False # can be NULL, too
2465
2466 mp = gmMatchProvider.cMatchProvider_FixedList (
2467 aSeq = [
2468 {'data': u'A', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'A'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'A'), 'weight': 1},
2469 {'data': u'B', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'B'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'B'), 'weight': 1},
2470 {'data': u'C', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'C'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'C'), 'weight': 1},
2471 {'data': u'D', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'D'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'D'), 'weight': 1}
2472 ]
2473 )
2474 mp.setThresholds(1, 2, 4)
2475 self.matcher = mp
2476
2477 self.SetToolTipString(_(
2478 "The diagnostic classification or grading of this assessment.\n"
2479 "\n"
2480 "This documents how certain one is about this being a true diagnosis."
2481 ))
2482 #================================================================
2483 # MAIN
2484 #----------------------------------------------------------------
2485 if __name__ == '__main__':
2486
2487 from Gnumed.business import gmPersonSearch
2488 from Gnumed.wxpython import gmPatSearchWidgets
2489
2490 #================================================================
2492 """
2493 Test application for testing EMR struct widgets
2494 """
2495 #--------------------------------------------------------
2497 """
2498 Create test application UI
2499 """
2500 frame = wx.Frame (
2501 None,
2502 -4,
2503 'Testing EMR struct widgets',
2504 size=wx.Size(600, 400),
2505 style=wx.DEFAULT_FRAME_STYLE | wx.NO_FULL_REPAINT_ON_RESIZE
2506 )
2507 filemenu= wx.Menu()
2508 filemenu.AppendSeparator()
2509 filemenu.Append(ID_EXIT,"E&xit"," Terminate test application")
2510
2511 # Creating the menubar.
2512 menuBar = wx.MenuBar()
2513 menuBar.Append(filemenu,"&File")
2514
2515 frame.SetMenuBar(menuBar)
2516
2517 txt = wx.StaticText( frame, -1, _("Select desired test option from the 'File' menu"),
2518 wx.DefaultPosition, wx.DefaultSize, 0 )
2519
2520 # event handlers
2521 wx.EVT_MENU(frame, ID_EXIT, self.OnCloseWindow)
2522
2523 # patient EMR
2524 self.__pat = gmPerson.gmCurrentPatient()
2525
2526 frame.Show(1)
2527 return 1
2528 #--------------------------------------------------------
2534 #----------------------------------------------------------------
2536 app = wx.PyWidgetTester(size = (200, 300))
2537 emr = pat.get_emr()
2538 enc = emr.active_encounter
2539 #enc = gmEMRStructItems.cEncounter(1)
2540 pnl = cEncounterEditAreaPnl(app.frame, -1, encounter=enc)
2541 app.frame.Show(True)
2542 app.MainLoop()
2543 return
2544 #----------------------------------------------------------------
2546 app = wx.PyWidgetTester(size = (200, 300))
2547 emr = pat.get_emr()
2548 enc = emr.active_encounter
2549 #enc = gmEMRStructItems.cEncounter(1)
2550
2551 dlg = cEncounterEditAreaDlg(parent=app.frame, id=-1, size = (400,400), encounter=enc)
2552 dlg.ShowModal()
2553
2554 # pnl = cEncounterEditAreaDlg(app.frame, -1, encounter=enc)
2555 # app.frame.Show(True)
2556 # app.MainLoop()
2557 #----------------------------------------------------------------
2559 app = wx.PyWidgetTester(size = (200, 300))
2560 emr = pat.get_emr()
2561 epi = emr.get_episodes()[0]
2562 pnl = cEpisodeEditAreaPnl(app.frame, -1, episode=epi)
2563 app.frame.Show(True)
2564 app.MainLoop()
2565 #----------------------------------------------------------------
2567 app = wx.PyWidgetTester(size = (200, 300))
2568 emr = pat.get_emr()
2569 epi = emr.get_episodes()[0]
2570 edit_episode(parent=app.frame, episode=epi)
2571 #----------------------------------------------------------------
2573 app = wx.PyWidgetTester(size = (400, 40))
2574 app.SetWidget(cHospitalStayPhraseWheel, id=-1, size=(180,20), pos=(10,20))
2575 app.MainLoop()
2576 #----------------------------------------------------------------
2578 app = wx.PyWidgetTester(size = (400, 40))
2579 app.SetWidget(cEpisodeSelectionPhraseWheel, id=-1, size=(180,20), pos=(10,20))
2580 # app.SetWidget(cEpisodeSelectionPhraseWheel, id=-1, size=(350,20), pos=(10,20), patient_id=pat.ID)
2581 app.MainLoop()
2582 #----------------------------------------------------------------
2584 app = wx.PyWidgetTester(size = (200, 300))
2585 edit_health_issue(parent=app.frame, issue=None)
2586 #----------------------------------------------------------------
2588 app = wx.PyWidgetTester(size = (200, 300))
2589 app.SetWidget(cHealthIssueEditAreaPnl, id=-1, size = (400,400))
2590 app.MainLoop()
2591 #----------------------------------------------------------------
2595 #================================================================
2596
2597 if (len(sys.argv) > 1) and (sys.argv[1] == 'test'):
2598
2599 gmI18N.activate_locale()
2600 gmI18N.install_domain()
2601 gmDateTime.init()
2602
2603 # obtain patient
2604 pat = gmPersonSearch.ask_for_patient()
2605 if pat is None:
2606 print "No patient. Exiting gracefully..."
2607 sys.exit(0)
2608 gmPatSearchWidgets.set_active_patient(patient=pat)
2609
2610 # try:
2611 # lauch emr dialogs test application
2612 # app = testapp(0)
2613 # app.MainLoop()
2614 # except StandardError:
2615 # _log.exception("unhandled exception caught !")
2616 # but re-raise them
2617 # raise
2618
2619 #test_encounter_edit_area_panel()
2620 #test_encounter_edit_area_dialog()
2621 #test_epsiode_edit_area_pnl()
2622 #test_episode_edit_area_dialog()
2623 #test_health_issue_edit_area_dlg()
2624 #test_episode_selection_prw()
2625 #test_hospital_stay_prw()
2626 test_edit_procedure()
2627
2628 #================================================================
2629
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Fri Jul 12 03:56:43 2013 | http://epydoc.sourceforge.net |