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