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