| 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 # $Source: /cvsroot/gnumed/gnumed/gnumed/client/wxpython/gmEMRStructWidgets.py,v $
11 # $Id: gmEMRStructWidgets.py,v 1.114 2010/02/06 21:01:27 ncq Exp $
12 __version__ = "$Revision: 1.114 $"
13 __author__ = "cfmoro1976@yahoo.es, karsten.hilbert@gmx.net"
14 __license__ = "GPL"
15
16 # stdlib
17 import sys, re, datetime as pydt, logging, time
18
19
20 # 3rd party
21 import wx
22 import wx.lib.pubsub as wxps
23
24
25 # GNUmed
26 if __name__ == '__main__':
27 sys.path.insert(0, '../../')
28 from Gnumed.pycommon import gmI18N, gmMatchProvider, gmDispatcher, gmTools, gmDateTime, gmCfg, gmExceptions
29 from Gnumed.business import gmEMRStructItems, gmPerson, gmSOAPimporter, gmSurgery
30 from Gnumed.wxpython import gmPhraseWheel, gmGuiHelpers, gmListWidgets, gmEditArea, gmPatSearchWidgets
31 from Gnumed.wxGladeWidgets import wxgIssueSelectionDlg, wxgMoveNarrativeDlg
32 from Gnumed.wxGladeWidgets import wxgHealthIssueEditAreaPnl
33 from Gnumed.wxGladeWidgets import wxgEncounterEditAreaPnl, wxgEncounterEditAreaDlg
34 from Gnumed.wxGladeWidgets import wxgEncounterTypeEditAreaPnl
35 from Gnumed.wxGladeWidgets import wxgEpisodeEditAreaPnl
36
37
38 _log = logging.getLogger('gm.ui')
39 _log.info(__version__)
40 #================================================================
41 # performed procedure related widgets/functions
42 #----------------------------------------------------------------
44
45 pat = gmPerson.gmCurrentPatient()
46 emr = pat.get_emr()
47
48 if parent is None:
49 parent = wx.GetApp().GetTopWindow()
50 #-----------------------------------------
51 def edit(procedure=None):
52 return edit_procedure(parent = parent, procedure = procedure)
53 #-----------------------------------------
54 def delete(procedure=None):
55 if gmEMRStructItems.delete_performed_procedure(procedure = procedure['pk_procedure']):
56 return True
57
58 gmDispatcher.send (
59 signal = u'statustext',
60 msg = _('Cannot delete performed procedure.'),
61 beep = True
62 )
63 return False
64 #-----------------------------------------
65 def refresh(lctrl):
66 procs = emr.get_performed_procedures()
67
68 items = [
69 [
70 p['clin_when'].strftime('%Y-%m-%d'),
71 p['clin_where'],
72 p['episode'],
73 p['performed_procedure']
74 ] for p in procs
75 ]
76 lctrl.set_string_items(items = items)
77 lctrl.set_data(data = procs)
78 #-----------------------------------------
79 gmListWidgets.get_choices_from_list (
80 parent = parent,
81 msg = _('\nSelect the procedure you want to edit !\n'),
82 caption = _('Editing performed procedures ...'),
83 columns = [_('When'), _('Where'), _('Episode'), _('Procedure')],
84 single_selection = True,
85 edit_callback = edit,
86 new_callback = edit,
87 delete_callback = delete,
88 refresh_callback = refresh
89 )
90 #----------------------------------------------------------------
91 from Gnumed.wxGladeWidgets import wxgProcedureEAPnl
92
94 ea = cProcedureEAPnl(parent = parent, id = -1)
95 ea.data = procedure
96 ea.mode = gmTools.coalesce(procedure, 'new', 'edit')
97 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = True)
98 dlg.SetTitle(gmTools.coalesce(procedure, _('Adding a procedure'), _('Editing a procedure')))
99 if dlg.ShowModal() == wx.ID_OK:
100 dlg.Destroy()
101 return True
102 dlg.Destroy()
103 return False
104 #----------------------------------------------------------------
106
108 wxgProcedureEAPnl.wxgProcedureEAPnl.__init__(self, *args, **kwargs)
109 gmEditArea.cGenericEditAreaMixin.__init__(self)
110
111 self.mode = 'new'
112 self.data = None
113
114 self.__init_ui()
115 #----------------------------------------------------------------
117 self._PRW_hospital_stay.add_callback_on_lose_focus(callback = self._on_hospital_stay_lost_focus)
118 self._PRW_location.add_callback_on_lose_focus(callback = self._on_location_lost_focus)
119
120 # location
121 mp = gmMatchProvider.cMatchProvider_SQL2 (
122 queries = [
123 u"""
124 select distinct on (clin_where) clin_where, clin_where
125 from clin.procedure
126 where clin_where %(fragment_condition)s
127 order by clin_where
128 limit 25
129 """ ]
130 )
131 mp.setThresholds(2, 4, 6)
132 self._PRW_location.matcher = mp
133
134 # procedure
135 mp = gmMatchProvider.cMatchProvider_SQL2 (
136 queries = [
137 u"""
138 select distinct on (narrative) narrative, narrative
139 from clin.procedure
140 where narrative %(fragment_condition)s
141 order by narrative
142 limit 25
143 """ ]
144 )
145 mp.setThresholds(2, 4, 6)
146 self._PRW_procedure.matcher = mp
147 #----------------------------------------------------------------
149 if self._PRW_hospital_stay.GetData() is None:
150 self._PRW_hospital_stay.SetText()
151 self._PRW_episode.Enable(True)
152 else:
153 self._PRW_location.SetText()
154 self._PRW_episode.SetText()
155 self._PRW_episode.Enable(False)
156 #----------------------------------------------------------------
158 if self._PRW_location.GetValue().strip() == u'':
159 return
160
161 self._PRW_hospital_stay.SetText()
162 self._PRW_episode.Enable(True)
163 #----------------------------------------------------------------
164 # generic Edit Area mixin API
165 #----------------------------------------------------------------
167
168 has_errors = False
169
170 if not self._DPRW_date.is_valid_timestamp():
171 self._DPRW_date.display_as_valid(False)
172 has_errors = True
173 else:
174 self._DPRW_date.display_as_valid(True)
175
176 if self._PRW_hospital_stay.GetData() is None:
177 if self._PRW_episode.GetData() is None:
178 self._PRW_episode.display_as_valid(False)
179 has_errors = True
180 else:
181 self._PRW_episode.display_as_valid(True)
182 else:
183 self._PRW_episode.display_as_valid(True)
184
185 if (self._PRW_procedure.GetValue() is None) or (self._PRW_procedure.GetValue().strip() == u''):
186 self._PRW_procedure.display_as_valid(False)
187 has_errors = True
188 else:
189 self._PRW_procedure.display_as_valid(True)
190
191 invalid_location = (
192 (self._PRW_hospital_stay.GetData() is None) and (self._PRW_location.GetValue().strip() == u'')
193 or
194 (self._PRW_hospital_stay.GetData() is not None) and (self._PRW_location.GetValue().strip() != u'')
195 )
196 if invalid_location:
197 self._PRW_hospital_stay.display_as_valid(False)
198 self._PRW_location.display_as_valid(False)
199 has_errors = True
200 else:
201 self._PRW_hospital_stay.display_as_valid(True)
202 self._PRW_location.display_as_valid(True)
203
204 wxps.Publisher().sendMessage (
205 topic = 'statustext',
206 data = {'msg': _('Cannot save procedure.'), 'beep': True}
207 )
208
209 return (has_errors is False)
210 #----------------------------------------------------------------
212
213 pat = gmPerson.gmCurrentPatient()
214 emr = pat.get_emr()
215
216 if self._PRW_hospital_stay.GetData() is None:
217 epi = self._PRW_episode.GetData()
218 else:
219 stay = gmEMRStructItems.cHospitalStay(aPK_obj = self._PRW_hospital_stay.GetData())
220 epi = stay['pk_episode']
221
222 proc = emr.add_performed_procedure (
223 episode = epi,
224 location = self._PRW_location.GetValue().strip(),
225 hospital_stay = self._PRW_hospital_stay.GetData(),
226 procedure = self._PRW_procedure.GetValue().strip()
227 )
228 proc['clin_when'] = self._DPRW_date.data.get_pydt()
229 proc.save()
230
231 self.data = proc
232
233 return True
234 #----------------------------------------------------------------
236 self.data['clin_when'] = self._DPRW_date.data.get_pydt()
237
238 if self._PRW_hospital_stay.GetData() is None:
239 self.data['pk_hospital_stay'] = None
240 self.data['clin_where'] = self._PRW_location.GetValue().strip()
241 self.data['pk_episode'] = self._PRW_episode.GetData()
242 else:
243 self.data['pk_hospital_stay'] = self._PRW_hospital_stay.GetData()
244 self.data['clin_where'] = None
245 stay = gmEMRStructItems.cHospitalStay(aPK_obj = self._PRW_hospital_stay.GetData())
246 self.data['pk_episode'] = stay['pk_episode']
247
248 self.data['performed_procedure'] = self._PRW_procedure.GetValue().strip()
249
250 self.data.save()
251 return True
252 #----------------------------------------------------------------
254 self._DPRW_date.SetText()
255 self._PRW_hospital_stay.SetText()
256 self._PRW_location.SetText()
257 self._PRW_episode.SetText()
258 self._PRW_procedure.SetText()
259
260 self._DPRW_date.SetFocus()
261 #----------------------------------------------------------------
263 self._DPRW_date.SetData(data = self.data['clin_when'])
264 self._PRW_episode.SetText(value = self.data['episode'], data = self.data['pk_episode'])
265 self._PRW_procedure.SetText(value = self.data['performed_procedure'], data = self.data['performed_procedure'])
266
267 if self.data['pk_hospital_stay'] is None:
268 self._PRW_hospital_stay.SetText()
269 self._PRW_location.SetText(value = self.data['clin_where'], data = self.data['clin_where'])
270 else:
271 self._PRW_hospital_stay.SetText(value = self.data['clin_where'], data = self.data['pk_hospital_stay'])
272 self._PRW_location.SetText()
273
274 self._DPRW_date.SetFocus()
275 #----------------------------------------------------------------
277 self._refresh_as_new()
278 self._PRW_episode.SetText(value = self.data['episode'], data = self.data['pk_episode'])
279 if self.data['pk_hospital_stay'] is None:
280 self._PRW_hospital_stay.SetText()
281 self._PRW_location.SetText(value = self.data['clin_where'], data = self.data['clin_where'])
282 else:
283 self._PRW_hospital_stay.SetText(value = self.data['clin_where'], data = self.data['pk_hospital_stay'])
284 self._PRW_location.SetText()
285
286 self._DPRW_date.SetFocus()
287 #----------------------------------------------------------------
291 #================================================================
292 # hospital stay related widgets/functions
293 #----------------------------------------------------------------
295
296 pat = gmPerson.gmCurrentPatient()
297 emr = pat.get_emr()
298
299 if parent is None:
300 parent = wx.GetApp().GetTopWindow()
301 #-----------------------------------------
302 def edit(stay=None):
303 return edit_hospital_stay(parent = parent, hospital_stay = stay)
304 #-----------------------------------------
305 def delete(stay=None):
306 if gmEMRStructItems.delete_hospital_stay(stay = stay['pk_hospital_stay']):
307 return True
308 gmDispatcher.send (
309 signal = u'statustext',
310 msg = _('Cannot delete hospital stay.'),
311 beep = True
312 )
313 return False
314 #-----------------------------------------
315 def refresh(lctrl):
316 stays = emr.get_hospital_stays()
317 items = [
318 [
319 s['admission'].strftime('%Y-%m-%d'),
320 gmTools.coalesce(s['discharge'], u''),
321 s['episode'],
322 gmTools.coalesce(s['hospital'], u'')
323 ] for s in stays
324 ]
325 lctrl.set_string_items(items = items)
326 lctrl.set_data(data = stays)
327 #-----------------------------------------
328 gmListWidgets.get_choices_from_list (
329 parent = parent,
330 msg = _('\nSelect the hospital stay you want to edit !\n'),
331 caption = _('Editing hospital stays ...'),
332 columns = [_('Admission'), _('Discharge'), _('Reason'), _('Hospital')],
333 single_selection = True,
334 edit_callback = edit,
335 new_callback = edit,
336 delete_callback = delete,
337 refresh_callback = refresh
338 )
339
340 #----------------------------------------------------------------
342 ea = cHospitalStayEditAreaPnl(parent = parent, id = -1)
343 ea.data = hospital_stay
344 ea.mode = gmTools.coalesce(hospital_stay, 'new', 'edit')
345 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = True)
346 dlg.SetTitle(gmTools.coalesce(hospital_stay, _('Adding a hospital stay'), _('Editing a hospital stay')))
347 if dlg.ShowModal() == wx.ID_OK:
348 dlg.Destroy()
349 return True
350 dlg.Destroy()
351 return False
352 #----------------------------------------------------------------
354 """Phrasewheel to allow selection of a hospital stay.
355 """
357
358 gmPhraseWheel.cPhraseWheel.__init__ (self, *args, **kwargs)
359
360 ctxt = {'ctxt_pat': {'where_part': u'pk_patient = %(pat)s and', 'placeholder': u'pat'}}
361
362 mp = gmMatchProvider.cMatchProvider_SQL2 (
363 queries = [
364 u"""
365 select
366 pk_hospital_stay,
367 descr
368 from (
369 select distinct on (pk_hospital_stay)
370 pk_hospital_stay,
371 descr
372 from
373 (select
374 pk_hospital_stay,
375 (
376 to_char(admission, 'YYYY-Mon-DD')
377 || coalesce((' (' || hospital || '):'), ': ')
378 || episode
379 || coalesce((' (' || health_issue || ')'), '')
380 ) as descr
381 from
382 clin.v_pat_hospital_stays
383 where
384 %(ctxt_pat)s
385
386 hospital %(fragment_condition)s
387 or
388 episode %(fragment_condition)s
389 or
390 health_issue %(fragment_condition)s
391 ) as the_stays
392 ) as distinct_stays
393 order by descr
394 limit 25
395 """ ],
396 context = ctxt
397 )
398 mp.setThresholds(3, 4, 6)
399 mp.set_context('pat', gmPerson.gmCurrentPatient().ID)
400
401 self.matcher = mp
402 self.selection_only = True
403 #----------------------------------------------------------------
404 from Gnumed.wxGladeWidgets import wxgHospitalStayEditAreaPnl
405
406 -class cHospitalStayEditAreaPnl(wxgHospitalStayEditAreaPnl.wxgHospitalStayEditAreaPnl, gmEditArea.cGenericEditAreaMixin):
407
409 wxgHospitalStayEditAreaPnl.wxgHospitalStayEditAreaPnl.__init__(self, *args, **kwargs)
410 gmEditArea.cGenericEditAreaMixin.__init__(self)
411 #----------------------------------------------------------------
412 # generic Edit Area mixin API
413 #----------------------------------------------------------------
415 if not self._DP_admission.GetValue().IsValid():
416 self._DP_admission.SetBackgroundColour(gmPhraseWheel.color_prw_invalid)
417 wxps.Publisher().sendMessage (
418 topic = 'statustext',
419 data = {'msg': _('Missing admission data. Cannot save hospital stay.'), 'beep': True}
420 )
421 return False
422 else:
423 self._DP_admission.SetBackgroundColour(gmPhraseWheel.color_prw_valid)
424
425 if self._DP_discharge.GetValue().IsValid():
426 if not self._DP_discharge.GetValue().IsLaterThan(self._DP_admission.GetValue()):
427 self._DP_discharge.SetBackgroundColour(gmPhraseWheel.color_prw_invalid)
428 wxps.Publisher().sendMessage (
429 topic = 'statustext',
430 data = {'msg': _('Discharge date must be empty or later than admission. Cannot save hospital stay.'), 'beep': True}
431 )
432 return False
433
434 return True
435 #----------------------------------------------------------------
437
438 pat = gmPerson.gmCurrentPatient()
439 emr = pat.get_emr()
440
441 stay = gmEMRStructItems.create_hospital_stay (
442 encounter = emr.active_encounter['pk_encounter'],
443 episode = self._PRW_episode.GetData(can_create = True)
444 )
445 stay['hospital'] = gmTools.none_if(self._PRW_hospital.GetValue().strip(), u'')
446 stay['admission'] = gmDateTime.wxDate2py_dt(wxDate = self._DP_admission.GetValue())
447 if self._DP_discharge.GetValue().IsValid():
448 stay['discharge'] = gmDateTime.wxDate2py_dt(wxDate = self._DP_discharge.GetValue())
449 stay.save_payload()
450
451 self.data = stay
452 return True
453 #----------------------------------------------------------------
455
456 self.data['pk_episode'] = self._PRW_episode.GetData(can_create = True)
457 self.data['hospital'] = gmTools.none_if(self._PRW_hospital.GetValue().strip(), u'')
458 self.data['admission'] = gmDateTime.wxDate2py_dt(wxDate = self._DP_admission.GetValue())
459 if self._DP_discharge.GetValue().IsValid():
460 self.data['discharge'] = gmDateTime.wxDate2py_dt(wxDate = self._DP_discharge.GetValue())
461 self.data.save_payload()
462
463 return True
464 #----------------------------------------------------------------
466 self._PRW_hospital.SetText(value = u'')
467 self._PRW_episode.SetText(value = u'')
468 self._DP_admission.SetValue(dt = wx.DateTime.UNow())
469 #self._DP_discharge.SetValue(dt = None)
470 #----------------------------------------------------------------
472 if self.data['hospital'] is not None:
473 self._PRW_hospital.SetText(value = self.data['hospital'])
474
475 if self.data['pk_episode'] is not None:
476 self._PRW_episode.SetText(value = self.data['episode'], data = self.data['pk_episode'])
477
478 self._DP_admission.SetValue(gmDateTime.py_dt2wxDate(py_dt = self.data['admission'], wx = wx))
479
480 if self.data['discharge'] is not None:
481 self._DP_discharge.SetValue(gmDateTime.py_dt2wxDate(py_dt = self.data['discharge'], wx = wx))
482 #----------------------------------------------------------------
485 #================================================================
486 # encounter related widgets/functions
487 #----------------------------------------------------------------
489 emr.start_new_encounter()
490 gmDispatcher.send(signal = 'statustext', msg = _('Started a new encounter for the active patient.'), beep = True)
491 time.sleep(0.5)
492 gmGuiHelpers.gm_show_info (
493 _('\nA new encounter was started for the active patient.\n'),
494 _('Start of new encounter')
495 )
496 #----------------------------------------------------------------
498
499 if parent is None:
500 parent = wx.GetApp().GetTopWindow()
501
502 # FIXME: use generic dialog 2
503 dlg = cEncounterEditAreaDlg(parent = parent, encounter = encounter)
504 dlg.ShowModal()
505 #----------------------------------------------------------------
507
508 if patient is None:
509 patient = gmPerson.gmCurrentPatient()
510
511 if not patient.connected:
512 gmDispatcher.send(signal = 'statustext', msg = _('Cannot list encounters. No active patient.'))
513 return False
514
515 if parent is None:
516 parent = wx.GetApp().GetTopWindow()
517
518 emr = patient.get_emr()
519
520 #--------------------
521 def refresh(lctrl):
522 if encounters is not None:
523 encs = encounters
524 else:
525 encs = emr.get_encounters()
526
527 items = [
528 [
529 e['started'].strftime('%x %H:%M'),
530 e['last_affirmed'].strftime('%H:%M'),
531 e['l10n_type'],
532 gmTools.coalesce(e['reason_for_encounter'], u''),
533 gmTools.coalesce(e['assessment_of_encounter'], u''),
534 gmTools.bool2subst(e.has_clinical_data(), u'', gmTools.u_checkmark_thin),
535 e['pk_encounter']
536 ] for e in encs
537 ]
538
539 lctrl.set_string_items(items = items)
540 lctrl.set_data(data = encs)
541 #--------------------
542 def edit(enc = None):
543 return edit_encounter(parent = parent, encounter = enc)
544 #--------------------
545 return gmListWidgets.get_choices_from_list (
546 parent = parent,
547 msg = _('\nBelow find the relevant encounters of the patient.\n'),
548 caption = _('Encounters ...'),
549 columns = [_('Started'), _('Ended'), _('Type'), _('Reason for Encounter'), _('Assessment of Encounter'), _('Empty'), '#'],
550 can_return_empty = True,
551 single_selection = single_selection,
552 refresh_callback = refresh,
553 edit_callback = edit
554 )
555 #----------------------------------------------------------------
557
558 if parent is None:
559 parent = wx.GetApp().GetTopWindow()
560
561 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
562 parent = None,
563 id = -1,
564 caption = caption,
565 question = msg,
566 button_defs = [
567 {'label': _('Continue'), 'tooltip': _('Continue the existing recent encounter.'), 'default': False},
568 {'label': _('Start new'), 'tooltip': _('Start a new encounter. The existing one will be closed.'), 'default': True}
569 ],
570 show_checkbox = False
571 )
572
573 result = dlg.ShowModal()
574 dlg.Destroy()
575
576 if result == wx.ID_YES:
577 return True
578
579 return False
580 #----------------------------------------------------------------
582
583 if parent is None:
584 parent = wx.GetApp().GetTopWindow()
585
586 #--------------------
587 def edit(enc_type=None):
588 return edit_encounter_type(parent = parent, encounter_type = enc_type)
589 #--------------------
590 def delete(enc_type=None):
591 if gmEMRStructItems.delete_encounter_type(description = enc_type['description']):
592 return True
593 gmDispatcher.send (
594 signal = u'statustext',
595 msg = _('Cannot delete encounter type [%s]. It is in use.') % enc_type['l10n_description'],
596 beep = True
597 )
598 return False
599 #--------------------
600 def refresh(lctrl):
601 enc_types = gmEMRStructItems.get_encounter_types()
602 lctrl.set_string_items(items = enc_types)
603 #--------------------
604 gmListWidgets.get_choices_from_list (
605 parent = parent,
606 msg = _('\nSelect the encounter type you want to edit !\n'),
607 caption = _('Managing encounter types ...'),
608 columns = [_('Local name'), _('Encounter type')],
609 single_selection = True,
610 edit_callback = edit,
611 new_callback = edit,
612 delete_callback = delete,
613 refresh_callback = refresh
614 )
615 #----------------------------------------------------------------
617 ea = cEncounterTypeEditAreaPnl(parent = parent, id = -1)
618 ea.data = encounter_type
619 ea.mode = gmTools.coalesce(encounter_type, 'new', 'edit')
620 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea)
621 dlg.SetTitle(gmTools.coalesce(encounter_type, _('Adding new encounter type'), _('Editing local encounter type name')))
622 if dlg.ShowModal() == wx.ID_OK:
623 return True
624 return False
625 #----------------------------------------------------------------
627 """Phrasewheel to allow selection of encounter type.
628
629 - user input interpreted as encounter type in English or local language
630 - data returned is pk of corresponding encounter type or None
631 """
633
634 gmPhraseWheel.cPhraseWheel.__init__ (self, *args, **kwargs)
635
636 mp = gmMatchProvider.cMatchProvider_SQL2 (
637 queries = [
638 u"""
639 select pk, l10n_description from (
640 select distinct on (pk) * from (
641 (select
642 pk,
643 _(description) as l10n_description,
644 1 as rank
645 from
646 clin.encounter_type
647 where
648 _(description) %(fragment_condition)s
649
650 ) union all (
651
652 select
653 pk,
654 _(description) as l10n_description,
655 2 as rank
656 from
657 clin.encounter_type
658 where
659 description %(fragment_condition)s
660 )
661 ) as q_distinct_pk
662 ) as q_ordered order by rank, l10n_description
663 """ ]
664 )
665 mp.setThresholds(2, 4, 6)
666
667 self.matcher = mp
668 self.selection_only = True
669 self.picklist_delay = 50
670 #----------------------------------------------------------------
671 -class cEncounterTypeEditAreaPnl(wxgEncounterTypeEditAreaPnl.wxgEncounterTypeEditAreaPnl, gmEditArea.cGenericEditAreaMixin):
672
674
675 wxgEncounterTypeEditAreaPnl.wxgEncounterTypeEditAreaPnl.__init__(self, *args, **kwargs)
676 gmEditArea.cGenericEditAreaMixin.__init__(self)
677
678 # self.__register_interests()
679 #-------------------------------------------------------
680 # generic edit area API
681 #-------------------------------------------------------
683 if self.mode == 'edit':
684 if self._TCTRL_l10n_name.GetValue().strip() == u'':
685 self.display_tctrl_as_valid(tctrl = self._TCTRL_l10n_name, valid = False)
686 return False
687 self.display_tctrl_as_valid(tctrl = self._TCTRL_l10n_name, valid = True)
688 return True
689
690 no_errors = True
691
692 if self._TCTRL_l10n_name.GetValue().strip() == u'':
693 if self._TCTRL_name.GetValue().strip() == u'':
694 self.display_tctrl_as_valid(tctrl = self._TCTRL_l10n_name, valid = False)
695 no_errors = False
696 else:
697 self.display_tctrl_as_valid(tctrl = self._TCTRL_l10n_name, valid = True)
698 else:
699 self.display_tctrl_as_valid(tctrl = self._TCTRL_l10n_name, valid = True)
700
701 if self._TCTRL_name.GetValue().strip() == u'':
702 if self._TCTRL_l10n_name.GetValue().strip() == u'':
703 self.display_tctrl_as_valid(tctrl = self._TCTRL_name, valid = False)
704 no_errors = False
705 else:
706 self.display_tctrl_as_valid(tctrl = self._TCTRL_name, valid = True)
707 else:
708 self.display_tctrl_as_valid(tctrl = self._TCTRL_name, valid = True)
709
710 return no_errors
711 #-------------------------------------------------------
713 enc_type = gmEMRStructItems.create_encounter_type (
714 description = gmTools.none_if(self._TCTRL_name.GetValue().strip(), u''),
715 l10n_description = gmTools.coalesce (
716 gmTools.none_if(self._TCTRL_l10n_name.GetValue().strip(), u''),
717 self._TCTRL_name.GetValue().strip()
718 )
719 )
720 if enc_type is None:
721 return False
722 self.data = enc_type
723 return True
724 #-------------------------------------------------------
726 enc_type = gmEMRStructItems.update_encounter_type (
727 description = self._TCTRL_name.GetValue().strip(),
728 l10n_description = self._TCTRL_l10n_name.GetValue().strip()
729 )
730 if enc_type is None:
731 return False
732 self.data = enc_type
733 return True
734 #-------------------------------------------------------
736 self._TCTRL_l10n_name.SetValue(u'')
737 self._TCTRL_name.SetValue(u'')
738 self._TCTRL_name.Enable(True)
739 #-------------------------------------------------------
741 self._TCTRL_l10n_name.SetValue(self.data['l10n_description'])
742 self._TCTRL_name.SetValue(self.data['description'])
743 # disallow changing type on all encounters by editing system name
744 self._TCTRL_name.Enable(False)
745 #-------------------------------------------------------
750 #-------------------------------------------------------
751 # internal API
752 #-------------------------------------------------------
753 # def __register_interests(self):
754 # return
755 #----------------------------------------------------------------
757
759 try:
760 self.__encounter = kwargs['encounter']
761 del kwargs['encounter']
762 except KeyError:
763 self.__encounter = None
764
765 try:
766 msg = kwargs['msg']
767 del kwargs['msg']
768 except KeyError:
769 msg = None
770
771 wxgEncounterEditAreaPnl.wxgEncounterEditAreaPnl.__init__(self, *args, **kwargs)
772
773 self.refresh(msg = msg)
774 #--------------------------------------------------------
775 # external API
776 #--------------------------------------------------------
778
779 if msg is not None:
780 self._LBL_instructions.SetLabel(msg)
781
782 if encounter is not None:
783 self.__encounter = encounter
784
785 if self.__encounter is None:
786 return True
787
788 # getting the patient via the encounter allows us to act
789 # on any encounter regardless of the currently active patient
790 pat = gmPerson.cPatient(aPK_obj = self.__encounter['pk_patient'])
791 self._LBL_patient.SetLabel(pat.get_description_gender())
792
793 self._PRW_encounter_type.SetText(self.__encounter['l10n_type'], data=self.__encounter['pk_type'])
794
795 fts = gmDateTime.cFuzzyTimestamp (
796 timestamp = self.__encounter['started'],
797 accuracy = gmDateTime.acc_minutes
798 )
799 self._PRW_start.SetText(fts.format_accurately(), data=fts)
800
801 fts = gmDateTime.cFuzzyTimestamp (
802 timestamp = self.__encounter['last_affirmed'],
803 accuracy = gmDateTime.acc_minutes
804 )
805 self._PRW_end.SetText(fts.format_accurately(), data=fts)
806
807 self._TCTRL_rfe.SetValue(gmTools.coalesce(self.__encounter['reason_for_encounter'], ''))
808 self._TCTRL_aoe.SetValue(gmTools.coalesce(self.__encounter['assessment_of_encounter'], ''))
809
810 if self.__encounter['last_affirmed'] == self.__encounter['started']:
811 self._PRW_end.SetFocus()
812 else:
813 self._TCTRL_aoe.SetFocus()
814
815 return True
816 #--------------------------------------------------------
818
819 if self._PRW_encounter_type.GetData() is None:
820 self._PRW_encounter_type.SetBackgroundColour('pink')
821 self._PRW_encounter_type.Refresh()
822 self._PRW_encounter_type.SetFocus()
823 return False
824 self._PRW_encounter_type.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
825 self._PRW_encounter_type.Refresh()
826
827 if not self._PRW_start.is_valid_timestamp():
828 self._PRW_start.SetFocus()
829 return False
830
831 if not self._PRW_end.is_valid_timestamp():
832 self._PRW_end.SetFocus()
833 return False
834
835 return True
836 #--------------------------------------------------------
838 if not self.__is_valid_for_save():
839 return False
840
841 self.__encounter['pk_type'] = self._PRW_encounter_type.GetData()
842 self.__encounter['started'] = self._PRW_start.GetData().get_pydt()
843 self.__encounter['last_affirmed'] = self._PRW_end.GetData().get_pydt()
844 self.__encounter['reason_for_encounter'] = gmTools.none_if(self._TCTRL_rfe.GetValue().strip(), u'')
845 self.__encounter['assessment_of_encounter'] = gmTools.none_if(self._TCTRL_aoe.GetValue().strip(), u'')
846 self.__encounter.save_payload() # FIXME: error checking
847
848 return True
849 #----------------------------------------------------------------
850 # FIXME: use generic dialog 2
852
854 encounter = kwargs['encounter']
855 del kwargs['encounter']
856
857 try:
858 button_defs = kwargs['button_defs']
859 del kwargs['button_defs']
860 except KeyError:
861 button_defs = None
862
863 try:
864 msg = kwargs['msg']
865 del kwargs['msg']
866 except KeyError:
867 msg = None
868
869 wxgEncounterEditAreaDlg.wxgEncounterEditAreaDlg.__init__(self, *args, **kwargs)
870 self.SetSize((450, 280))
871 self.SetMinSize((450, 280))
872
873 if button_defs is not None:
874 self._BTN_save.SetLabel(button_defs[0][0])
875 self._BTN_save.SetToolTipString(button_defs[0][1])
876 self._BTN_close.SetLabel(button_defs[1][0])
877 self._BTN_close.SetToolTipString(button_defs[1][1])
878 self.Refresh()
879
880 self._PNL_edit_area.refresh(encounter = encounter, msg = msg)
881
882 self.Fit()
883 #--------------------------------------------------------
890 #================================================================
891 # episode related widgets/functions
892 #----------------------------------------------------------------
894 ea = cEpisodeEditAreaPnl(parent = parent, id = -1)
895 ea.data = episode
896 ea.mode = gmTools.coalesce(episode, 'new', 'edit')
897 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = True)
898 dlg.SetTitle(gmTools.coalesce(episode, _('Adding a new episode'), _('Editing an episode')))
899 if dlg.ShowModal() == wx.ID_OK:
900 return True
901 return False
902 #----------------------------------------------------------------
904
905 created_new_issue = False
906
907 try:
908 issue = gmEMRStructItems.cHealthIssue(name = episode['description'], patient = episode['pk_patient'])
909 except gmExceptions.NoSuchBusinessObjectError:
910 issue = None
911
912 if issue is None:
913 issue = emr.add_health_issue(issue_name = episode['description'])
914 created_new_issue = True
915 else:
916 # issue exists already, so ask user
917 dlg = gmGuiHelpers.c3ButtonQuestionDlg (
918 parent,
919 -1,
920 caption = _('Promoting episode to health issue'),
921 question = _(
922 'There already is a health issue\n'
923 '\n'
924 ' %s\n'
925 '\n'
926 'What do you want to do ?'
927 ) % issue['description'],
928 button_defs = [
929 {'label': _('Use existing'), 'tooltip': _('Move episode into existing health issue'), 'default': False},
930 {'label': _('Create new'), 'tooltip': _('Create a new health issue with another name'), 'default': True}
931 ]
932 )
933 use_existing = dlg.ShowModal()
934 dlg.Destroy()
935
936 if use_existing == wx.ID_CANCEL:
937 return
938
939 # user wants to create new issue with alternate name
940 if use_existing == wx.ID_NO:
941 # loop until name modified but non-empty or cancelled
942 issue_name = episode['description']
943 while issue_name == episode['description']:
944 dlg = wx.TextEntryDialog (
945 parent = parent,
946 message = _('Enter a short descriptive name for the new health issue:'),
947 caption = _('Creating a new health issue ...'),
948 defaultValue = issue_name,
949 style = wx.OK | wx.CANCEL | wx.CENTRE
950 )
951 decision = dlg.ShowModal()
952 if decision != wx.ID_OK:
953 dlg.Destroy()
954 return
955 issue_name = dlg.GetValue().strip()
956 dlg.Destroy()
957 if issue_name == u'':
958 issue_name = episode['description']
959
960 issue = emr.add_health_issue(issue_name = issue_name)
961 created_new_issue = True
962
963 # eventually move the episode to the issue
964 if not move_episode_to_issue(episode = episode, target_issue = issue, save_to_backend = True):
965 # user cancelled the move so delete just-created issue
966 if created_new_issue:
967 # shouldn't fail as it is completely new
968 gmEMRStructItems.delete_health_issue(health_issue = issue)
969 return
970
971 return
972 #----------------------------------------------------------------
974 """Prepare changing health issue for an episode.
975
976 Checks for two-open-episodes conflict. When this
977 function succeeds, the pk_health_issue has been set
978 on the episode instance and the episode should - for
979 all practical purposes - be ready for save_payload().
980 """
981 # episode is closed: should always work
982 if not episode['episode_open']:
983 episode['pk_health_issue'] = target_issue['pk_health_issue']
984 if save_to_backend:
985 episode.save_payload()
986 return True
987
988 # un-associate: should always work, too
989 if target_issue is None:
990 episode['pk_health_issue'] = None
991 if save_to_backend:
992 episode.save_payload()
993 return True
994
995 # try closing possibly expired episode on target issue if any
996 db_cfg = gmCfg.cCfgSQL()
997 epi_ttl = int(db_cfg.get2 (
998 option = u'episode.ttl',
999 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1000 bias = 'user',
1001 default = 60 # 2 months
1002 ))
1003 if target_issue.close_expired_episode(ttl=epi_ttl) is True:
1004 gmDispatcher.send(signal='statustext', msg=_('Closed episodes older than %s days on health issue [%s]') % (epi_ttl, target_issue['description']))
1005 existing_epi = target_issue.get_open_episode()
1006
1007 # no more open episode on target issue: should work now
1008 if existing_epi is None:
1009 episode['pk_health_issue'] = target_issue['pk_health_issue']
1010 if save_to_backend:
1011 episode.save_payload()
1012 return True
1013
1014 # don't conflict on SELF ;-)
1015 if existing_epi['pk_episode'] == episode['pk_episode']:
1016 episode['pk_health_issue'] = target_issue['pk_health_issue']
1017 if save_to_backend:
1018 episode.save_payload()
1019 return True
1020
1021 # we got two open episodes at once, ask user
1022 move_range = episode.get_access_range()
1023 exist_range = existing_epi.get_access_range()
1024 question = _(
1025 'You want to associate the running episode:\n\n'
1026 ' "%(new_epi_name)s" (%(new_epi_start)s - %(new_epi_end)s)\n\n'
1027 'with the health issue:\n\n'
1028 ' "%(issue_name)s"\n\n'
1029 'There already is another episode running\n'
1030 'for this health issue:\n\n'
1031 ' "%(old_epi_name)s" (%(old_epi_start)s - %(old_epi_end)s)\n\n'
1032 'However, there can only be one running\n'
1033 'episode per health issue.\n\n'
1034 'Which episode do you want to close ?'
1035 ) % {
1036 'new_epi_name': episode['description'],
1037 'new_epi_start': move_range[0].strftime('%m/%y'),
1038 'new_epi_end': move_range[1].strftime('%m/%y'),
1039 'issue_name': target_issue['description'],
1040 'old_epi_name': existing_epi['description'],
1041 'old_epi_start': exist_range[0].strftime('%m/%y'),
1042 'old_epi_end': exist_range[1].strftime('%m/%y')
1043 }
1044 dlg = gmGuiHelpers.c3ButtonQuestionDlg (
1045 parent = None,
1046 id = -1,
1047 caption = _('Resolving two-running-episodes conflict'),
1048 question = question,
1049 button_defs = [
1050 {'label': _('old episode'), 'default': True, 'tooltip': _('close existing episode "%s"') % existing_epi['description']},
1051 {'label': _('new episode'), 'default': False, 'tooltip': _('close moving (new) episode "%s"') % episode['description']}
1052 ]
1053 )
1054 decision = dlg.ShowModal()
1055
1056 if decision == wx.ID_CANCEL:
1057 # button 3: move cancelled by user
1058 return False
1059
1060 elif decision == wx.ID_YES:
1061 # button 1: close old episode
1062 existing_epi['episode_open'] = False
1063 existing_epi.save_payload()
1064
1065 elif decision == wx.ID_NO:
1066 # button 2: close new episode
1067 episode['episode_open'] = False
1068
1069 else:
1070 raise ValueError('invalid result from c3ButtonQuestionDlg: [%s]' % decision)
1071
1072 episode['pk_health_issue'] = target_issue['pk_health_issue']
1073 if save_to_backend:
1074 episode.save_payload()
1075 return True
1076 #----------------------------------------------------------------
1078
1079 # FIXME: support pre-selection
1080
1082
1083 episodes = kwargs['episodes']
1084 del kwargs['episodes']
1085
1086 gmListWidgets.cGenericListSelectorDlg.__init__(self, *args, **kwargs)
1087
1088 self.SetTitle(_('Select the episodes you are interested in ...'))
1089 self._LCTRL_items.set_columns([_('Episode'), _('Status'), _('Health Issue')])
1090 self._LCTRL_items.set_string_items (
1091 items = [
1092 [ epi['description'],
1093 gmTools.bool2str(epi['episode_open'], _('ongoing'), u''),
1094 gmTools.coalesce(epi['health_issue'], u'')
1095 ]
1096 for epi in episodes ]
1097 )
1098 self._LCTRL_items.set_column_widths()
1099 self._LCTRL_items.set_data(data = episodes)
1100 #----------------------------------------------------------------
1102 """Let user select an episode *description*.
1103
1104 The user can select an episode description from the previously
1105 used descriptions across all episodes across all patients.
1106
1107 Selection is done with a phrasewheel so the user can
1108 type the episode name and matches will be shown. Typing
1109 "*" will show the entire list of episodes.
1110
1111 If the user types a description not existing yet a
1112 new episode description will be returned.
1113 """
1115
1116 mp = gmMatchProvider.cMatchProvider_SQL2 (
1117 queries = [u"""
1118 select distinct on (description) description, description, 1
1119 from clin.episode
1120 where description %(fragment_condition)s
1121 order by description
1122 limit 30"""
1123 ]
1124 )
1125 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
1126 self.matcher = mp
1127 #----------------------------------------------------------------
1129 """Let user select an episode.
1130
1131 The user can select an episode from the existing episodes of a
1132 patient. Selection is done with a phrasewheel so the user
1133 can type the episode name and matches will be shown. Typing
1134 "*" will show the entire list of episodes. Closed episodes
1135 will be marked as such. If the user types an episode name not
1136 in the list of existing episodes a new episode can be created
1137 from it if the programmer activated that feature.
1138
1139 If keyword <patient_id> is set to None or left out the control
1140 will listen to patient change signals and therefore act on
1141 gmPerson.gmCurrentPatient() changes.
1142 """
1144
1145 ctxt = {'ctxt_pat': {'where_part': u'and pk_patient = %(pat)s', 'placeholder': u'pat'}}
1146
1147 mp = gmMatchProvider.cMatchProvider_SQL2 (
1148 queries = [
1149 u"""(
1150
1151 select
1152 pk_episode,
1153 coalesce (
1154 description || ' - ' || health_issue,
1155 description
1156 ) as description,
1157 1 as rank
1158 from
1159 clin.v_pat_episodes
1160 where
1161 episode_open is true and
1162 description %(fragment_condition)s
1163 %(ctxt_pat)s
1164
1165 ) union all (
1166
1167 select
1168 pk_episode,
1169 coalesce (
1170 description || _(' (closed)') || ' - ' || health_issue,
1171 description || _(' (closed)')
1172 ) as description,
1173 2 as rank
1174 from
1175 clin.v_pat_episodes
1176 where
1177 description %(fragment_condition)s and
1178 episode_open is false
1179 %(ctxt_pat)s
1180
1181 )
1182 order by rank, description
1183 limit 30"""
1184 ],
1185 context = ctxt
1186 )
1187
1188 try:
1189 kwargs['patient_id']
1190 except KeyError:
1191 kwargs['patient_id'] = None
1192
1193 if kwargs['patient_id'] is None:
1194 self.use_current_patient = True
1195 self.__register_patient_change_signals()
1196 pat = gmPerson.gmCurrentPatient()
1197 if pat.connected:
1198 mp.set_context('pat', pat.ID)
1199 else:
1200 self.use_current_patient = False
1201 self.__patient_id = int(kwargs['patient_id'])
1202 mp.set_context('pat', self.__patient_id)
1203
1204 del kwargs['patient_id']
1205
1206 gmPhraseWheel.cPhraseWheel.__init__ (
1207 self,
1208 *args,
1209 **kwargs
1210 )
1211 self.matcher = mp
1212 #--------------------------------------------------------
1213 # external API
1214 #--------------------------------------------------------
1216 if self.use_current_patient:
1217 return False
1218 self.__patient_id = int(patient_id)
1219 self.set_context('pat', self.__patient_id)
1220 return True
1221 #--------------------------------------------------------
1223 if self.data is None:
1224 if can_create:
1225 epi_name = self.GetValue().strip()
1226 if epi_name == u'':
1227 gmDispatcher.send(signal = u'statustext', msg = _('Cannot create episode without name.'), beep = True)
1228 _log.debug('cannot create episode without name')
1229 else:
1230 if self.use_current_patient:
1231 pat = gmPerson.gmCurrentPatient()
1232 else:
1233 pat = gmPerson.cPatient(aPK_obj=self.__patient_id)
1234
1235 emr = pat.get_emr()
1236 epi = emr.add_episode(episode_name = epi_name, is_open = is_open)
1237 if epi is None:
1238 self.data = None
1239 else:
1240 self.data = epi['pk_episode']
1241
1242 return gmPhraseWheel.cPhraseWheel.GetData(self)
1243 #--------------------------------------------------------
1244 # internal API
1245 #--------------------------------------------------------
1247 gmDispatcher.connect(self._pre_patient_selection, u'pre_patient_selection')
1248 gmDispatcher.connect(self._post_patient_selection, u'post_patient_selection')
1249 #--------------------------------------------------------
1252 #--------------------------------------------------------
1254 if self.use_current_patient:
1255 patient = gmPerson.gmCurrentPatient()
1256 self.set_context('pat', patient.ID)
1257 return True
1258 #----------------------------------------------------------------
1259 -class cEpisodeEditAreaPnl(gmEditArea.cGenericEditAreaMixin, wxgEpisodeEditAreaPnl.wxgEpisodeEditAreaPnl):
1260
1262
1263 try:
1264 episode = kwargs['episode']
1265 del kwargs['episode']
1266 except KeyError:
1267 episode = None
1268
1269 wxgEpisodeEditAreaPnl.wxgEpisodeEditAreaPnl.__init__(self, *args, **kwargs)
1270 gmEditArea.cGenericEditAreaMixin.__init__(self)
1271
1272 self.data = episode
1273 #----------------------------------------------------------------
1274 # generic Edit Area mixin API
1275 #----------------------------------------------------------------
1277
1278 errors = False
1279
1280 if len(self._PRW_description.GetValue().strip()) == 0:
1281 errors = True
1282 self._PRW_description.display_as_valid(False)
1283 self._PRW_description.SetFocus()
1284 else:
1285 self._PRW_description.display_as_valid(True)
1286 self._PRW_description.Refresh()
1287
1288 return not errors
1289 #----------------------------------------------------------------
1291
1292 pat = gmPerson.gmCurrentPatient()
1293 emr = pat.get_emr()
1294
1295 epi = emr.add_episode(episode_name = self._PRW_description.GetValue().strip())
1296 epi['episode_open'] = not self._CHBOX_closed.IsChecked()
1297 epi['diagnostic_certainty_classification'] = self._PRW_classification.GetData()
1298
1299 issue_name = self._PRW_issue.GetValue().strip()
1300 if len(issue_name) != 0:
1301 epi['pk_health_issue'] = self._PRW_issue.GetData(can_create = True)
1302 issue = gmEMRStructItems.cHealthIssue(aPK_obj = epi['pk_health_issue'])
1303
1304 if not move_episode_to_issue(episode = epi, target_issue = issue, save_to_backend = False):
1305 gmDispatcher.send (
1306 signal = 'statustext',
1307 msg = _('Cannot attach episode [%s] to health issue [%s] because it already has a running episode.') % (
1308 epi['description'],
1309 issue['description']
1310 )
1311 )
1312 gmEMRStructItems.delete_episode(episode = epi)
1313 return False
1314
1315 epi.save()
1316
1317 self.data = epi
1318 return True
1319 #----------------------------------------------------------------
1321
1322 self.data['description'] = self._PRW_description.GetValue().strip()
1323 self.data['episode_open'] = not self._CHBOX_closed.IsChecked()
1324 self.data['diagnostic_certainty_classification'] = self._PRW_classification.GetData()
1325
1326 issue_name = self._PRW_issue.GetValue().strip()
1327 if len(issue_name) != 0:
1328 self.data['pk_health_issue'] = self._PRW_issue.GetData(can_create = True)
1329 issue = gmEMRStructItems.cHealthIssue(aPK_obj = self.data['pk_health_issue'])
1330
1331 if not move_episode_to_issue(episode = self.data, target_issue = issue, save_to_backend = False):
1332 gmDispatcher.send (
1333 signal = 'statustext',
1334 msg = _('Cannot attach episode [%s] to health issue [%s] because it already has a running episode.') % (
1335 self.data['description'],
1336 issue['description']
1337 )
1338 )
1339 return False
1340
1341 self.data.save()
1342 return True
1343 #----------------------------------------------------------------
1345 if self.data is None:
1346 ident = gmPerson.gmCurrentPatient()
1347 else:
1348 ident = gmPerson.cIdentity(aPK_obj = self.data['pk_patient'])
1349 self._TCTRL_patient.SetValue(ident.get_description_gender())
1350 self._PRW_issue.SetText()
1351 self._PRW_description.SetText()
1352 self._PRW_classification.SetText()
1353 self._CHBOX_closed.SetValue(False)
1354 #----------------------------------------------------------------
1356 ident = gmPerson.cIdentity(aPK_obj = self.data['pk_patient'])
1357 self._TCTRL_patient.SetValue(ident.get_description_gender())
1358
1359 if self.data['pk_health_issue'] is not None:
1360 self._PRW_issue.SetText(self.data['health_issue'], data=self.data['pk_health_issue'])
1361
1362 self._PRW_description.SetText(self.data['description'], data=self.data['description'])
1363
1364 if self.data['diagnostic_certainty_classification'] is not None:
1365 self._PRW_classification.SetData(data = self.data['diagnostic_certainty_classification'])
1366
1367 self._CHBOX_closed.SetValue(not self.data['episode_open'])
1368 #----------------------------------------------------------------
1371 #================================================================
1372 # health issue related widgets/functions
1373 #----------------------------------------------------------------
1375 ea = cHealthIssueEditAreaPnl(parent = parent, id = -1)
1376 ea.data = issue
1377 ea.mode = gmTools.coalesce(issue, 'new', 'edit')
1378 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = True)
1379 dlg.SetTitle(gmTools.coalesce(issue, _('Adding a new health issue'), _('Editing a health issue')))
1380 if dlg.ShowModal() == wx.ID_OK:
1381 return True
1382 return False
1383 #----------------------------------------------------------------
1385
1386 # FIXME: support pre-selection
1387
1389
1390 issues = kwargs['issues']
1391 del kwargs['issues']
1392
1393 gmListWidgets.cGenericListSelectorDlg.__init__(self, *args, **kwargs)
1394
1395 self.SetTitle(_('Select the health issues you are interested in ...'))
1396 self._LCTRL_items.set_columns([u'', _('Health Issue'), u'', u'', u''])
1397
1398 for issue in issues:
1399 if issue['is_confidential']:
1400 row_num = self._LCTRL_items.InsertStringItem(sys.maxint, label = _('confidential'))
1401 self._LCTRL_items.SetItemTextColour(row_num, col=wx.NamedColour('RED'))
1402 else:
1403 row_num = self._LCTRL_items.InsertStringItem(sys.maxint, label = u'')
1404
1405 self._LCTRL_items.SetStringItem(index = row_num, col = 1, label = issue['description'])
1406 if issue['clinically_relevant']:
1407 self._LCTRL_items.SetStringItem(index = row_num, col = 2, label = _('relevant'))
1408 if issue['is_active']:
1409 self._LCTRL_items.SetStringItem(index = row_num, col = 3, label = _('active'))
1410 if issue['is_cause_of_death']:
1411 self._LCTRL_items.SetStringItem(index = row_num, col = 4, label = _('fatal'))
1412
1413 self._LCTRL_items.set_column_widths()
1414 self._LCTRL_items.set_data(data = issues)
1415 #----------------------------------------------------------------
1417 """Let the user select a health issue.
1418
1419 The user can select a health issue from the existing issues
1420 of a patient. Selection is done with a phrasewheel so the user
1421 can type the issue name and matches will be shown. Typing
1422 "*" will show the entire list of issues. Inactive issues
1423 will be marked as such. If the user types an issue name not
1424 in the list of existing issues a new issue can be created
1425 from it if the programmer activated that feature.
1426
1427 If keyword <patient_id> is set to None or left out the control
1428 will listen to patient change signals and therefore act on
1429 gmPerson.gmCurrentPatient() changes.
1430 """
1432
1433 ctxt = {'ctxt_pat': {'where_part': u'pk_patient=%(pat)s', 'placeholder': u'pat'}}
1434
1435 mp = gmMatchProvider.cMatchProvider_SQL2 (
1436 # FIXME: consider clin.health_issue.clinically_relevant
1437 queries = [u"""
1438 (select pk_health_issue, description, 1
1439 from clin.v_health_issues where
1440 is_active is true and
1441 description %(fragment_condition)s and
1442 %(ctxt_pat)s
1443 order by description)
1444
1445 union
1446
1447 (select pk_health_issue, description || _(' (inactive)'), 2
1448 from clin.v_health_issues where
1449 is_active is false and
1450 description %(fragment_condition)s and
1451 %(ctxt_pat)s
1452 order by description)"""
1453 ],
1454 context = ctxt
1455 )
1456
1457 try: kwargs['patient_id']
1458 except KeyError: kwargs['patient_id'] = None
1459
1460 if kwargs['patient_id'] is None:
1461 self.use_current_patient = True
1462 self.__register_patient_change_signals()
1463 pat = gmPerson.gmCurrentPatient()
1464 if pat.connected:
1465 mp.set_context('pat', pat.ID)
1466 else:
1467 self.use_current_patient = False
1468 self.__patient_id = int(kwargs['patient_id'])
1469 mp.set_context('pat', self.__patient_id)
1470
1471 del kwargs['patient_id']
1472
1473 gmPhraseWheel.cPhraseWheel.__init__ (
1474 self,
1475 *args,
1476 **kwargs
1477 )
1478 self.matcher = mp
1479 #--------------------------------------------------------
1480 # external API
1481 #--------------------------------------------------------
1483 if self.use_current_patient:
1484 return False
1485 self.__patient_id = int(patient_id)
1486 self.set_context('pat', self.__patient_id)
1487 return True
1488 #--------------------------------------------------------
1490 if self.data is None:
1491 if can_create:
1492 issue_name = self.GetValue().strip()
1493
1494 if self.use_current_patient:
1495 pat = gmPerson.gmCurrentPatient()
1496 else:
1497 pat = gmPerson.cPatient(aPK_obj=self.__patient_id)
1498 emr = pat.get_emr()
1499
1500 issue = emr.add_health_issue(issue_name = issue_name)
1501 if issue is None:
1502 self.data = None
1503 else:
1504 self.data = issue['pk_health_issue']
1505
1506 return gmPhraseWheel.cPhraseWheel.GetData(self)
1507 #--------------------------------------------------------
1508 # internal API
1509 #--------------------------------------------------------
1511 gmDispatcher.connect(self._pre_patient_selection, u'pre_patient_selection')
1512 gmDispatcher.connect(self._post_patient_selection, u'post_patient_selection')
1513 #--------------------------------------------------------
1516 #--------------------------------------------------------
1518 if self.use_current_patient:
1519 patient = gmPerson.gmCurrentPatient()
1520 self.set_context('pat', patient.ID)
1521 return True
1522 #------------------------------------------------------------
1524
1526 try:
1527 msg = kwargs['message']
1528 except KeyError:
1529 msg = None
1530 del kwargs['message']
1531 wxgIssueSelectionDlg.wxgIssueSelectionDlg.__init__(self, *args, **kwargs)
1532 if msg is not None:
1533 self._lbl_message.SetLabel(label=msg)
1534 #--------------------------------------------------------
1545 #------------------------------------------------------------
1546 -class cHealthIssueEditAreaPnl(gmEditArea.cGenericEditAreaMixin, wxgHealthIssueEditAreaPnl.wxgHealthIssueEditAreaPnl):
1547 """Panel encapsulating health issue edit area functionality."""
1548
1550
1551 try:
1552 issue = kwargs['issue']
1553 except KeyError:
1554 issue = None
1555
1556 wxgHealthIssueEditAreaPnl.wxgHealthIssueEditAreaPnl.__init__(self, *args, **kwargs)
1557
1558 gmEditArea.cGenericEditAreaMixin.__init__(self)
1559
1560 # FIXME: include more sources: coding systems/other database columns
1561 mp = gmMatchProvider.cMatchProvider_SQL2 (
1562 queries = [u"select distinct on (description) description, description from clin.health_issue where description %(fragment_condition)s limit 50"]
1563 )
1564 mp.setThresholds(1, 3, 5)
1565 self._PRW_condition.matcher = mp
1566
1567 mp = gmMatchProvider.cMatchProvider_SQL2 (
1568 queries = [u"""
1569 select distinct on (grouping) grouping, grouping from (
1570
1571 select rank, grouping from ((
1572
1573 select
1574 grouping,
1575 1 as rank
1576 from
1577 clin.health_issue
1578 where
1579 grouping %%(fragment_condition)s
1580 and
1581 (select True from clin.encounter where fk_patient = %s and pk = clin.health_issue.fk_encounter)
1582
1583 ) union (
1584
1585 select
1586 grouping,
1587 2 as rank
1588 from
1589 clin.health_issue
1590 where
1591 grouping %%(fragment_condition)s
1592
1593 )) as union_result
1594
1595 order by rank
1596
1597 ) as order_result
1598
1599 limit 50""" % gmPerson.gmCurrentPatient().ID
1600 ]
1601 )
1602 mp.setThresholds(1, 3, 5)
1603 self._PRW_grouping.matcher = mp
1604
1605 self._PRW_age_noted.add_callback_on_lose_focus(self._on_leave_age_noted)
1606 self._PRW_year_noted.add_callback_on_lose_focus(self._on_leave_year_noted)
1607
1608 self._PRW_age_noted.add_callback_on_modified(self._on_modified_age_noted)
1609 self._PRW_year_noted.add_callback_on_modified(self._on_modified_year_noted)
1610
1611 self.data = issue
1612 #----------------------------------------------------------------
1613 # generic Edit Area mixin API
1614 #----------------------------------------------------------------
1616
1617 if self._PRW_condition.GetValue().strip() == '':
1618 self._PRW_condition.display_as_valid(False)
1619 self._PRW_condition.SetFocus()
1620 return False
1621 self._PRW_condition.display_as_valid(True)
1622 self._PRW_condition.Refresh()
1623
1624 # FIXME: sanity check age/year diagnosed
1625 age_noted = self._PRW_age_noted.GetValue().strip()
1626 if age_noted != '':
1627 if gmDateTime.str2interval(str_interval = age_noted) is None:
1628 self._PRW_age_noted.display_as_valid(False)
1629 self._PRW_age_noted.SetFocus()
1630 return False
1631 self._PRW_age_noted.display_as_valid(True)
1632
1633 return True
1634 #----------------------------------------------------------------
1636 pat = gmPerson.gmCurrentPatient()
1637 emr = pat.get_emr()
1638
1639 issue = emr.add_health_issue(issue_name = self._PRW_condition.GetValue().strip())
1640
1641 side = u''
1642 if self._ChBOX_left.GetValue():
1643 side += u's'
1644 if self._ChBOX_right.GetValue():
1645 side += u'd'
1646 issue['laterality'] = side
1647
1648 issue['diagnostic_certainty_classification'] = self._PRW_classification.GetData()
1649 issue['grouping'] = self._PRW_grouping.GetValue().strip()
1650 issue['is_active'] = self._ChBOX_active.GetValue()
1651 issue['clinically_relevant'] = self._ChBOX_relevant.GetValue()
1652 issue['is_confidential'] = self._ChBOX_confidential.GetValue()
1653 issue['is_cause_of_death'] = self._ChBOX_caused_death.GetValue()
1654
1655 age_noted = self._PRW_age_noted.GetData()
1656 if age_noted is not None:
1657 issue['age_noted'] = age_noted
1658
1659 issue.save()
1660
1661 narr = self._TCTRL_notes.GetValue().strip()
1662 if narr != u'':
1663 epi = emr.add_episode(episode_name = _('inception notes'), pk_health_issue = issue['pk_health_issue'])
1664 emr.add_clin_narrative(note = narr, soap_cat = 's', episode = epi)
1665
1666 self.data = issue
1667
1668 return True
1669 #----------------------------------------------------------------
1671 # update self.data and save the changes
1672
1673 self.data['description'] = self._PRW_condition.GetValue().strip()
1674
1675 side = u''
1676 if self._ChBOX_left.GetValue():
1677 side += u's'
1678 if self._ChBOX_right.GetValue():
1679 side += u'd'
1680 self.data['laterality'] = side
1681
1682 self.data['diagnostic_certainty_classification'] = self._PRW_classification.GetData()
1683 self.data['grouping'] = self._PRW_grouping.GetValue().strip()
1684 self.data['is_active'] = bool(self._ChBOX_active.GetValue())
1685 self.data['clinically_relevant'] = bool(self._ChBOX_relevant.GetValue())
1686 self.data['is_confidential'] = bool(self._ChBOX_confidential.GetValue())
1687 self.data['is_cause_of_death'] = bool(self._ChBOX_caused_death.GetValue())
1688
1689 age_noted = self._PRW_age_noted.GetData()
1690 if age_noted is not None:
1691 self.data['age_noted'] = age_noted
1692
1693 self.data.save()
1694
1695 narr = self._TCTRL_notes.GetValue().strip()
1696 if narr != '':
1697 pat = gmPerson.gmCurrentPatient()
1698 emr = pat.get_emr()
1699 epi = emr.add_episode(episode_name = _('inception notes'), pk_health_issue = self.data['pk_health_issue'])
1700 emr.add_clin_narrative(note = narr, soap_cat = 's', episode = epi)
1701
1702 # FIXME: handle is_operation
1703 return True
1704 #----------------------------------------------------------------
1706 self._PRW_condition.SetText()
1707 self._ChBOX_left.SetValue(0)
1708 self._ChBOX_right.SetValue(0)
1709 self._PRW_classification.SetText()
1710 self._PRW_grouping.SetText()
1711 self._TCTRL_notes.SetValue(u'')
1712 self._PRW_age_noted.SetText()
1713 self._PRW_year_noted.SetText()
1714 self._ChBOX_active.SetValue(0)
1715 self._ChBOX_relevant.SetValue(1)
1716 self._ChBOX_is_operation.SetValue(0)
1717 self._ChBOX_confidential.SetValue(0)
1718 self._ChBOX_caused_death.SetValue(0)
1719
1720 return True
1721 #----------------------------------------------------------------
1723 self._PRW_condition.SetText(self.data['description'])
1724
1725 lat = gmTools.coalesce(self.data['laterality'], '')
1726 if lat.find('s') == -1:
1727 self._ChBOX_left.SetValue(0)
1728 else:
1729 self._ChBOX_left.SetValue(1)
1730 if lat.find('d') == -1:
1731 self._ChBOX_right.SetValue(0)
1732 else:
1733 self._ChBOX_right.SetValue(1)
1734
1735 self._PRW_classification.SetData(data = self.data['diagnostic_certainty_classification'])
1736 self._PRW_grouping.SetText(gmTools.coalesce(self.data['grouping'], u''))
1737 self._TCTRL_notes.SetValue('')
1738
1739 if self.data['age_noted'] is None:
1740 self._PRW_age_noted.SetText()
1741 else:
1742 self._PRW_age_noted.SetText (
1743 value = '%sd' % self.data['age_noted'].days,
1744 data = self.data['age_noted']
1745 )
1746
1747 self._ChBOX_active.SetValue(self.data['is_active'])
1748 self._ChBOX_relevant.SetValue(self.data['clinically_relevant'])
1749 self._ChBOX_is_operation.SetValue(0) # FIXME
1750 self._ChBOX_confidential.SetValue(self.data['is_confidential'])
1751 self._ChBOX_caused_death.SetValue(self.data['is_cause_of_death'])
1752
1753 # this dance should assure self._PRW_year_noted gets set -- but it doesn't ...
1754 # self._PRW_age_noted.SetFocus()
1755 # self._PRW_condition.SetFocus()
1756
1757 return True
1758 #----------------------------------------------------------------
1761 #--------------------------------------------------------
1762 # internal helpers
1763 #--------------------------------------------------------
1765
1766 if not self._PRW_age_noted.IsModified():
1767 return True
1768
1769 str_age = self._PRW_age_noted.GetValue().strip()
1770
1771 if str_age == u'':
1772 wx.CallAfter(self._PRW_year_noted.SetText, u'', None, True)
1773 return True
1774
1775 age = gmDateTime.str2interval(str_interval = str_age)
1776 pat = gmPerson.gmCurrentPatient()
1777 max_age = pydt.datetime.now(tz=pat['dob'].tzinfo) - pat['dob']
1778
1779 if age is None:
1780 gmDispatcher.send(signal='statustext', msg=_('Cannot parse [%s] into valid interval.') % str_age)
1781 self._PRW_age_noted.SetBackgroundColour('pink')
1782 self._PRW_age_noted.Refresh()
1783 wx.CallAfter(self._PRW_year_noted.SetText, u'', None, True)
1784 return True
1785
1786 if age >= max_age:
1787 gmDispatcher.send (
1788 signal = 'statustext',
1789 msg = _(
1790 'Health issue cannot have been noted at age %s. Patient is only %s old.'
1791 ) % (age, pat.get_medical_age())
1792 )
1793 self._PRW_age_noted.SetBackgroundColour('pink')
1794 self._PRW_age_noted.Refresh()
1795 wx.CallAfter(self._PRW_year_noted.SetText, u'', None, True)
1796 return True
1797
1798 self._PRW_age_noted.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1799 self._PRW_age_noted.Refresh()
1800 self._PRW_age_noted.SetData(data=age)
1801
1802 fts = gmDateTime.cFuzzyTimestamp (
1803 timestamp = pat['dob'] + age,
1804 accuracy = gmDateTime.acc_months
1805 )
1806 wx.CallAfter(self._PRW_year_noted.SetText, str(fts), fts)
1807 # if we do this we will *always* navigate there, regardless of TAB vs ALT-TAB
1808 #wx.CallAfter(self._ChBOX_active.SetFocus)
1809 # if we do the following instead it will take us to the save/update button ...
1810 #wx.CallAfter(self.Navigate)
1811
1812 return True
1813 #--------------------------------------------------------
1815
1816 if not self._PRW_year_noted.IsModified():
1817 return True
1818
1819 year_noted = self._PRW_year_noted.GetData()
1820
1821 if year_noted is None:
1822 if self._PRW_year_noted.GetValue().strip() == u'':
1823 wx.CallAfter(self._PRW_age_noted.SetText, u'', None, True)
1824 return True
1825 self._PRW_year_noted.SetBackgroundColour('pink')
1826 self._PRW_year_noted.Refresh()
1827 wx.CallAfter(self._PRW_age_noted.SetText, u'', None, True)
1828 return True
1829
1830 year_noted = year_noted.get_pydt()
1831
1832 if year_noted >= pydt.datetime.now(tz=year_noted.tzinfo):
1833 gmDispatcher.send(signal='statustext', msg=_('Condition diagnosed in the future.'))
1834 self._PRW_year_noted.SetBackgroundColour('pink')
1835 self._PRW_year_noted.Refresh()
1836 wx.CallAfter(self._PRW_age_noted.SetText, u'', None, True)
1837 return True
1838
1839 self._PRW_year_noted.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
1840 self._PRW_year_noted.Refresh()
1841
1842 pat = gmPerson.gmCurrentPatient()
1843 issue_age = year_noted - pat['dob']
1844 str_age = gmDateTime.format_interval_medically(interval = issue_age)
1845 wx.CallAfter(self._PRW_age_noted.SetText, str_age, issue_age)
1846
1847 return True
1848 #--------------------------------------------------------
1852 #--------------------------------------------------------
1856 #================================================================
1857 # diagnostic certainty related widgets/functions
1858 #----------------------------------------------------------------
1860
1862
1863 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
1864
1865 self.selection_only = False # can be NULL, too
1866
1867 mp = gmMatchProvider.cMatchProvider_FixedList (
1868 aSeq = [
1869 {'data': u'A', 'label': gmEMRStructItems.diagnostic_certainty_classification2str(u'A'), 'weight': 1},
1870 {'data': u'B', 'label': gmEMRStructItems.diagnostic_certainty_classification2str(u'B'), 'weight': 1},
1871 {'data': u'C', 'label': gmEMRStructItems.diagnostic_certainty_classification2str(u'C'), 'weight': 1},
1872 {'data': u'D', 'label': gmEMRStructItems.diagnostic_certainty_classification2str(u'D'), 'weight': 1}
1873 ]
1874 )
1875 mp.setThresholds(1, 2, 4)
1876 self.matcher = mp
1877
1878 self.SetToolTipString(_(
1879 "The diagnostic classification or grading of this assessment.\n"
1880 "\n"
1881 "This documents how certain one is about this being a true diagnosis."
1882 ))
1883 #================================================================
1884 # MAIN
1885 #----------------------------------------------------------------
1886 if __name__ == '__main__':
1887
1888 #================================================================
1890 """
1891 Test application for testing EMR struct widgets
1892 """
1893 #--------------------------------------------------------
1895 """
1896 Create test application UI
1897 """
1898 frame = wx.Frame (
1899 None,
1900 -4,
1901 'Testing EMR struct widgets',
1902 size=wx.Size(600, 400),
1903 style=wx.DEFAULT_FRAME_STYLE | wx.NO_FULL_REPAINT_ON_RESIZE
1904 )
1905 filemenu= wx.Menu()
1906 filemenu.AppendSeparator()
1907 filemenu.Append(ID_EXIT,"E&xit"," Terminate test application")
1908
1909 # Creating the menubar.
1910 menuBar = wx.MenuBar()
1911 menuBar.Append(filemenu,"&File")
1912
1913 frame.SetMenuBar(menuBar)
1914
1915 txt = wx.StaticText( frame, -1, _("Select desired test option from the 'File' menu"),
1916 wx.DefaultPosition, wx.DefaultSize, 0 )
1917
1918 # event handlers
1919 wx.EVT_MENU(frame, ID_EXIT, self.OnCloseWindow)
1920
1921 # patient EMR
1922 self.__pat = gmPerson.gmCurrentPatient()
1923
1924 frame.Show(1)
1925 return 1
1926 #--------------------------------------------------------
1932 #----------------------------------------------------------------
1934 app = wx.PyWidgetTester(size = (200, 300))
1935 emr = pat.get_emr()
1936 enc = emr.active_encounter
1937 #enc = gmEMRStructItems.cEncounter(1)
1938 pnl = cEncounterEditAreaPnl(app.frame, -1, encounter=enc)
1939 app.frame.Show(True)
1940 app.MainLoop()
1941 return
1942 #----------------------------------------------------------------
1944 app = wx.PyWidgetTester(size = (200, 300))
1945 emr = pat.get_emr()
1946 enc = emr.active_encounter
1947 #enc = gmEMRStructItems.cEncounter(1)
1948
1949 dlg = cEncounterEditAreaDlg(parent=app.frame, id=-1, size = (400,400), encounter=enc)
1950 dlg.ShowModal()
1951
1952 # pnl = cEncounterEditAreaDlg(app.frame, -1, encounter=enc)
1953 # app.frame.Show(True)
1954 # app.MainLoop()
1955 #----------------------------------------------------------------
1957 app = wx.PyWidgetTester(size = (200, 300))
1958 emr = pat.get_emr()
1959 epi = emr.get_episodes()[0]
1960 pnl = cEpisodeEditAreaPnl(app.frame, -1, episode=epi)
1961 app.frame.Show(True)
1962 app.MainLoop()
1963 #----------------------------------------------------------------
1965 app = wx.PyWidgetTester(size = (200, 300))
1966 emr = pat.get_emr()
1967 epi = emr.get_episodes()[0]
1968 edit_episode(parent=app.frame, episode=epi)
1969 #----------------------------------------------------------------
1971 app = wx.PyWidgetTester(size = (400, 40))
1972 app.SetWidget(cHospitalStayPhraseWheel, id=-1, size=(180,20), pos=(10,20))
1973 app.MainLoop()
1974 #----------------------------------------------------------------
1976 app = wx.PyWidgetTester(size = (400, 40))
1977 app.SetWidget(cEpisodeSelectionPhraseWheel, id=-1, size=(180,20), pos=(10,20))
1978 # app.SetWidget(cEpisodeSelectionPhraseWheel, id=-1, size=(350,20), pos=(10,20), patient_id=pat.ID)
1979 app.MainLoop()
1980 #----------------------------------------------------------------
1982 app = wx.PyWidgetTester(size = (200, 300))
1983 edit_health_issue(parent=app.frame, issue=None)
1984 #----------------------------------------------------------------
1986 app = wx.PyWidgetTester(size = (200, 300))
1987 app.SetWidget(cHealthIssueEditAreaPnl, id=-1, size = (400,400))
1988 app.MainLoop()
1989 #----------------------------------------------------------------
1993 #================================================================
1994
1995 if (len(sys.argv) > 1) and (sys.argv[1] == 'test'):
1996
1997 gmI18N.activate_locale()
1998 gmI18N.install_domain()
1999 gmDateTime.init()
2000
2001 # obtain patient
2002 pat = gmPerson.ask_for_patient()
2003 if pat is None:
2004 print "No patient. Exiting gracefully..."
2005 sys.exit(0)
2006 gmPatSearchWidgets.set_active_patient(patient=pat)
2007
2008 # try:
2009 # lauch emr dialogs test application
2010 # app = testapp(0)
2011 # app.MainLoop()
2012 # except StandardError:
2013 # _log.exception("unhandled exception caught !")
2014 # but re-raise them
2015 # raise
2016
2017 #test_encounter_edit_area_panel()
2018 #test_encounter_edit_area_dialog()
2019 #test_epsiode_edit_area_pnl()
2020 #test_episode_edit_area_dialog()
2021 #test_health_issue_edit_area_dlg()
2022 #test_episode_selection_prw()
2023 #test_hospital_stay_prw()
2024 test_edit_procedure()
2025
2026 #================================================================
2027 # $Log: gmEMRStructWidgets.py,v $
2028 # Revision 1.114 2010/02/06 21:01:27 ncq
2029 # - fix health issue editing
2030 #
2031 # Revision 1.113 2010/01/21 08:42:31 ncq
2032 # - adjust to Dx certainty mapper changes
2033 #
2034 # Revision 1.112 2009/12/03 17:48:15 ncq
2035 # - only create episode in GetData if there's a name available
2036 #
2037 # Revision 1.111 2009/12/01 10:50:44 ncq
2038 # - fix issue creation
2039 #
2040 # Revision 1.110 2009/11/15 01:05:44 ncq
2041 # - start-new-encounter
2042 # - enhance select-encounters
2043 #
2044 # Revision 1.109 2009/11/13 21:07:20 ncq
2045 # - fully implement procedure EA
2046 #
2047 # Revision 1.108 2009/11/06 15:17:46 ncq
2048 # - set better size on encounter EA
2049 #
2050 # Revision 1.107 2009/09/23 14:42:04 ncq
2051 # - implement procedure management
2052 # - implement promote-episode-to-issue
2053 #
2054 # Revision 1.106 2009/09/17 21:53:41 ncq
2055 # - start support for managing performed procedures
2056 #
2057 # Revision 1.105 2009/09/15 15:24:21 ncq
2058 # - start procedure EA
2059 # - phrasewheel for hospital stays
2060 #
2061 # Revision 1.104 2009/09/13 18:45:25 ncq
2062 # - no more get-active-encounter()
2063 #
2064 # Revision 1.103 2009/09/01 23:05:20 ncq
2065 # - improved tooltip for classification phrasewheel
2066 #
2067 # Revision 1.102 2009/09/01 22:29:09 ncq
2068 # - normalize issue/episode edit area handling, obsoleting their dialogs
2069 # - add edit_episode/edit_health_issue
2070 # - support diagnostic certainty
2071 #
2072 # Revision 1.101 2009/07/30 12:03:54 ncq
2073 # - fix editing age noted
2074 #
2075 # Revision 1.100 2009/07/16 09:52:15 ncq
2076 # - improved labelling
2077 #
2078 # Revision 1.99 2009/07/15 21:32:35 ncq
2079 # - add missing () thereby making changing enc type possible
2080 #
2081 # Revision 1.98 2009/07/09 16:46:20 ncq
2082 # - cleanup
2083 # - improved wording as per list
2084 #
2085 # Revision 1.97 2009/07/02 20:50:37 ncq
2086 # - use generic EA dlg 2
2087 #
2088 # Revision 1.96 2009/07/01 17:07:16 ncq
2089 # - in episode selector sort closed below unclosed episodes
2090 #
2091 # Revision 1.95 2009/06/29 15:05:52 ncq
2092 # - fix typo
2093 #
2094 # Revision 1.94 2009/06/11 12:37:25 ncq
2095 # - much simplified initial setup of list ctrls
2096 #
2097 # Revision 1.93 2009/06/04 16:30:30 ncq
2098 # - use set active patient from pat search widgets
2099 #
2100 # Revision 1.92 2009/05/13 13:12:21 ncq
2101 # - enable encounter editing right from the list
2102 #
2103 # Revision 1.91 2009/05/13 12:18:35 ncq
2104 # - streamline managing encounters
2105 #
2106 # Revision 1.90 2009/05/08 07:59:33 ncq
2107 # - cleanup, better display of encounter list
2108 #
2109 # Revision 1.89 2009/04/21 16:59:59 ncq
2110 # - edit area dlg now takes single_entry argument
2111 #
2112 # Revision 1.88 2009/04/19 22:26:47 ncq
2113 # - interval parsing moved
2114 #
2115 # Revision 1.87 2009/04/13 10:54:06 ncq
2116 # - show encounter list
2117 # - allow removing RFE/AOE from edit area
2118 #
2119 # Revision 1.86 2009/04/05 18:04:46 ncq
2120 # - support and use grouping
2121 #
2122 # Revision 1.85 2009/04/03 09:47:29 ncq
2123 # - hospital stay widgets
2124 #
2125 # Revision 1.84 2009/01/02 11:39:48 ncq
2126 # - support custom message/buttons in encounter edit area/dlg
2127 #
2128 # Revision 1.83 2008/12/09 23:28:51 ncq
2129 # - use description_gender
2130 #
2131 # Revision 1.82 2008/10/22 12:19:10 ncq
2132 # - rename pseudo episode on health issue creation to "inceptio notes"
2133 #
2134 # Revision 1.81 2008/10/12 16:15:17 ncq
2135 # - no more "foundational" health issue
2136 #
2137 # Revision 1.80 2008/09/02 19:01:12 ncq
2138 # - adjust to clin health_issue fk_patient drop and related changes
2139 #
2140 # Revision 1.79 2008/07/24 13:58:40 ncq
2141 # - manage encounter types
2142 #
2143 # Revision 1.78 2008/07/07 13:43:16 ncq
2144 # - current patient .connected
2145 #
2146 # Revision 1.77 2008/06/09 15:33:59 ncq
2147 # - improved episode selector SQL
2148 #
2149 # Revision 1.76 2008/05/13 14:11:53 ncq
2150 # - properly handle age=None in pHX ea
2151 #
2152 # Revision 1.75 2008/05/07 15:21:10 ncq
2153 # - move health issue EA behaviour close to Richard's specs
2154 #
2155 # Revision 1.74 2008/03/05 22:37:45 ncq
2156 # - new style logging
2157 # - new health issue adding
2158 #
2159 # Revision 1.73 2008/01/30 14:03:41 ncq
2160 # - use signal names directly
2161 # - switch to std lib logging
2162 #
2163 # Revision 1.72 2008/01/22 12:21:27 ncq
2164 # - better encounter editor
2165 #
2166 # Revision 1.71 2007/10/07 12:32:41 ncq
2167 # - workplace property now on gmSurgery.gmCurrentPractice() borg
2168 #
2169 # Revision 1.70 2007/08/29 22:08:57 ncq
2170 # - narrative widgets factored out
2171 #
2172 # Revision 1.69 2007/08/29 14:38:39 ncq
2173 # - improve encounter details dialog
2174 #
2175 # Revision 1.68 2007/08/15 09:19:32 ncq
2176 # - cleanup
2177 #
2178 # Revision 1.67 2007/08/13 22:00:48 ncq
2179 # - proper year format specs
2180 #
2181 # Revision 1.66 2007/08/13 11:07:41 ncq
2182 # - make episode descriptions phrasewheel actually *match*
2183 # on input, IOW, add a where clause to the select ;-)
2184 #
2185 # Revision 1.65 2007/08/12 00:09:07 ncq
2186 # - no more gmSignals.py
2187 #
2188 # Revision 1.64 2007/07/13 12:20:48 ncq
2189 # - select_narrative_from_episodes(), related widgets, and test suite
2190 #
2191 # Revision 1.63 2007/06/10 10:02:53 ncq
2192 # - episode pk is pk_episode
2193 #
2194 # Revision 1.62 2007/05/18 13:28:57 ncq
2195 # - implement cMoveNarrativeDlg
2196 #
2197 # Revision 1.61 2007/05/14 13:11:24 ncq
2198 # - use statustext() signal
2199 #
2200 # Revision 1.60 2007/04/27 13:28:25 ncq
2201 # - use c2ButtonQuestionDlg
2202 #
2203 # Revision 1.59 2007/04/25 22:00:47 ncq
2204 # - use better question dialog for very recent encounter
2205 #
2206 # Revision 1.58 2007/04/13 15:58:00 ncq
2207 # - don't conflict open episode on itself
2208 #
2209 # Revision 1.57 2007/04/02 18:39:52 ncq
2210 # - gmFuzzyTimestamp -> gmDateTime
2211 #
2212 # Revision 1.56 2007/03/31 21:50:15 ncq
2213 # - cPatient now cIdentity child
2214 #
2215 # Revision 1.55 2007/03/18 14:05:31 ncq
2216 # - re-add lost 1.55
2217 #
2218 # Revision 1.55 2007/03/12 12:27:13 ncq
2219 # - convert some statustext calls to use signal handler
2220 #
2221 # Revision 1.54 2007/03/08 11:39:13 ncq
2222 # - cEpisodeSelectionPhraseWheel
2223 # - limit 30
2224 # - order by weight, description
2225 # - show both open and closed
2226 # - include health issue string
2227 # - test suite
2228 #
2229 # Revision 1.53 2007/02/22 17:41:13 ncq
2230 # - adjust to gmPerson changes
2231 #
2232 # Revision 1.52 2007/02/17 14:13:11 ncq
2233 # - gmPerson.gmCurrentProvider().workplace now property
2234 #
2235 # Revision 1.51 2007/02/16 12:53:19 ncq
2236 # - now that we have cPhraseWheel.suppress_text_update_smarts we
2237 # can avoid infinite looping due to circular on_modified callbacks
2238 #
2239 # Revision 1.50 2007/02/09 14:59:39 ncq
2240 # - cleanup
2241 #
2242 # Revision 1.49 2007/02/06 13:43:40 ncq
2243 # - no more aDelay in __init__()
2244 #
2245 # Revision 1.48 2007/02/05 12:15:23 ncq
2246 # - no more aMatchProvider/selection_only in cPhraseWheel.__init__()
2247 #
2248 # Revision 1.47 2007/02/04 15:53:58 ncq
2249 # - use SetText()
2250 #
2251 # Revision 1.46 2007/01/15 20:22:09 ncq
2252 # - fix several bugs in move_episode_to_issue()
2253 #
2254 # Revision 1.45 2007/01/15 13:02:26 ncq
2255 # - completely revamped move_episode_to_issue() and use it
2256 #
2257 # Revision 1.44 2007/01/09 12:59:01 ncq
2258 # - datetime.timedelta needs int, not decimal, so make epi_ttl an int
2259 # - missing _ in front of CHBOX_closed
2260 # - save() needs to return True/False so dialog can close or not
2261 #
2262 # Revision 1.43 2007/01/04 23:29:02 ncq
2263 # - cEpisodeDescriptionPhraseWheel
2264 # - cEpisodeEditAreaPnl
2265 # - cEpisodeEditAreaDlg
2266 # - test suite enhancement
2267 #
2268 # Revision 1.42 2007/01/02 16:18:10 ncq
2269 # - health issue phrasewheel: clin.health_issue has fk_patient, not pk_patient
2270 #
2271 # Revision 1.41 2006/12/25 22:52:14 ncq
2272 # - encounter details editor
2273 # - set patient name
2274 # - valid_for_save(), save()
2275 # - properly set started/ended
2276 #
2277 # Revision 1.40 2006/12/23 01:07:28 ncq
2278 # - fix encounter type phrasewheel query
2279 # - add encounter details edit area panel/dialog
2280 # - still needs save() and friends fixed
2281 # - general cleanup/moving about of stuff
2282 # - fix move_episode_to_issue() logic
2283 # - start cleanup of test suite
2284 #
2285 # Revision 1.39 2006/11/28 20:44:36 ncq
2286 # - some rearrangement
2287 #
2288 # Revision 1.38 2006/11/27 23:15:01 ncq
2289 # - remove prints
2290 #
2291 # Revision 1.37 2006/11/27 23:05:49 ncq
2292 # - add commented out on_modified callbacks
2293 #
2294 # Revision 1.36 2006/11/27 12:40:20 ncq
2295 # - adapt to field name fixes from wxGlade
2296 #
2297 # Revision 1.35 2006/11/24 16:40:35 ncq
2298 # - age_noted can be NULL so handle set when refresh()ing health issue edit area
2299 #
2300 # Revision 1.34 2006/11/24 14:22:35 ncq
2301 # - cannot pass issue keyword to wx.Dialog child in cHealthIssueEditAreaDlg.__init__
2302 # - relabel buttons to save or update re clear/restore when adding/editing health issue
2303 # - EndModal needs argument
2304 #
2305 # Revision 1.33 2006/11/24 09:55:05 ncq
2306 # - cHealthIssueEditArea(Pnl/Dlg) closely following Richard's specs
2307 # - test code
2308 #
2309 # Revision 1.32 2006/11/15 00:40:07 ncq
2310 # - properly set up context for phrasewheels
2311 #
2312 # Revision 1.31 2006/10/31 17:21:16 ncq
2313 # - unicode()ify queries
2314 #
2315 # Revision 1.30 2006/10/24 13:22:40 ncq
2316 # - gmPG -> gmPG2
2317 # - no need for service name in cMatchProvider_SQL2()
2318 #
2319 # Revision 1.29 2006/09/03 11:30:28 ncq
2320 # - add move_episode_to_issue()
2321 #
2322 # Revision 1.28 2006/06/26 21:37:43 ncq
2323 # - cleanup
2324 #
2325 # Revision 1.27 2006/06/26 13:07:00 ncq
2326 # - fix issue selection phrasewheel SQL UNION
2327 # - improved variable naming
2328 # - track patient id in set_patient on issue/episode selection phrasewheel
2329 # so GetData can create new issues/episodes if told to do so
2330 # - add cIssueSelectionDlg
2331 #
2332 # Revision 1.26 2006/06/23 21:32:11 ncq
2333 # - add cIssueSelectionPhrasewheel
2334 #
2335 # Revision 1.25 2006/05/31 09:46:20 ncq
2336 # - cleanup
2337 #
2338 # Revision 1.24 2006/05/28 15:40:51 ncq
2339 # - fix typo in variable
2340 #
2341 # Revision 1.23 2006/05/25 22:19:25 ncq
2342 # - add preconfigured episode selection/creation phrasewheel
2343 # - cleanup, fix unit test
2344 #
2345 # Revision 1.22 2006/05/04 09:49:20 ncq
2346 # - get_clinical_record() -> get_emr()
2347 # - adjust to changes in set_active_patient()
2348 # - need explicit set_active_patient() after ask_for_patient() if wanted
2349 #
2350 # Revision 1.21 2005/12/26 05:26:37 sjtan
2351 #
2352 # match schema
2353 #
2354 # Revision 1.20 2005/12/26 04:23:05 sjtan
2355 #
2356 # match schema changes.
2357 #
2358 # Revision 1.19 2005/12/06 14:24:15 ncq
2359 # - clin.clin_health_issue/episode -> clin.health_issue/episode
2360 #
2361 # Revision 1.18 2005/10/20 07:42:27 ncq
2362 # - somewhat improved edit area for issue
2363 #
2364 # Revision 1.17 2005/10/08 12:33:09 sjtan
2365 # tree can be updated now without refetching entire cache; done by passing emr object to create_xxxx methods and calling emr.update_cache(key,obj);refresh_historical_tree non-destructively checks for changes and removes removed nodes and adds them if cache mismatch.
2366 #
2367 # Revision 1.16 2005/10/04 19:24:53 sjtan
2368 # browser now remembers expansion state and select state between change of patients, between health issue rename, episode rename or encounter relinking. This helps when reviewing the record more than once in a day.
2369 #
2370 # Revision 1.15 2005/09/27 20:44:58 ncq
2371 # - wx.wx* -> wx.*
2372 #
2373 # Revision 1.14 2005/09/26 18:01:50 ncq
2374 # - use proper way to import wx26 vs wx2.4
2375 # - note: THIS WILL BREAK RUNNING THE CLIENT IN SOME PLACES
2376 # - time for fixup
2377 #
2378 # Revision 1.13 2005/09/24 09:17:28 ncq
2379 # - some wx2.6 compatibility fixes
2380 #
2381 # Revision 1.12 2005/08/06 16:50:51 ncq
2382 # - zero-size spacer seems pointless and besides it
2383 # don't work like that in wx2.5
2384 #
2385 # Revision 1.11 2005/06/29 15:06:38 ncq
2386 # - defaults for edit area popup/editarea2 __init__
2387 #
2388 # Revision 1.10 2005/06/28 17:14:56 cfmoro
2389 # Auto size flag causes the text not being displayed
2390 #
2391 # Revision 1.9 2005/06/20 13:03:38 cfmoro
2392 # Relink encounter to another episode
2393 #
2394 # Revision 1.8 2005/06/10 23:22:43 ncq
2395 # - SQL2 match provider now requires query *list*
2396 #
2397 # Revision 1.7 2005/05/06 15:30:15 ncq
2398 # - attempt to properly set focus
2399 #
2400 # Revision 1.6 2005/04/25 08:30:59 ncq
2401 # - make past medical history proxy episodes closed by default
2402 #
2403 # Revision 1.5 2005/04/24 14:45:18 ncq
2404 # - cleanup, use generic edit area popup dialog
2405 # - "finalize" (as for 0.1) health issue edit area
2406 #
2407 # Revision 1.4 2005/04/20 22:09:54 ncq
2408 # - add edit area and popup dialog for health issue
2409 #
2410 # Revision 1.3 2005/03/14 14:36:31 ncq
2411 # - use simplified episode naming
2412 #
2413 # Revision 1.2 2005/01/31 18:51:08 ncq
2414 # - caching emr = patient.get_clinical_record() locally is unsafe
2415 # because patient can change but emr will stay the same (it's a
2416 # local "pointer", after all, and not a singleton)
2417 # - adding episodes actually works now
2418 #
2419 # Revision 1.1 2005/01/31 13:09:21 ncq
2420 # - this is OK to go in
2421 #
2422 # Revision 1.9 2005/01/31 13:06:02 ncq
2423 # - use gmPerson.ask_for_patient()
2424 #
2425 # Revision 1.8 2005/01/31 09:50:59 ncq
2426 # - gmPatient -> gmPerson
2427 #
2428 # Revision 1.7 2005/01/29 19:12:19 cfmoro
2429 # Episode creation on episode editor widget
2430 #
2431 # Revision 1.6 2005/01/29 18:01:20 ncq
2432 # - some cleanup
2433 # - actually create new episodes
2434 #
2435 # Revision 1.5 2005/01/28 18:05:56 cfmoro
2436 # Implemented episode picker and episode selector dialogs and widgets
2437 #
2438 # Revision 1.4 2005/01/24 16:57:38 ncq
2439 # - some cleanup here and there
2440 #
2441 #
2442
| Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Tue Feb 9 04:01:38 2010 | http://epydoc.sourceforge.net |