| Home | Trees | Indices | Help |
|
|---|
|
|
1 """GNUmed EMR structure editors
2
3 This module contains widgets to create and edit EMR structural
4 elements (issues, enconters, episodes).
5
6 This is based on initial work and ideas by Syan <kittylitter@swiftdsl.com.au>
7 and Karsten <Karsten.Hilbert@gmx.net>.
8 """
9 #================================================================
10 __author__ = "cfmoro1976@yahoo.es, karsten.hilbert@gmx.net"
11 __license__ = "GPL v2 or later"
12
13 # stdlib
14 import sys
15 import time
16 import logging
17 import datetime as pydt
18
19
20 # 3rd party
21 import wx
22
23
24 # GNUmed
25 if __name__ == '__main__':
26 sys.path.insert(0, '../../')
27 from Gnumed.pycommon import gmI18N
28 from Gnumed.pycommon import gmExceptions
29 from Gnumed.pycommon import gmCfg
30 from Gnumed.pycommon import gmDateTime
31 from Gnumed.pycommon import gmTools
32 from Gnumed.pycommon import gmDispatcher
33 from Gnumed.pycommon import gmMatchProvider
34
35 from Gnumed.business import gmEMRStructItems
36 from Gnumed.business import gmPraxis
37 from Gnumed.business import gmPerson
38
39 from Gnumed.wxpython import gmPhraseWheel
40 from Gnumed.wxpython import gmGuiHelpers
41 from Gnumed.wxpython import gmListWidgets
42 from Gnumed.wxpython import gmEditArea
43 from Gnumed.wxpython import gmOrganizationWidgets
44
45
46 _log = logging.getLogger('gm.ui')
47
48 #================================================================
49 # EMR access helper functions
50 #----------------------------------------------------------------
52 """Spin time in seconds."""
53 if time2spin == 0:
54 return
55 sleep_time = 0.1
56 total_rounds = int(time2spin / sleep_time)
57 if total_rounds < 1:
58 return
59 rounds = 0
60 while rounds < total_rounds:
61 wx.Yield()
62 time.sleep(sleep_time)
63 rounds += 1
64 #================================================================
65 # performed procedure related widgets/functions
66 #----------------------------------------------------------------
68
69 pat = gmPerson.gmCurrentPatient()
70 emr = pat.get_emr()
71
72 if parent is None:
73 parent = wx.GetApp().GetTopWindow()
74 #-----------------------------------------
75 def edit(procedure=None):
76 return edit_procedure(parent = parent, procedure = procedure)
77 #-----------------------------------------
78 def delete(procedure=None):
79 if gmEMRStructItems.delete_performed_procedure(procedure = procedure['pk_procedure']):
80 return True
81
82 gmDispatcher.send (
83 signal = u'statustext',
84 msg = _('Cannot delete performed procedure.'),
85 beep = True
86 )
87 return False
88 #-----------------------------------------
89 def refresh(lctrl):
90 procs = emr.get_performed_procedures()
91
92 items = [
93 [
94 u'%s%s' % (
95 p['clin_when'].strftime('%Y-%m-%d'),
96 gmTools.bool2subst (
97 p['is_ongoing'],
98 _(' (ongoing)'),
99 gmTools.coalesce (
100 initial = p['clin_end'],
101 instead = u'',
102 template_initial = u' - %s',
103 function_initial = ('strftime', u'%Y-%m-%d')
104 )
105 )
106 ),
107 u'%s @ %s' % (p['unit'], p['organization']),
108 p['episode'],
109 p['performed_procedure']
110 ] for p in procs
111 ]
112 lctrl.set_string_items(items = items)
113 lctrl.set_data(data = procs)
114 #-----------------------------------------
115 gmListWidgets.get_choices_from_list (
116 parent = parent,
117 msg = _('\nSelect the procedure you want to edit !\n'),
118 caption = _('Editing performed procedures ...'),
119 columns = [_('When'), _('Where'), _('Episode'), _('Procedure')],
120 single_selection = True,
121 edit_callback = edit,
122 new_callback = edit,
123 delete_callback = delete,
124 refresh_callback = refresh
125 )
126 #----------------------------------------------------------------
128 ea = cProcedureEAPnl(parent = parent, id = -1)
129 ea.data = procedure
130 ea.mode = gmTools.coalesce(procedure, 'new', 'edit')
131 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = True)
132 dlg.SetTitle(gmTools.coalesce(procedure, _('Adding a procedure'), _('Editing a procedure')))
133 if dlg.ShowModal() == wx.ID_OK:
134 dlg.Destroy()
135 return True
136 dlg.Destroy()
137 return False
138 #----------------------------------------------------------------
139 from Gnumed.wxGladeWidgets import wxgProcedureEAPnl
140
142
144 wxgProcedureEAPnl.wxgProcedureEAPnl.__init__(self, *args, **kwargs)
145 gmEditArea.cGenericEditAreaMixin.__init__(self)
146
147 self.mode = 'new'
148 self.data = None
149
150 self.__init_ui()
151 #----------------------------------------------------------------
153 self._PRW_hospital_stay.add_callback_on_lose_focus(callback = self._on_hospital_stay_lost_focus)
154 self._PRW_hospital_stay.set_context(context = 'pat', val = gmPerson.gmCurrentPatient().ID)
155 self._PRW_location.add_callback_on_lose_focus(callback = self._on_location_lost_focus)
156 self._DPRW_date.add_callback_on_lose_focus(callback = self._on_start_lost_focus)
157 self._DPRW_end.add_callback_on_lose_focus(callback = self._on_end_lost_focus)
158
159 # procedure
160 mp = gmMatchProvider.cMatchProvider_SQL2 (
161 queries = [
162 u"""
163 select distinct on (narrative) narrative, narrative
164 from clin.procedure
165 where narrative %(fragment_condition)s
166 order by narrative
167 limit 25
168 """ ]
169 )
170 mp.setThresholds(2, 4, 6)
171 self._PRW_procedure.matcher = mp
172 #----------------------------------------------------------------
174 stay = self._PRW_hospital_stay.GetData()
175 if stay is None:
176 self._PRW_hospital_stay.SetText()
177 self._PRW_location.Enable(True)
178 self._PRW_location.display_as_disabled(False)
179 self._PRW_episode.Enable(True)
180 self._PRW_episode.display_as_disabled(False)
181 self._LBL_hospital_details.SetLabel(u'')
182 else:
183 self._PRW_location.SetText()
184 self._PRW_location.Enable(False)
185 self._PRW_location.display_as_disabled(True)
186 self._PRW_episode.SetText()
187 self._PRW_episode.Enable(False)
188 self._PRW_episode.display_as_disabled(True)
189 self._LBL_hospital_details.SetLabel(gmEMRStructItems.cHospitalStay(aPK_obj = stay).format())
190 #----------------------------------------------------------------
192 loc = self._PRW_location.GetData()
193 if loc is None:
194 self._PRW_hospital_stay.Enable(True)
195 self._PRW_hospital_stay.display_as_disabled(False)
196 self._PRW_episode.Enable(False)
197 self._PRW_episode.display_as_disabled(True)
198 else:
199 self._PRW_hospital_stay.SetText()
200 self._PRW_hospital_stay.Enable(False)
201 self._PRW_hospital_stay.display_as_disabled(True)
202 self._PRW_episode.Enable(True)
203 self._PRW_episode.display_as_disabled(False)
204 #----------------------------------------------------------------
206 if not self._DPRW_date.is_valid_timestamp():
207 return
208 end = self._DPRW_end.GetData()
209 if end is None:
210 return
211 end = end.get_pydt()
212 start = self._DPRW_date.GetData().get_pydt()
213 if start < end:
214 return
215 self._DPRW_date.display_as_valid(False)
216 #----------------------------------------------------------------
218 end = self._DPRW_end.GetData()
219 if end is None:
220 self._CHBOX_ongoing.Enable(True)
221 self._DPRW_end.display_as_valid(True)
222 else:
223 self._CHBOX_ongoing.Enable(False)
224 end = end.get_pydt()
225 now = gmDateTime.pydt_now_here()
226 if end > now:
227 self._CHBOX_ongoing.SetValue(True)
228 else:
229 self._CHBOX_ongoing.SetValue(False)
230 start = self._DPRW_date.GetData()
231 if start is None:
232 self._DPRW_end.display_as_valid(True)
233 else:
234 start = start.get_pydt()
235 if end > start:
236 self._DPRW_end.display_as_valid(True)
237 else:
238 self._DPRW_end.display_as_valid(False)
239 #----------------------------------------------------------------
240 # generic Edit Area mixin API
241 #----------------------------------------------------------------
243
244 has_errors = False
245
246 if not self._DPRW_date.is_valid_timestamp():
247 self._DPRW_date.display_as_valid(False)
248 has_errors = True
249 else:
250 self._DPRW_date.display_as_valid(True)
251
252 start = self._DPRW_date.GetData()
253 end = self._DPRW_end.GetData()
254 self._DPRW_end.display_as_valid(True)
255 if end is not None:
256 end = end.get_pydt()
257 if start is not None:
258 start = start.get_pydt()
259 if end < start:
260 has_errors = True
261 self._DPRW_end.display_as_valid(False)
262 if self._CHBOX_ongoing.IsChecked():
263 now = gmDateTime.pydt_now_here()
264 if end < now:
265 has_errors = True
266 self._DPRW_end.display_as_valid(False)
267
268 if self._PRW_hospital_stay.GetData() is None:
269 if self._PRW_episode.GetValue().strip() == u'':
270 self._PRW_episode.display_as_valid(False)
271 has_errors = True
272 else:
273 self._PRW_episode.display_as_valid(True)
274 else:
275 self._PRW_episode.display_as_valid(True)
276
277 if (self._PRW_procedure.GetValue() is None) or (self._PRW_procedure.GetValue().strip() == u''):
278 self._PRW_procedure.display_as_valid(False)
279 has_errors = True
280 else:
281 self._PRW_procedure.display_as_valid(True)
282
283 invalid_location = (
284 (self._PRW_hospital_stay.GetData() is None) and (self._PRW_location.GetData() is None)
285 or
286 (self._PRW_hospital_stay.GetData() is not None) and (self._PRW_location.GetData() is not None)
287 )
288 if invalid_location:
289 self._PRW_hospital_stay.display_as_valid(False)
290 self._PRW_location.display_as_valid(False)
291 has_errors = True
292 else:
293 self._PRW_hospital_stay.display_as_valid(True)
294 self._PRW_location.display_as_valid(True)
295
296 gmDispatcher.send(signal = 'statustext', msg = _('Cannot save procedure.'), beep = True)
297
298 return (has_errors is False)
299 #----------------------------------------------------------------
301
302 pat = gmPerson.gmCurrentPatient()
303 emr = pat.get_emr()
304
305 stay = self._PRW_hospital_stay.GetData()
306 if stay is None:
307 epi = self._PRW_episode.GetData(can_create = True)
308 else:
309 epi = gmEMRStructItems.cHospitalStay(aPK_obj = stay)['pk_episode']
310
311 proc = emr.add_performed_procedure (
312 episode = epi,
313 location = self._PRW_location.GetData(),
314 hospital_stay = stay,
315 procedure = self._PRW_procedure.GetValue().strip()
316 )
317
318 proc['clin_when'] = self._DPRW_date.GetData().get_pydt()
319 if self._DPRW_end.GetData() is None:
320 proc['clin_end'] = None
321 else:
322 proc['clin_end'] = self._DPRW_end.GetData().get_pydt()
323 proc['is_ongoing'] = self._CHBOX_ongoing.IsChecked()
324 proc.save()
325
326 proc.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
327
328 self.data = proc
329
330 return True
331 #----------------------------------------------------------------
333 self.data['clin_when'] = self._DPRW_date.GetData().get_pydt()
334 self.data['is_ongoing'] = self._CHBOX_ongoing.IsChecked()
335 self.data['pk_org_unit'] = self._PRW_location.GetData()
336 self.data['pk_hospital_stay'] = self._PRW_hospital_stay.GetData()
337 self.data['performed_procedure'] = self._PRW_procedure.GetValue().strip()
338 if self._DPRW_end.GetData() is None:
339 self.data['clin_end'] = None
340 else:
341 self.data['clin_end'] = self._DPRW_end.GetData().get_pydt()
342 if self.data['pk_hospital_stay'] is None:
343 self.data['pk_episode'] = self._PRW_episode.GetData()
344 else:
345 self.data['pk_episode'] = gmEMRStructItems.cHospitalStay(aPK_obj = self._PRW_hospital_stay.GetData())['pk_episode']
346 self.data.save()
347
348 self.data.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
349
350 return True
351 #----------------------------------------------------------------
353 self._DPRW_date.SetText()
354 self._DPRW_end.SetText()
355 self._CHBOX_ongoing.SetValue(False)
356 self._CHBOX_ongoing.Enable(True)
357 self._PRW_hospital_stay.SetText()
358 self._LBL_hospital_details.SetLabel(u'')
359 self._PRW_location.SetText()
360 self._PRW_episode.SetText()
361 self._PRW_procedure.SetText()
362 self._PRW_codes.SetText()
363
364 self._PRW_procedure.SetFocus()
365 #----------------------------------------------------------------
367 self._DPRW_date.SetData(data = self.data['clin_when'])
368 if self.data['clin_end'] is None:
369 self._DPRW_end.SetText()
370 self._CHBOX_ongoing.Enable(True)
371 self._CHBOX_ongoing.SetValue(self.data['is_ongoing'])
372 else:
373 self._DPRW_end.SetData(data = self.data['clin_end'])
374 self._CHBOX_ongoing.Enable(False)
375 now = gmDateTime.pydt_now_here()
376 if self.data['clin_end'] > now:
377 self._CHBOX_ongoing.SetValue(True)
378 else:
379 self._CHBOX_ongoing.SetValue(False)
380 self._PRW_episode.SetText(value = self.data['episode'], data = self.data['pk_episode'])
381 self._PRW_procedure.SetText(value = self.data['performed_procedure'], data = self.data['performed_procedure'])
382
383 if self.data['pk_hospital_stay'] is None:
384 self._PRW_hospital_stay.SetText()
385 self._PRW_hospital_stay.Enable(False)
386 self._PRW_hospital_stay.display_as_disabled(True)
387 self._LBL_hospital_details.SetLabel(u'')
388 self._PRW_location.SetText(value = u'%s @ %s' % (self.data['unit'], self.data['organization']), data = self.data['pk_org_unit'])
389 self._PRW_location.Enable(True)
390 self._PRW_location.display_as_disabled(False)
391 self._PRW_episode.Enable(True)
392 self._PRW_episode.display_as_disabled(False)
393 else:
394 self._PRW_hospital_stay.SetText(value = u'%s @ %s' % (self.data['unit'], self.data['organization']), data = self.data['pk_hospital_stay'])
395 self._PRW_hospital_stay.Enable(True)
396 self._PRW_hospital_stay.display_as_disabled(False)
397 self._LBL_hospital_details.SetLabel(gmEMRStructItems.cHospitalStay(aPK_obj = self.data['pk_hospital_stay']).format())
398 self._PRW_location.SetText()
399 self._PRW_location.Enable(False)
400 self._PRW_location.display_as_disabled(True)
401 self._PRW_episode.Enable(False)
402 self._PRW_episode.display_as_disabled(True)
403
404 val, data = self._PRW_codes.generic_linked_codes2item_dict(self.data.generic_codes)
405 self._PRW_codes.SetText(val, data)
406
407 self._PRW_procedure.SetFocus()
408 #----------------------------------------------------------------
410 self._refresh_as_new()
411
412 self._PRW_episode.SetText(value = self.data['episode'], data = self.data['pk_episode'])
413
414 if self.data['pk_hospital_stay'] is None:
415 self._PRW_hospital_stay.SetText()
416 self._PRW_hospital_stay.Enable(False)
417 self._PRW_hospital_stay.display_as_disabled(True)
418 self._LBL_hospital_details.SetLabel(u'')
419 self._PRW_location.SetText(value = u'%s @ %s' % (self.data['unit'], self.data['organization']), data = self.data['pk_org_unit'])
420 self._PRW_location.Enable(True)
421 self._PRW_location.display_as_disabled(False)
422 self._PRW_episode.Enable(True)
423 self._PRW_episode.display_as_disabled(False)
424 else:
425 self._PRW_hospital_stay.SetText(value = u'%s @ %s' % (self.data['unit'], self.data['organization']), data = self.data['pk_hospital_stay'])
426 self._PRW_hospital_stay.Enable(True)
427 self._PRW_hospital_stay.display_as_disabled(False)
428 self._LBL_hospital_details.SetLabel(gmEMRStructItems.cHospitalStay(aPK_obj = self.data['pk_hospital_stay']).format())
429 self._PRW_location.SetText()
430 self._PRW_location.Enable(False)
431 self._PRW_location.display_as_disabled(True)
432 self._PRW_episode.Enable(False)
433 self._PRW_episode.display_as_disabled(True)
434
435 self._PRW_procedure.SetFocus()
436 #----------------------------------------------------------------
437 # event handlers
438 #----------------------------------------------------------------
443 #----------------------------------------------------------------
447 #----------------------------------------------------------------
449 if self._CHBOX_ongoing.IsChecked():
450 end = self._DPRW_end.GetData()
451 if end is None:
452 self._DPRW_end.display_as_valid(True)
453 else:
454 end = end.get_pydt()
455 now = gmDateTime.pydt_now_here()
456 if end > now:
457 self._DPRW_end.display_as_valid(True)
458 else:
459 self._DPRW_end.display_as_valid(False)
460 else:
461 self._DPRW_end.is_valid_timestamp()
462 event.Skip()
463
464 #================================================================
465 # hospitalizations related widgets/functions
466 #----------------------------------------------------------------
468
469 pat = gmPerson.gmCurrentPatient()
470 emr = pat.get_emr()
471
472 if parent is None:
473 parent = wx.GetApp().GetTopWindow()
474 #-----------------------------------------
475 def get_tooltip(stay=None):
476 if stay is None:
477 return None
478 return stay.format (
479 include_procedures = True,
480 include_docs = True
481 )
482 #-----------------------------------------
483 def edit(stay=None):
484 return edit_hospital_stay(parent = parent, hospital_stay = stay)
485 #-----------------------------------------
486 def delete(stay=None):
487 if gmEMRStructItems.delete_hospital_stay(stay = stay['pk_hospital_stay']):
488 return True
489 gmDispatcher.send (
490 signal = u'statustext',
491 msg = _('Cannot delete hospitalization.'),
492 beep = True
493 )
494 return False
495 #-----------------------------------------
496 def refresh(lctrl):
497 stays = emr.get_hospital_stays()
498 items = [
499 [
500 s['admission'].strftime('%Y-%m-%d'),
501 gmTools.coalesce(s['discharge'], u'', function_initial = ('strftime', '%Y-%m-%d')),
502 s['episode'],
503 u'%s @ %s' % (s['ward'], s['hospital'])
504 ] for s in stays
505 ]
506 lctrl.set_string_items(items = items)
507 lctrl.set_data(data = stays)
508 #-----------------------------------------
509 gmListWidgets.get_choices_from_list (
510 parent = parent,
511 msg = _("The patient's hospitalizations:\n"),
512 caption = _('Editing hospitalizations ...'),
513 columns = [_('Admission'), _('Discharge'), _('Reason'), _('Hospital')],
514 single_selection = True,
515 edit_callback = edit,
516 new_callback = edit,
517 delete_callback = delete,
518 refresh_callback = refresh,
519 list_tooltip_callback = get_tooltip
520 )
521
522 #----------------------------------------------------------------
524 ea = cHospitalStayEditAreaPnl(parent = parent, id = -1)
525 ea.data = hospital_stay
526 ea.mode = gmTools.coalesce(hospital_stay, 'new', 'edit')
527 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = True)
528 dlg.SetTitle(gmTools.coalesce(hospital_stay, _('Adding a hospitalization'), _('Editing a hospitalization')))
529 if dlg.ShowModal() == wx.ID_OK:
530 dlg.Destroy()
531 return True
532 dlg.Destroy()
533 return False
534
535 #----------------------------------------------------------------
537 """Phrasewheel to allow selection of a hospitalization."""
539
540 gmPhraseWheel.cPhraseWheel.__init__ (self, *args, **kwargs)
541
542 query = u"""
543 SELECT data, list_label, field_label FROM (
544 SELECT DISTINCT ON (data) * FROM ((
545
546 -- already-used org_units
547 SELECT
548 pk_org_unit
549 AS data,
550 ward || ' @ ' || hospital
551 AS list_label,
552 ward || ' @ ' || hospital
553 AS field_label,
554 1
555 AS rank
556 FROM
557 clin.v_hospital_stays
558 WHERE
559 ward %(fragment_condition)s
560 OR
561 hospital %(fragment_condition)s
562
563 ) UNION ALL (
564 -- wards
565 SELECT
566 pk_org_unit
567 AS data,
568 unit || ' (' || l10n_unit_category || ') @ ' || organization
569 AS list_label,
570 unit || ' @ ' || organization
571 AS field_label,
572 2
573 AS rank
574 FROM
575 dem.v_org_units
576 WHERE
577 unit_category = 'Ward'
578 AND
579 unit %(fragment_condition)s
580 AND
581 NOT EXISTS (
582 SELECT 1 FROM clin.v_hospital_stays WHERE clin.v_hospital_stays.pk_org_unit = dem.v_org_units.pk_org_unit
583 )
584
585 ) UNION ALL (
586 -- hospital units
587 SELECT
588 pk_org_unit
589 AS data,
590 unit || coalesce(' (' || l10n_unit_category || ')', '') || ' @ ' || organization || ' (' || l10n_organization_category || ')'
591 AS list_label,
592 unit || ' @ ' || organization
593 AS field_label,
594 3
595 AS rank
596 FROM
597 dem.v_org_units
598 WHERE
599 unit_category <> 'Ward'
600 AND
601 organization_category = 'Hospital'
602 AND
603 unit %(fragment_condition)s
604 AND
605 NOT EXISTS (
606 SELECT 1 FROM clin.v_hospital_stays WHERE clin.v_hospital_stays.pk_org_unit = dem.v_org_units.pk_org_unit
607 )
608
609 ) UNION ALL (
610 -- any other units
611 SELECT
612 pk_org_unit
613 AS data,
614 unit || coalesce(' (' || l10n_unit_category || ')', '') || ' @ ' || organization || ' (' || l10n_organization_category || ')'
615 AS list_label,
616 unit || ' @ ' || organization
617 AS field_label,
618 3
619 AS rank
620 FROM
621 dem.v_org_units
622 WHERE
623 unit_category <> 'Ward'
624 AND
625 organization_category <> 'Hospital'
626 AND
627 unit %(fragment_condition)s
628 AND
629 NOT EXISTS (
630 SELECT 1 FROM clin.v_hospital_stays WHERE clin.v_hospital_stays.pk_org_unit = dem.v_org_units.pk_org_unit
631 )
632 )) AS all_matches
633 ORDER BY data, rank
634 ) AS distinct_matches
635 ORDER BY rank, list_label
636 LIMIT 50
637 """
638
639 mp = gmMatchProvider.cMatchProvider_SQL2(queries = [query])
640 mp.setThresholds(2, 4, 6)
641 self.matcher = mp
642 self.selection_only = True
643
644 #----------------------------------------------------------------
646 """Phrasewheel to allow selection of a hospital-type org_unit."""
648
649 gmPhraseWheel.cPhraseWheel.__init__ (self, *args, **kwargs)
650
651 ctxt = {'ctxt_pat': {'where_part': u'pk_patient = %(pat)s and', 'placeholder': u'pat'}}
652
653 mp = gmMatchProvider.cMatchProvider_SQL2 (
654 queries = [
655 u"""
656 SELECT
657 pk_hospital_stay,
658 descr
659 FROM (
660 SELECT DISTINCT ON (pk_hospital_stay)
661 pk_hospital_stay,
662 descr
663 FROM
664 (SELECT
665 pk_hospital_stay,
666 (
667 to_char(admission, 'YYYY-Mon-DD')
668 || ' (' || ward || ' @ ' || hospital || '):'
669 || episode
670 || coalesce((' (' || health_issue || ')'), '')
671 ) AS descr
672 FROM
673 clin.v_hospital_stays
674 WHERE
675 %(ctxt_pat)s
676
677 hospital %(fragment_condition)s
678 OR
679 ward %(fragment_condition)s
680 OR
681 episode %(fragment_condition)s
682 OR
683 health_issue %(fragment_condition)s
684 ) AS the_stays
685 ) AS distinct_stays
686 ORDER BY descr
687 LIMIT 25
688 """ ],
689 context = ctxt
690 )
691 mp.setThresholds(3, 4, 6)
692 mp.set_context('pat', gmPerson.gmCurrentPatient().ID)
693
694 self.matcher = mp
695 self.selection_only = True
696
697 #----------------------------------------------------------------
698 from Gnumed.wxGladeWidgets import wxgHospitalStayEditAreaPnl
699
700 -class cHospitalStayEditAreaPnl(wxgHospitalStayEditAreaPnl.wxgHospitalStayEditAreaPnl, gmEditArea.cGenericEditAreaMixin):
701
703 wxgHospitalStayEditAreaPnl.wxgHospitalStayEditAreaPnl.__init__(self, *args, **kwargs)
704 gmEditArea.cGenericEditAreaMixin.__init__(self)
705 #----------------------------------------------------------------
706 # generic Edit Area mixin API
707 #----------------------------------------------------------------
709
710 valid = True
711
712 if self._PRW_episode.GetValue().strip() == u'':
713 valid = False
714 self._PRW_episode.display_as_valid(False)
715 gmDispatcher.send(signal = 'statustext', msg = _('Must select an episode or enter a name for a new one. Cannot save hospitalization.'), beep = True)
716 self._PRW_episode.SetFocus()
717
718 if not self._PRW_admission.is_valid_timestamp(allow_empty = False):
719 valid = False
720 gmDispatcher.send(signal = 'statustext', msg = _('Missing admission data. Cannot save hospitalization.'), beep = True)
721 self._PRW_admission.SetFocus()
722
723 if self._PRW_discharge.is_valid_timestamp(allow_empty = True):
724 if self._PRW_discharge.date is not None:
725 adm = self._PRW_admission.date
726 discharge = self._PRW_discharge.date
727 # normalize for comparison
728 discharge = discharge.replace (
729 hour = adm.hour,
730 minute = adm.minute,
731 second = adm.second,
732 microsecond = adm.microsecond
733 )
734 if adm is not None:
735 if discharge == adm:
736 self._PRW_discharge.SetData(discharge + pydt.timedelta(seconds = 1))
737 elif not self._PRW_discharge.date > self._PRW_admission.date:
738 valid = False
739 self._PRW_discharge.display_as_valid(False)
740 gmDispatcher.send(signal = 'statustext', msg = _('Discharge date must be empty or later than admission. Cannot save hospitalization.'), beep = True)
741 self._PRW_discharge.SetFocus()
742
743 if self._PRW_hospital.GetData() is None:
744 self._PRW_hospital.display_as_valid(False)
745 self.status_message = _('Must select a hospital. Cannot save hospitalization.')
746 self._PRW_hospital.SetFocus()
747 else:
748 self._PRW_hospital.display_as_valid(True)
749
750 return (valid is True)
751 #----------------------------------------------------------------
753
754 pat = gmPerson.gmCurrentPatient()
755 emr = pat.get_emr()
756 stay = emr.add_hospital_stay(episode = self._PRW_episode.GetData(can_create = True), fk_org_unit = self._PRW_hospital.GetData())
757 stay['comment'] = self._TCTRL_comment.GetValue().strip()
758 stay['admission'] = self._PRW_admission.GetData()
759 stay['discharge'] = self._PRW_discharge.GetData()
760 stay['comment'] = self._TCTRL_comment.GetValue()
761 stay.save_payload()
762
763 self.data = stay
764 return True
765 #----------------------------------------------------------------
767
768 self.data['pk_episode'] = self._PRW_episode.GetData(can_create = True)
769 self.data['pk_org_unit'] = self._PRW_hospital.GetData()
770 self.data['admission'] = self._PRW_admission.GetData()
771 self.data['discharge'] = self._PRW_discharge.GetData()
772 self.data['comment'] = self._TCTRL_comment.GetValue()
773 self.data.save_payload()
774
775 return True
776 #----------------------------------------------------------------
778 self._PRW_hospital.SetText(value = u'', data = None)
779 self._PRW_episode.SetText(value = u'')
780 self._PRW_admission.SetText(data = gmDateTime.pydt_now_here())
781 self._PRW_discharge.SetText()
782 self._TCTRL_comment.SetValue(u'')
783 self._PRW_hospital.SetFocus()
784 #----------------------------------------------------------------
786 self._PRW_hospital.SetText(value = u'%s @ %s' % (self.data['ward'], self.data['hospital']), data = self.data['pk_org_unit'])
787
788 if self.data['pk_episode'] is not None:
789 self._PRW_episode.SetText(value = self.data['episode'], data = self.data['pk_episode'])
790
791 self._PRW_admission.SetText(data = self.data['admission'])
792 self._PRW_discharge.SetText(data = self.data['discharge'])
793 self._TCTRL_comment.SetValue(self.data['comment'])
794
795 self._PRW_hospital.SetFocus()
796 #----------------------------------------------------------------
799
800 #================================================================
801 # episode related widgets/functions
802 #----------------------------------------------------------------
804 ea = cEpisodeEditAreaPnl(parent = parent, id = -1)
805 ea.data = episode
806 ea.mode = gmTools.coalesce(episode, 'new', 'edit')
807 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = True)
808 dlg.SetTitle(gmTools.coalesce(episode, _('Adding a new episode'), _('Editing an episode')))
809 if dlg.ShowModal() == wx.ID_OK:
810 return True
811 return False
812 #----------------------------------------------------------------
814
815 created_new_issue = False
816
817 try:
818 issue = gmEMRStructItems.cHealthIssue(name = episode['description'], patient = episode['pk_patient'])
819 except gmExceptions.NoSuchBusinessObjectError:
820 issue = None
821
822 if issue is None:
823 issue = emr.add_health_issue(issue_name = episode['description'])
824 created_new_issue = True
825 else:
826 # issue exists already, so ask user
827 dlg = gmGuiHelpers.c3ButtonQuestionDlg (
828 parent,
829 -1,
830 caption = _('Promoting episode to health issue'),
831 question = _(
832 'There already is a health issue\n'
833 '\n'
834 ' %s\n'
835 '\n'
836 'What do you want to do ?'
837 ) % issue['description'],
838 button_defs = [
839 {'label': _('Use existing'), 'tooltip': _('Move episode into existing health issue'), 'default': False},
840 {'label': _('Create new'), 'tooltip': _('Create a new health issue with another name'), 'default': True}
841 ]
842 )
843 use_existing = dlg.ShowModal()
844 dlg.Destroy()
845
846 if use_existing == wx.ID_CANCEL:
847 return
848
849 # user wants to create new issue with alternate name
850 if use_existing == wx.ID_NO:
851 # loop until name modified but non-empty or cancelled
852 issue_name = episode['description']
853 while issue_name == episode['description']:
854 dlg = wx.TextEntryDialog (
855 parent = parent,
856 message = _('Enter a short descriptive name for the new health issue:'),
857 caption = _('Creating a new health issue ...'),
858 defaultValue = issue_name,
859 style = wx.OK | wx.CANCEL | wx.CENTRE
860 )
861 decision = dlg.ShowModal()
862 if decision != wx.ID_OK:
863 dlg.Destroy()
864 return
865 issue_name = dlg.GetValue().strip()
866 dlg.Destroy()
867 if issue_name == u'':
868 issue_name = episode['description']
869
870 issue = emr.add_health_issue(issue_name = issue_name)
871 created_new_issue = True
872
873 # eventually move the episode to the issue
874 if not move_episode_to_issue(episode = episode, target_issue = issue, save_to_backend = True):
875 # user cancelled the move so delete just-created issue
876 if created_new_issue:
877 # shouldn't fail as it is completely new
878 gmEMRStructItems.delete_health_issue(health_issue = issue)
879 return
880
881 return
882 #----------------------------------------------------------------
884 """Prepare changing health issue for an episode.
885
886 Checks for two-open-episodes conflict. When this
887 function succeeds, the pk_health_issue has been set
888 on the episode instance and the episode should - for
889 all practical purposes - be ready for save_payload().
890 """
891 # episode is closed: should always work
892 if not episode['episode_open']:
893 episode['pk_health_issue'] = target_issue['pk_health_issue']
894 if save_to_backend:
895 episode.save_payload()
896 return True
897
898 # un-associate: should always work, too
899 if target_issue is None:
900 episode['pk_health_issue'] = None
901 if save_to_backend:
902 episode.save_payload()
903 return True
904
905 # try closing possibly expired episode on target issue if any
906 db_cfg = gmCfg.cCfgSQL()
907 epi_ttl = int(db_cfg.get2 (
908 option = u'episode.ttl',
909 workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
910 bias = 'user',
911 default = 60 # 2 months
912 ))
913 if target_issue.close_expired_episode(ttl=epi_ttl) is True:
914 gmDispatcher.send(signal='statustext', msg=_('Closed episodes older than %s days on health issue [%s]') % (epi_ttl, target_issue['description']))
915 existing_epi = target_issue.get_open_episode()
916
917 # no more open episode on target issue: should work now
918 if existing_epi is None:
919 episode['pk_health_issue'] = target_issue['pk_health_issue']
920 if save_to_backend:
921 episode.save_payload()
922 return True
923
924 # don't conflict on SELF ;-)
925 if existing_epi['pk_episode'] == episode['pk_episode']:
926 episode['pk_health_issue'] = target_issue['pk_health_issue']
927 if save_to_backend:
928 episode.save_payload()
929 return True
930
931 # we got two open episodes at once, ask user
932 move_range = episode.get_access_range()
933 exist_range = existing_epi.get_access_range()
934 question = _(
935 'You want to associate the running episode:\n\n'
936 ' "%(new_epi_name)s" (%(new_epi_start)s - %(new_epi_end)s)\n\n'
937 'with the health issue:\n\n'
938 ' "%(issue_name)s"\n\n'
939 'There already is another episode running\n'
940 'for this health issue:\n\n'
941 ' "%(old_epi_name)s" (%(old_epi_start)s - %(old_epi_end)s)\n\n'
942 'However, there can only be one running\n'
943 'episode per health issue.\n\n'
944 'Which episode do you want to close ?'
945 ) % {
946 'new_epi_name': episode['description'],
947 'new_epi_start': move_range[0].strftime('%m/%y'),
948 'new_epi_end': move_range[1].strftime('%m/%y'),
949 'issue_name': target_issue['description'],
950 'old_epi_name': existing_epi['description'],
951 'old_epi_start': exist_range[0].strftime('%m/%y'),
952 'old_epi_end': exist_range[1].strftime('%m/%y')
953 }
954 dlg = gmGuiHelpers.c3ButtonQuestionDlg (
955 parent = None,
956 id = -1,
957 caption = _('Resolving two-running-episodes conflict'),
958 question = question,
959 button_defs = [
960 {'label': _('old episode'), 'default': True, 'tooltip': _('close existing episode "%s"') % existing_epi['description']},
961 {'label': _('new episode'), 'default': False, 'tooltip': _('close moving (new) episode "%s"') % episode['description']}
962 ]
963 )
964 decision = dlg.ShowModal()
965
966 if decision == wx.ID_CANCEL:
967 # button 3: move cancelled by user
968 return False
969
970 elif decision == wx.ID_YES:
971 # button 1: close old episode
972 existing_epi['episode_open'] = False
973 existing_epi.save_payload()
974
975 elif decision == wx.ID_NO:
976 # button 2: close new episode
977 episode['episode_open'] = False
978
979 else:
980 raise ValueError('invalid result from c3ButtonQuestionDlg: [%s]' % decision)
981
982 episode['pk_health_issue'] = target_issue['pk_health_issue']
983 if save_to_backend:
984 episode.save_payload()
985 return True
986 #----------------------------------------------------------------
988
989 # FIXME: support pre-selection
990
992
993 episodes = kwargs['episodes']
994 del kwargs['episodes']
995
996 gmListWidgets.cGenericListSelectorDlg.__init__(self, *args, **kwargs)
997
998 self.SetTitle(_('Select the episodes you are interested in ...'))
999 self._LCTRL_items.set_columns([_('Episode'), _('Status'), _('Health Issue')])
1000 self._LCTRL_items.set_string_items (
1001 items = [
1002 [ epi['description'],
1003 gmTools.bool2str(epi['episode_open'], _('ongoing'), u''),
1004 gmTools.coalesce(epi['health_issue'], u'')
1005 ]
1006 for epi in episodes ]
1007 )
1008 self._LCTRL_items.set_column_widths()
1009 self._LCTRL_items.set_data(data = episodes)
1010 #----------------------------------------------------------------
1012 """Let user select an episode *description*.
1013
1014 The user can select an episode description from the previously
1015 used descriptions across all episodes across all patients.
1016
1017 Selection is done with a phrasewheel so the user can
1018 type the episode name and matches will be shown. Typing
1019 "*" will show the entire list of episodes.
1020
1021 If the user types a description not existing yet a
1022 new episode description will be returned.
1023 """
1025
1026 mp = gmMatchProvider.cMatchProvider_SQL2 (
1027 queries = [
1028 u"""
1029 SELECT DISTINCT ON (description)
1030 description
1031 AS data,
1032 description
1033 AS field_label,
1034 description || ' ('
1035 || CASE
1036 WHEN is_open IS TRUE THEN _('ongoing')
1037 ELSE _('closed')
1038 END
1039 || ')'
1040 AS list_label
1041 FROM
1042 clin.episode
1043 WHERE
1044 description %(fragment_condition)s
1045 ORDER BY description
1046 LIMIT 30
1047 """
1048 ]
1049 )
1050 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
1051 self.matcher = mp
1052 #----------------------------------------------------------------
1054 """Let user select an episode.
1055
1056 The user can select an episode from the existing episodes of a
1057 patient. Selection is done with a phrasewheel so the user
1058 can type the episode name and matches will be shown. Typing
1059 "*" will show the entire list of episodes. Closed episodes
1060 will be marked as such. If the user types an episode name not
1061 in the list of existing episodes a new episode can be created
1062 from it if the programmer activated that feature.
1063
1064 If keyword <patient_id> is set to None or left out the control
1065 will listen to patient change signals and therefore act on
1066 gmPerson.gmCurrentPatient() changes.
1067 """
1069
1070 ctxt = {'ctxt_pat': {'where_part': u'and pk_patient = %(pat)s', 'placeholder': u'pat'}}
1071
1072 mp = gmMatchProvider.cMatchProvider_SQL2 (
1073 queries = [
1074 u"""(
1075
1076 SELECT
1077 pk_episode
1078 as data,
1079 description
1080 as field_label,
1081 coalesce (
1082 description || ' - ' || health_issue,
1083 description
1084 ) as list_label,
1085 1 as rank
1086 from
1087 clin.v_pat_episodes
1088 where
1089 episode_open is true and
1090 description %(fragment_condition)s
1091 %(ctxt_pat)s
1092
1093 ) union all (
1094
1095 SELECT
1096 pk_episode
1097 as data,
1098 description
1099 as field_label,
1100 coalesce (
1101 description || _(' (closed)') || ' - ' || health_issue,
1102 description || _(' (closed)')
1103 ) as list_label,
1104 2 as rank
1105 from
1106 clin.v_pat_episodes
1107 where
1108 description %(fragment_condition)s and
1109 episode_open is false
1110 %(ctxt_pat)s
1111
1112 )
1113
1114 order by rank, list_label
1115 limit 30"""
1116 ],
1117 context = ctxt
1118 )
1119
1120 try:
1121 kwargs['patient_id']
1122 except KeyError:
1123 kwargs['patient_id'] = None
1124
1125 if kwargs['patient_id'] is None:
1126 self.use_current_patient = True
1127 self.__register_patient_change_signals()
1128 pat = gmPerson.gmCurrentPatient()
1129 if pat.connected:
1130 mp.set_context('pat', pat.ID)
1131 else:
1132 self.use_current_patient = False
1133 self.__patient_id = int(kwargs['patient_id'])
1134 mp.set_context('pat', self.__patient_id)
1135
1136 del kwargs['patient_id']
1137
1138 gmPhraseWheel.cPhraseWheel.__init__ (
1139 self,
1140 *args,
1141 **kwargs
1142 )
1143 self.matcher = mp
1144 #--------------------------------------------------------
1145 # external API
1146 #--------------------------------------------------------
1148 if self.use_current_patient:
1149 return False
1150 self.__patient_id = int(patient_id)
1151 self.set_context('pat', self.__patient_id)
1152 return True
1153 #--------------------------------------------------------
1155 self.__is_open_for_create_data = is_open # used (only) in _create_data()
1156 return gmPhraseWheel.cPhraseWheel.GetData(self, can_create = can_create, as_instance = as_instance)
1157 #--------------------------------------------------------
1159
1160 epi_name = self.GetValue().strip()
1161 if epi_name == u'':
1162 gmDispatcher.send(signal = u'statustext', msg = _('Cannot create episode without name.'), beep = True)
1163 _log.debug('cannot create episode without name')
1164 return
1165
1166 if self.use_current_patient:
1167 pat = gmPerson.gmCurrentPatient()
1168 else:
1169 pat = gmPerson.cPatient(aPK_obj = self.__patient_id)
1170
1171 emr = pat.get_emr()
1172 epi = emr.add_episode(episode_name = epi_name, is_open = self.__is_open_for_create_data)
1173 if epi is None:
1174 self.data = {}
1175 else:
1176 self.SetText (
1177 value = epi_name,
1178 data = epi['pk_episode']
1179 )
1180 #--------------------------------------------------------
1183 #--------------------------------------------------------
1184 # internal API
1185 #--------------------------------------------------------
1187 gmDispatcher.connect(self._pre_patient_selection, u'pre_patient_selection')
1188 gmDispatcher.connect(self._post_patient_selection, u'post_patient_selection')
1189 #--------------------------------------------------------
1192 #--------------------------------------------------------
1194 if self.use_current_patient:
1195 patient = gmPerson.gmCurrentPatient()
1196 self.set_context('pat', patient.ID)
1197 return True
1198 #----------------------------------------------------------------
1199 from Gnumed.wxGladeWidgets import wxgEpisodeEditAreaPnl
1200
1201 -class cEpisodeEditAreaPnl(gmEditArea.cGenericEditAreaMixin, wxgEpisodeEditAreaPnl.wxgEpisodeEditAreaPnl):
1202
1204
1205 try:
1206 episode = kwargs['episode']
1207 del kwargs['episode']
1208 except KeyError:
1209 episode = None
1210
1211 wxgEpisodeEditAreaPnl.wxgEpisodeEditAreaPnl.__init__(self, *args, **kwargs)
1212 gmEditArea.cGenericEditAreaMixin.__init__(self)
1213
1214 self.data = episode
1215 #----------------------------------------------------------------
1216 # generic Edit Area mixin API
1217 #----------------------------------------------------------------
1219
1220 errors = False
1221
1222 if len(self._PRW_description.GetValue().strip()) == 0:
1223 errors = True
1224 self._PRW_description.display_as_valid(False)
1225 self._PRW_description.SetFocus()
1226 else:
1227 self._PRW_description.display_as_valid(True)
1228 self._PRW_description.Refresh()
1229
1230 return not errors
1231 #----------------------------------------------------------------
1233
1234 pat = gmPerson.gmCurrentPatient()
1235 emr = pat.get_emr()
1236
1237 epi = emr.add_episode(episode_name = self._PRW_description.GetValue().strip())
1238 epi['summary'] = self._TCTRL_status.GetValue().strip()
1239 epi['episode_open'] = not self._CHBOX_closed.IsChecked()
1240 epi['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
1241
1242 issue_name = self._PRW_issue.GetValue().strip()
1243 if len(issue_name) != 0:
1244 epi['pk_health_issue'] = self._PRW_issue.GetData(can_create = True)
1245 issue = gmEMRStructItems.cHealthIssue(aPK_obj = epi['pk_health_issue'])
1246
1247 if not move_episode_to_issue(episode = epi, target_issue = issue, save_to_backend = False):
1248 gmDispatcher.send (
1249 signal = 'statustext',
1250 msg = _('Cannot attach episode [%s] to health issue [%s] because it already has a running episode.') % (
1251 epi['description'],
1252 issue['description']
1253 )
1254 )
1255 gmEMRStructItems.delete_episode(episode = epi)
1256 return False
1257
1258 epi.save()
1259
1260 epi.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
1261
1262 self.data = epi
1263 return True
1264 #----------------------------------------------------------------
1266
1267 self.data['description'] = self._PRW_description.GetValue().strip()
1268 self.data['summary'] = self._TCTRL_status.GetValue().strip()
1269 self.data['episode_open'] = not self._CHBOX_closed.IsChecked()
1270 self.data['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
1271
1272 issue_name = self._PRW_issue.GetValue().strip()
1273 if len(issue_name) == 0:
1274 self.data['pk_health_issue'] = None
1275 else:
1276 self.data['pk_health_issue'] = self._PRW_issue.GetData(can_create = True)
1277 issue = gmEMRStructItems.cHealthIssue(aPK_obj = self.data['pk_health_issue'])
1278
1279 if not move_episode_to_issue(episode = self.data, target_issue = issue, save_to_backend = False):
1280 gmDispatcher.send (
1281 signal = 'statustext',
1282 msg = _('Cannot attach episode [%s] to health issue [%s] because it already has a running episode.') % (
1283 self.data['description'],
1284 issue['description']
1285 )
1286 )
1287 return False
1288
1289 self.data.save()
1290 self.data.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
1291
1292 return True
1293 #----------------------------------------------------------------
1295 if self.data is None:
1296 ident = gmPerson.gmCurrentPatient()
1297 else:
1298 ident = gmPerson.cIdentity(aPK_obj = self.data['pk_patient'])
1299 self._TCTRL_patient.SetValue(ident.get_description_gender())
1300 self._PRW_issue.SetText()
1301 self._PRW_description.SetText()
1302 self._TCTRL_status.SetValue(u'')
1303 self._PRW_certainty.SetText()
1304 self._CHBOX_closed.SetValue(False)
1305 self._PRW_codes.SetText()
1306 #----------------------------------------------------------------
1308 ident = gmPerson.cIdentity(aPK_obj = self.data['pk_patient'])
1309 self._TCTRL_patient.SetValue(ident.get_description_gender())
1310
1311 if self.data['pk_health_issue'] is not None:
1312 self._PRW_issue.SetText(self.data['health_issue'], data=self.data['pk_health_issue'])
1313
1314 self._PRW_description.SetText(self.data['description'], data=self.data['description'])
1315
1316 self._TCTRL_status.SetValue(gmTools.coalesce(self.data['summary'], u''))
1317
1318 if self.data['diagnostic_certainty_classification'] is not None:
1319 self._PRW_certainty.SetData(data = self.data['diagnostic_certainty_classification'])
1320
1321 self._CHBOX_closed.SetValue(not self.data['episode_open'])
1322
1323 val, data = self._PRW_codes.generic_linked_codes2item_dict(self.data.generic_codes)
1324 self._PRW_codes.SetText(val, data)
1325 #----------------------------------------------------------------
1328 #================================================================
1329 # health issue related widgets/functions
1330 #----------------------------------------------------------------
1332 ea = cHealthIssueEditAreaPnl(parent = parent, id = -1)
1333 ea.data = issue
1334 ea.mode = gmTools.coalesce(issue, 'new', 'edit')
1335 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = (issue is not None))
1336 dlg.SetTitle(gmTools.coalesce(issue, _('Adding a new health issue'), _('Editing a health issue')))
1337 if dlg.ShowModal() == wx.ID_OK:
1338 dlg.Destroy()
1339 return True
1340 dlg.Destroy()
1341 return False
1342 #----------------------------------------------------------------
1344
1345 if parent is None:
1346 parent = wx.GetApp().GetTopWindow()
1347 #-----------------------------------------
1348 def refresh(lctrl):
1349 issues = emr.get_health_issues()
1350 items = [
1351 [
1352 gmTools.bool2subst(i['is_confidential'], _('CONFIDENTIAL'), u'', u''),
1353 i['description'],
1354 gmTools.bool2subst(i['clinically_relevant'], _('relevant'), u'', u''),
1355 gmTools.bool2subst(i['is_active'], _('active'), u'', u''),
1356 gmTools.bool2subst(i['is_cause_of_death'], _('fatal'), u'', u'')
1357 ] for i in issues
1358 ]
1359 lctrl.set_string_items(items = items)
1360 lctrl.set_data(data = issues)
1361 #-----------------------------------------
1362 return gmListWidgets.get_choices_from_list (
1363 parent = parent,
1364 msg = _('\nSelect the health issues !\n'),
1365 caption = _('Showing health issues ...'),
1366 columns = [u'', _('Health issue'), u'', u'', u''],
1367 single_selection = False,
1368 #edit_callback = edit,
1369 #new_callback = edit,
1370 #delete_callback = delete,
1371 refresh_callback = refresh
1372 )
1373 #----------------------------------------------------------------
1375
1376 # FIXME: support pre-selection
1377
1379
1380 issues = kwargs['issues']
1381 del kwargs['issues']
1382
1383 gmListWidgets.cGenericListSelectorDlg.__init__(self, *args, **kwargs)
1384
1385 self.SetTitle(_('Select the health issues you are interested in ...'))
1386 self._LCTRL_items.set_columns([u'', _('Health Issue'), u'', u'', u''])
1387
1388 for issue in issues:
1389 if issue['is_confidential']:
1390 row_num = self._LCTRL_items.InsertStringItem(sys.maxint, label = _('confidential'))
1391 self._LCTRL_items.SetItemTextColour(row_num, col=wx.NamedColour('RED'))
1392 else:
1393 row_num = self._LCTRL_items.InsertStringItem(sys.maxint, label = u'')
1394
1395 self._LCTRL_items.SetStringItem(index = row_num, col = 1, label = issue['description'])
1396 if issue['clinically_relevant']:
1397 self._LCTRL_items.SetStringItem(index = row_num, col = 2, label = _('relevant'))
1398 if issue['is_active']:
1399 self._LCTRL_items.SetStringItem(index = row_num, col = 3, label = _('active'))
1400 if issue['is_cause_of_death']:
1401 self._LCTRL_items.SetStringItem(index = row_num, col = 4, label = _('fatal'))
1402
1403 self._LCTRL_items.set_column_widths()
1404 self._LCTRL_items.set_data(data = issues)
1405 #----------------------------------------------------------------
1407 """Let the user select a health issue.
1408
1409 The user can select a health issue from the existing issues
1410 of a patient. Selection is done with a phrasewheel so the user
1411 can type the issue name and matches will be shown. Typing
1412 "*" will show the entire list of issues. Inactive issues
1413 will be marked as such. If the user types an issue name not
1414 in the list of existing issues a new issue can be created
1415 from it if the programmer activated that feature.
1416
1417 If keyword <patient_id> is set to None or left out the control
1418 will listen to patient change signals and therefore act on
1419 gmPerson.gmCurrentPatient() changes.
1420 """
1422
1423 ctxt = {'ctxt_pat': {'where_part': u'pk_patient=%(pat)s', 'placeholder': u'pat'}}
1424
1425 mp = gmMatchProvider.cMatchProvider_SQL2 (
1426 # FIXME: consider clin.health_issue.clinically_relevant
1427 queries = [
1428 u"""
1429 SELECT
1430 data,
1431 field_label,
1432 list_label
1433 FROM ((
1434 SELECT
1435 pk_health_issue AS data,
1436 description AS field_label,
1437 description AS list_label
1438 FROM clin.v_health_issues
1439 WHERE
1440 is_active IS true
1441 AND
1442 description %(fragment_condition)s
1443 AND
1444 %(ctxt_pat)s
1445
1446 ) UNION (
1447
1448 SELECT
1449 pk_health_issue AS data,
1450 description AS field_label,
1451 description || _(' (inactive)') AS list_label
1452 FROM clin.v_health_issues
1453 WHERE
1454 is_active IS false
1455 AND
1456 description %(fragment_condition)s
1457 AND
1458 %(ctxt_pat)s
1459 )) AS union_query
1460 ORDER BY
1461 list_label"""],
1462 context = ctxt
1463 )
1464
1465 try: kwargs['patient_id']
1466 except KeyError: kwargs['patient_id'] = None
1467
1468 if kwargs['patient_id'] is None:
1469 self.use_current_patient = True
1470 self.__register_patient_change_signals()
1471 pat = gmPerson.gmCurrentPatient()
1472 if pat.connected:
1473 mp.set_context('pat', pat.ID)
1474 else:
1475 self.use_current_patient = False
1476 self.__patient_id = int(kwargs['patient_id'])
1477 mp.set_context('pat', self.__patient_id)
1478
1479 del kwargs['patient_id']
1480
1481 gmPhraseWheel.cPhraseWheel.__init__ (
1482 self,
1483 *args,
1484 **kwargs
1485 )
1486 self.matcher = mp
1487 #--------------------------------------------------------
1488 # external API
1489 #--------------------------------------------------------
1491 if self.use_current_patient:
1492 return False
1493 self.__patient_id = int(patient_id)
1494 self.set_context('pat', self.__patient_id)
1495 return True
1496 #--------------------------------------------------------
1498 issue_name = self.GetValue().strip()
1499 if issue_name == u'':
1500 gmDispatcher.send(signal = u'statustext', msg = _('Cannot create health issue without name.'), beep = True)
1501 _log.debug('cannot create health issue without name')
1502 return
1503
1504 if self.use_current_patient:
1505 pat = gmPerson.gmCurrentPatient()
1506 else:
1507 pat = gmPerson.cPatient(aPK_obj = self.__patient_id)
1508
1509 emr = pat.get_emr()
1510 issue = emr.add_health_issue(issue_name = issue_name)
1511
1512 if issue is None:
1513 self.data = {}
1514 else:
1515 self.SetText (
1516 value = issue_name,
1517 data = issue['pk_health_issue']
1518 )
1519 #--------------------------------------------------------
1522 #--------------------------------------------------------
1523 # internal API
1524 #--------------------------------------------------------
1526 gmDispatcher.connect(self._pre_patient_selection, u'pre_patient_selection')
1527 gmDispatcher.connect(self._post_patient_selection, u'post_patient_selection')
1528 #--------------------------------------------------------
1531 #--------------------------------------------------------
1533 if self.use_current_patient:
1534 patient = gmPerson.gmCurrentPatient()
1535 self.set_context('pat', patient.ID)
1536 return True
1537 #------------------------------------------------------------
1538 from Gnumed.wxGladeWidgets import wxgIssueSelectionDlg
1539
1541
1543 try:
1544 msg = kwargs['message']
1545 except KeyError:
1546 msg = None
1547 del kwargs['message']
1548 wxgIssueSelectionDlg.wxgIssueSelectionDlg.__init__(self, *args, **kwargs)
1549 if msg is not None:
1550 self._lbl_message.SetLabel(label=msg)
1551 #--------------------------------------------------------
1562 #------------------------------------------------------------
1563 from Gnumed.wxGladeWidgets import wxgHealthIssueEditAreaPnl
1564
1565 -class cHealthIssueEditAreaPnl(gmEditArea.cGenericEditAreaMixin, wxgHealthIssueEditAreaPnl.wxgHealthIssueEditAreaPnl):
1566 """Panel encapsulating health issue edit area functionality."""
1567
1569
1570 try:
1571 data = kwargs['issue']
1572 del kwargs['issue']
1573 except KeyError:
1574 data = None
1575
1576 wxgHealthIssueEditAreaPnl.wxgHealthIssueEditAreaPnl.__init__(self, *args, **kwargs)
1577 gmEditArea.cGenericEditAreaMixin.__init__(self)
1578
1579 self.mode = 'new'
1580 self.data = data
1581 if data is not None:
1582 self.mode = 'edit'
1583
1584 self.__init_ui()
1585 #----------------------------------------------------------------
1587
1588 # FIXME: include more sources: coding systems/other database columns
1589 mp = gmMatchProvider.cMatchProvider_SQL2 (
1590 queries = [u"SELECT DISTINCT ON (description) description, description FROM clin.health_issue WHERE description %(fragment_condition)s LIMIT 50"]
1591 )
1592 mp.setThresholds(1, 3, 5)
1593 self._PRW_condition.matcher = mp
1594
1595 mp = gmMatchProvider.cMatchProvider_SQL2 (
1596 queries = [u"""
1597 SELECT DISTINCT ON (grouping) grouping, grouping from (
1598
1599 SELECT rank, grouping from ((
1600
1601 SELECT
1602 grouping,
1603 1 as rank
1604 from
1605 clin.health_issue
1606 where
1607 grouping %%(fragment_condition)s
1608 and
1609 (SELECT True from clin.encounter where fk_patient = %s and pk = clin.health_issue.fk_encounter)
1610
1611 ) union (
1612
1613 SELECT
1614 grouping,
1615 2 as rank
1616 from
1617 clin.health_issue
1618 where
1619 grouping %%(fragment_condition)s
1620
1621 )) as union_result
1622
1623 order by rank
1624
1625 ) as order_result
1626
1627 limit 50""" % gmPerson.gmCurrentPatient().ID
1628 ]
1629 )
1630 mp.setThresholds(1, 3, 5)
1631 self._PRW_grouping.matcher = mp
1632
1633 self._PRW_age_noted.add_callback_on_lose_focus(self._on_leave_age_noted)
1634 self._PRW_year_noted.add_callback_on_lose_focus(self._on_leave_year_noted)
1635
1636 # self._PRW_age_noted.add_callback_on_modified(self._on_modified_age_noted)
1637 # self._PRW_year_noted.add_callback_on_modified(self._on_modified_year_noted)
1638
1639 self._PRW_year_noted.Enable(True)
1640
1641 self._PRW_codes.add_callback_on_lose_focus(self._on_leave_codes)
1642
1643 #----------------------------------------------------------------
1644 # generic Edit Area mixin API
1645 #----------------------------------------------------------------
1647
1648 if self._PRW_condition.GetValue().strip() == '':
1649 self._PRW_condition.display_as_valid(False)
1650 self._PRW_condition.SetFocus()
1651 return False
1652 self._PRW_condition.display_as_valid(True)
1653 self._PRW_condition.Refresh()
1654
1655 # FIXME: sanity check age/year diagnosed
1656 age_noted = self._PRW_age_noted.GetValue().strip()
1657 if age_noted != u'':
1658 if gmDateTime.str2interval(str_interval = age_noted) is None:
1659 self._PRW_age_noted.display_as_valid(False)
1660 self._PRW_age_noted.SetFocus()
1661 return False
1662 self._PRW_age_noted.display_as_valid(True)
1663 return True
1664 #----------------------------------------------------------------
1666 pat = gmPerson.gmCurrentPatient()
1667 emr = pat.get_emr()
1668
1669 issue = emr.add_health_issue(issue_name = self._PRW_condition.GetValue().strip())
1670
1671 side = u''
1672 if self._ChBOX_left.GetValue():
1673 side += u's'
1674 if self._ChBOX_right.GetValue():
1675 side += u'd'
1676 issue['laterality'] = side
1677
1678 issue['summary'] = self._TCTRL_status.GetValue().strip()
1679 issue['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
1680 issue['grouping'] = self._PRW_grouping.GetValue().strip()
1681 issue['is_active'] = self._ChBOX_active.GetValue()
1682 issue['clinically_relevant'] = self._ChBOX_relevant.GetValue()
1683 issue['is_confidential'] = self._ChBOX_confidential.GetValue()
1684 issue['is_cause_of_death'] = self._ChBOX_caused_death.GetValue()
1685
1686 age_noted = self._PRW_age_noted.GetData()
1687 if age_noted is not None:
1688 issue['age_noted'] = age_noted
1689
1690 issue.save()
1691
1692 issue.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
1693
1694 self.data = issue
1695 return True
1696 #----------------------------------------------------------------
1698
1699 self.data['description'] = self._PRW_condition.GetValue().strip()
1700
1701 side = u''
1702 if self._ChBOX_left.GetValue():
1703 side += u's'
1704 if self._ChBOX_right.GetValue():
1705 side += u'd'
1706 self.data['laterality'] = side
1707
1708 self.data['summary'] = self._TCTRL_status.GetValue().strip()
1709 self.data['diagnostic_certainty_classification'] = self._PRW_certainty.GetData()
1710 self.data['grouping'] = self._PRW_grouping.GetValue().strip()
1711 self.data['is_active'] = bool(self._ChBOX_active.GetValue())
1712 self.data['clinically_relevant'] = bool(self._ChBOX_relevant.GetValue())
1713 self.data['is_confidential'] = bool(self._ChBOX_confidential.GetValue())
1714 self.data['is_cause_of_death'] = bool(self._ChBOX_caused_death.GetValue())
1715
1716 age_noted = self._PRW_age_noted.GetData()
1717 if age_noted is not None:
1718 self.data['age_noted'] = age_noted
1719
1720 self.data.save()
1721 self.data.generic_codes = [ c['data'] for c in self._PRW_codes.GetData() ]
1722
1723 return True
1724 #----------------------------------------------------------------
1726 self._PRW_condition.SetText()
1727 self._ChBOX_left.SetValue(0)
1728 self._ChBOX_right.SetValue(0)
1729 self._PRW_codes.SetText()
1730 self._on_leave_codes()
1731 self._PRW_certainty.SetText()
1732 self._PRW_grouping.SetText()
1733 self._TCTRL_status.SetValue(u'')
1734 self._PRW_age_noted.SetText()
1735 self._PRW_year_noted.SetText()
1736 self._ChBOX_active.SetValue(1)
1737 self._ChBOX_relevant.SetValue(1)
1738 self._ChBOX_confidential.SetValue(0)
1739 self._ChBOX_caused_death.SetValue(0)
1740
1741 self._PRW_condition.SetFocus()
1742 return True
1743 #----------------------------------------------------------------
1745 self._PRW_condition.SetText(self.data['description'])
1746
1747 lat = gmTools.coalesce(self.data['laterality'], '')
1748 if lat.find('s') == -1:
1749 self._ChBOX_left.SetValue(0)
1750 else:
1751 self._ChBOX_left.SetValue(1)
1752 if lat.find('d') == -1:
1753 self._ChBOX_right.SetValue(0)
1754 else:
1755 self._ChBOX_right.SetValue(1)
1756
1757 val, data = self._PRW_codes.generic_linked_codes2item_dict(self.data.generic_codes)
1758 self._PRW_codes.SetText(val, data)
1759 self._on_leave_codes()
1760
1761 if self.data['diagnostic_certainty_classification'] is not None:
1762 self._PRW_certainty.SetData(data = self.data['diagnostic_certainty_classification'])
1763 self._PRW_grouping.SetText(gmTools.coalesce(self.data['grouping'], u''))
1764 self._TCTRL_status.SetValue(gmTools.coalesce(self.data['summary'], u''))
1765
1766 if self.data['age_noted'] is None:
1767 self._PRW_age_noted.SetText()
1768 else:
1769 self._PRW_age_noted.SetText (
1770 value = '%sd' % self.data['age_noted'].days,
1771 data = self.data['age_noted']
1772 )
1773
1774 self._ChBOX_active.SetValue(self.data['is_active'])
1775 self._ChBOX_relevant.SetValue(self.data['clinically_relevant'])
1776 self._ChBOX_confidential.SetValue(self.data['is_confidential'])
1777 self._ChBOX_caused_death.SetValue(self.data['is_cause_of_death'])
1778
1779 self._TCTRL_status.SetFocus()
1780
1781 return True
1782 #----------------------------------------------------------------
1785 #--------------------------------------------------------
1786 # internal helpers
1787 #--------------------------------------------------------
1789 if not self._PRW_codes.IsModified():
1790 return True
1791
1792 self._TCTRL_code_details.SetValue(u'- ' + u'\n- '.join([ c['list_label'] for c in self._PRW_codes.GetData() ]))
1793 #--------------------------------------------------------
1795
1796 if not self._PRW_age_noted.IsModified():
1797 return True
1798
1799 age_str = self._PRW_age_noted.GetValue().strip()
1800
1801 if age_str == u'':
1802 return True
1803
1804 issue_age = gmDateTime.str2interval(str_interval = age_str)
1805
1806 if issue_age is None:
1807 self.status_message = _('Cannot parse [%s] into valid interval.') % age_str
1808 self._PRW_age_noted.display_as_valid(False)
1809 return True
1810
1811 pat = gmPerson.gmCurrentPatient()
1812 if pat['dob'] is not None:
1813 max_issue_age = pydt.datetime.now(tz=pat['dob'].tzinfo) - pat['dob']
1814 if issue_age >= max_issue_age:
1815 self.status_message = _('Health issue cannot have been noted at age %s. Patient is only %s old.') % (issue_age, pat.get_medical_age())
1816 self._PRW_age_noted.display_as_valid(False)
1817 return True
1818
1819 self._PRW_age_noted.display_as_valid(True)
1820 self._PRW_age_noted.SetText(value = age_str, data = issue_age)
1821
1822 if pat['dob'] is not None:
1823 fts = gmDateTime.cFuzzyTimestamp (
1824 timestamp = pat['dob'] + issue_age,
1825 accuracy = gmDateTime.acc_months
1826 )
1827 self._PRW_year_noted.SetText(value = str(fts), data = fts)
1828
1829 return True
1830 #--------------------------------------------------------
1832
1833 if not self._PRW_year_noted.IsModified():
1834 return True
1835
1836 year_noted = self._PRW_year_noted.GetData()
1837
1838 if year_noted is None:
1839 if self._PRW_year_noted.GetValue().strip() == u'':
1840 self._PRW_year_noted.display_as_valid(True)
1841 return True
1842 self._PRW_year_noted.display_as_valid(False)
1843 return True
1844
1845 year_noted = year_noted.get_pydt()
1846
1847 if year_noted >= pydt.datetime.now(tz = year_noted.tzinfo):
1848 self.status_message = _('Condition diagnosed in the future.')
1849 self._PRW_year_noted.display_as_valid(False)
1850 return True
1851
1852 self._PRW_year_noted.display_as_valid(True)
1853
1854 pat = gmPerson.gmCurrentPatient()
1855 if pat['dob'] is not None:
1856 issue_age = year_noted - pat['dob']
1857 age_str = gmDateTime.format_interval_medically(interval = issue_age)
1858 self._PRW_age_noted.SetText(age_str, issue_age, True)
1859
1860 return True
1861 #--------------------------------------------------------
1865 #--------------------------------------------------------
1869 #================================================================
1870 # diagnostic certainty related widgets/functions
1871 #----------------------------------------------------------------
1873
1875
1876 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
1877
1878 self.selection_only = False # can be NULL, too
1879
1880 mp = gmMatchProvider.cMatchProvider_FixedList (
1881 aSeq = [
1882 {'data': u'A', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'A'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'A'), 'weight': 1},
1883 {'data': u'B', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'B'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'B'), 'weight': 1},
1884 {'data': u'C', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'C'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'C'), 'weight': 1},
1885 {'data': u'D', 'list_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'D'), 'field_label': gmEMRStructItems.diagnostic_certainty_classification2str(u'D'), 'weight': 1}
1886 ]
1887 )
1888 mp.setThresholds(1, 2, 4)
1889 self.matcher = mp
1890
1891 self.SetToolTipString(_(
1892 "The diagnostic classification or grading of this assessment.\n"
1893 "\n"
1894 "This documents how certain one is about this being a true diagnosis."
1895 ))
1896
1897 #================================================================
1898 # MAIN
1899 #----------------------------------------------------------------
1900 if __name__ == '__main__':
1901
1902 from Gnumed.business import gmPersonSearch
1903 from Gnumed.wxpython import gmPatSearchWidgets
1904
1905 #================================================================
1907 """
1908 Test application for testing EMR struct widgets
1909 """
1910 #--------------------------------------------------------
1912 """
1913 Create test application UI
1914 """
1915 frame = wx.Frame (
1916 None,
1917 -4,
1918 'Testing EMR struct widgets',
1919 size=wx.Size(600, 400),
1920 style=wx.DEFAULT_FRAME_STYLE | wx.NO_FULL_REPAINT_ON_RESIZE
1921 )
1922 filemenu= wx.Menu()
1923 filemenu.AppendSeparator()
1924 filemenu.Append(ID_EXIT,"E&xit"," Terminate test application")
1925
1926 # Creating the menubar.
1927 menuBar = wx.MenuBar()
1928 menuBar.Append(filemenu,"&File")
1929
1930 frame.SetMenuBar(menuBar)
1931
1932 txt = wx.StaticText( frame, -1, _("Select desired test option from the 'File' menu"),
1933 wx.DefaultPosition, wx.DefaultSize, 0 )
1934
1935 # event handlers
1936 wx.EVT_MENU(frame, ID_EXIT, self.OnCloseWindow)
1937
1938 # patient EMR
1939 self.__pat = gmPerson.gmCurrentPatient()
1940
1941 frame.Show(1)
1942 return 1
1943 #--------------------------------------------------------
1949
1950 #----------------------------------------------------------------
1952 app = wx.PyWidgetTester(size = (200, 300))
1953 emr = pat.get_emr()
1954 epi = emr.get_episodes()[0]
1955 pnl = cEpisodeEditAreaPnl(app.frame, -1, episode=epi)
1956 app.frame.Show(True)
1957 app.MainLoop()
1958 #----------------------------------------------------------------
1960 app = wx.PyWidgetTester(size = (200, 300))
1961 emr = pat.get_emr()
1962 epi = emr.get_episodes()[0]
1963 edit_episode(parent=app.frame, episode=epi)
1964 #----------------------------------------------------------------
1966 app = wx.PyWidgetTester(size = (400, 40))
1967 app.SetWidget(cHospitalStayPhraseWheel, id=-1, size=(180,20), pos=(10,20))
1968 app.MainLoop()
1969 #----------------------------------------------------------------
1971 app = wx.PyWidgetTester(size = (400, 40))
1972 app.SetWidget(cEpisodeSelectionPhraseWheel, id=-1, size=(180,20), pos=(10,20))
1973 # app.SetWidget(cEpisodeSelectionPhraseWheel, id=-1, size=(350,20), pos=(10,20), patient_id=pat.ID)
1974 app.MainLoop()
1975 #----------------------------------------------------------------
1977 app = wx.PyWidgetTester(size = (200, 300))
1978 edit_health_issue(parent=app.frame, issue=None)
1979 #----------------------------------------------------------------
1981 app = wx.PyWidgetTester(size = (200, 300))
1982 app.SetWidget(cHealthIssueEditAreaPnl, id=-1, size = (400,400))
1983 app.MainLoop()
1984 #----------------------------------------------------------------
1988 #================================================================
1989
1990 if (len(sys.argv) > 1) and (sys.argv[1] == 'test'):
1991
1992 gmI18N.activate_locale()
1993 gmI18N.install_domain()
1994 gmDateTime.init()
1995
1996 # obtain patient
1997 pat = gmPersonSearch.ask_for_patient()
1998 if pat is None:
1999 print "No patient. Exiting gracefully..."
2000 sys.exit(0)
2001 gmPatSearchWidgets.set_active_patient(patient=pat)
2002
2003 # try:
2004 # lauch emr dialogs test application
2005 # app = testapp(0)
2006 # app.MainLoop()
2007 # except StandardError:
2008 # _log.exception("unhandled exception caught !")
2009 # but re-raise them
2010 # raise
2011
2012 #test_epsiode_edit_area_pnl()
2013 #test_episode_edit_area_dialog()
2014 #test_health_issue_edit_area_dlg()
2015 #test_episode_selection_prw()
2016 #test_hospital_stay_prw()
2017 test_edit_procedure()
2018
2019 #================================================================
2020
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Sat Oct 5 03:57:36 2013 | http://epydoc.sourceforge.net |