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