"""GNUmed EMR structure editors This module contains widgets to create and edit EMR structural elements (issues, enconters, episodes). This is based on initial work and ideas by Syan
and Karsten . """ #================================================================ __version__ = "$Revision: 1.114 $" __author__ = "address@hidden, address@hidden" __license__ = "GPL" # stdlib import sys, re, datetime as pydt, logging, time # 3rd party import wx import wx.lib.pubsub as wxps # GNUmed if __name__ == '__main__': sys.path.insert(0, '../../') from Gnumed.pycommon import gmI18N, gmMatchProvider, gmDispatcher, gmTools, gmDateTime, gmCfg, gmExceptions from Gnumed.business import gmEMRStructItems, gmPerson, gmSOAPimporter, gmSurgery, gmPersonSearch from Gnumed.wxpython import gmPhraseWheel, gmGuiHelpers, gmListWidgets, gmEditArea, gmPatSearchWidgets from Gnumed.wxGladeWidgets import wxgIssueSelectionDlg, wxgMoveNarrativeDlg from Gnumed.wxGladeWidgets import wxgEncounterTypeEditAreaPnl _log = logging.getLogger('gm.ui') _log.info(__version__) #================================================================ # performed procedure related widgets/functions #---------------------------------------------------------------- def manage_performed_procedures(parent=None): pat = gmPerson.gmCurrentPatient() emr = pat.get_emr() if parent is None: parent = wx.GetApp().GetTopWindow() #----------------------------------------- def edit(procedure=None): return edit_procedure(parent = parent, procedure = procedure) #----------------------------------------- def delete(procedure=None): if gmEMRStructItems.delete_performed_procedure(procedure = procedure['pk_procedure']): return True gmDispatcher.send ( signal = u'statustext', msg = _('Cannot delete performed procedure.'), beep = True ) return False #----------------------------------------- def refresh(lctrl): procs = emr.get_performed_procedures() items = [ [ u'%s%s' % ( p['clin_when'].strftime('%Y-%m-%d'), gmTools.bool2subst ( p['is_ongoing'], _(' (ongoing)'), gmTools.coalesce ( initial = p['clin_end'], instead = u'', template_initial = u' - %s', function_initial = ('strftime', u'%Y-%m-%d') ) ) ), p['clin_where'], p['episode'], p['performed_procedure'] ] for p in procs ] lctrl.set_string_items(items = items) lctrl.set_data(data = procs) #----------------------------------------- gmListWidgets.get_choices_from_list ( parent = parent, msg = _('\nSelect the procedure you want to edit !\n'), caption = _('Editing performed procedures ...'), columns = [_('When'), _('Where'), _('Episode'), _('Procedure')], single_selection = True, edit_callback = edit, new_callback = edit, delete_callback = delete, refresh_callback = refresh ) #---------------------------------------------------------------- def edit_procedure(parent=None, procedure=None): ea = cProcedureEAPnl(parent = parent, id = -1) ea.data = procedure ea.mode = gmTools.coalesce(procedure, 'new', 'edit') dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = True) dlg.SetTitle(gmTools.coalesce(procedure, _('Adding a procedure'), _('Editing a procedure'))) if dlg.ShowModal() == wx.ID_OK: dlg.Destroy() return True dlg.Destroy() return False #---------------------------------------------------------------- from Gnumed.wxGladeWidgets import wxgProcedureEAPnl class cProcedureEAPnl(wxgProcedureEAPnl.wxgProcedureEAPnl, gmEditArea.cGenericEditAreaMixin): def __init__(self, *args, **kwargs): wxgProcedureEAPnl.wxgProcedureEAPnl.__init__(self, *args, **kwargs) gmEditArea.cGenericEditAreaMixin.__init__(self) self.mode = 'new' self.data = None self.__init_ui() #---------------------------------------------------------------- def __init_ui(self): self._PRW_hospital_stay.add_callback_on_lose_focus(callback = self._on_hospital_stay_lost_focus) self._PRW_hospital_stay.set_context(context = 'pat', val = gmPerson.gmCurrentPatient().ID) self._PRW_location.add_callback_on_lose_focus(callback = self._on_location_lost_focus) self._DPRW_date.add_callback_on_lose_focus(callback = self._on_start_lost_focus) self._DPRW_end.add_callback_on_lose_focus(callback = self._on_end_lost_focus) # location mp = gmMatchProvider.cMatchProvider_SQL2 ( queries = [ u""" SELECT DISTINCT ON (data) data, location FROM ( SELECT clin_where as data, clin_where as location FROM clin.procedure WHERE clin_where %(fragment_condition)s UNION ALL SELECT narrative as data, narrative as location FROM clin.hospital_stay WHERE narrative %(fragment_condition)s ) as union_result ORDER BY data LIMIT 25""" ] ) mp.setThresholds(2, 4, 6) self._PRW_location.matcher = mp # procedure mp = gmMatchProvider.cMatchProvider_SQL2 ( queries = [ u""" select distinct on (narrative) narrative, narrative from clin.procedure where narrative %(fragment_condition)s order by narrative limit 25 """ ] ) mp.setThresholds(2, 4, 6) self._PRW_procedure.matcher = mp #---------------------------------------------------------------- def _on_hospital_stay_lost_focus(self): stay = self._PRW_hospital_stay.GetData() if stay is None: self._PRW_hospital_stay.SetText() self._PRW_location.Enable(True) self._PRW_episode.Enable(True) self._LBL_hospital_details.SetLabel(u'') else: self._PRW_location.SetText() self._PRW_location.Enable(False) self._PRW_episode.SetText() self._PRW_episode.Enable(False) self._LBL_hospital_details.SetLabel(gmEMRStructItems.cHospitalStay(aPK_obj = stay).format()) #---------------------------------------------------------------- def _on_location_lost_focus(self): if self._PRW_location.GetValue().strip() == u'': self._PRW_hospital_stay.Enable(True) # self._PRW_episode.Enable(False) else: self._PRW_hospital_stay.SetText() self._PRW_hospital_stay.Enable(False) self._PRW_hospital_stay.display_as_valid(True) # self._PRW_episode.Enable(True) #---------------------------------------------------------------- def _on_start_lost_focus(self): if not self._DPRW_date.is_valid_timestamp(): return end = self._DPRW_end.GetData() if end is None: return end = end.get_pydt() start = self._DPRW_date.GetData().get_pydt() if start < end: return self._DPRW_date.display_as_valid(False) #---------------------------------------------------------------- def _on_end_lost_focus(self): end = self._DPRW_end.GetData() if end is None: self._CHBOX_ongoing.Enable(True) self._DPRW_end.display_as_valid(True) else: self._CHBOX_ongoing.Enable(False) end = end.get_pydt() now = gmDateTime.pydt_now_here() if end > now: self._CHBOX_ongoing.SetValue(True) else: self._CHBOX_ongoing.SetValue(False) start = self._DPRW_date.GetData() if start is None: self._DPRW_end.display_as_valid(True) else: start = start.get_pydt() if end > start: self._DPRW_end.display_as_valid(True) else: self._DPRW_end.display_as_valid(False) #---------------------------------------------------------------- # generic Edit Area mixin API #---------------------------------------------------------------- def _valid_for_save(self): has_errors = False if not self._DPRW_date.is_valid_timestamp(): self._DPRW_date.display_as_valid(False) has_errors = True else: self._DPRW_date.display_as_valid(True) end = self._DPRW_end.GetData() self._DPRW_end.display_as_valid(True) if end is not None: end = end.get_pydt() start = self._DPRW_end.GetData() if start is not None: start = start.get_pydt() if end < start: has_errors = True self._DPRW_end.display_as_valid(False) if self._CHBOX_ongoing.IsChecked(): now = gmDateTime.pydt_now_here() if end < now: has_errors = True self._DPRW_end.display_as_valid(False) if self._PRW_hospital_stay.GetData() is None: if self._PRW_episode.GetData() is None: self._PRW_episode.display_as_valid(False) has_errors = True else: self._PRW_episode.display_as_valid(True) else: self._PRW_episode.display_as_valid(True) if (self._PRW_procedure.GetValue() is None) or (self._PRW_procedure.GetValue().strip() == u''): self._PRW_procedure.display_as_valid(False) has_errors = True else: self._PRW_procedure.display_as_valid(True) invalid_location = ( (self._PRW_hospital_stay.GetData() is None) and (self._PRW_location.GetValue().strip() == u'') or (self._PRW_hospital_stay.GetData() is not None) and (self._PRW_location.GetValue().strip() != u'') ) if invalid_location: self._PRW_hospital_stay.display_as_valid(False) self._PRW_location.display_as_valid(False) has_errors = True else: self._PRW_hospital_stay.display_as_valid(True) self._PRW_location.display_as_valid(True) wxps.Publisher().sendMessage ( topic = 'statustext', data = {'msg': _('Cannot save procedure.'), 'beep': True} ) return (has_errors is False) #---------------------------------------------------------------- def _save_as_new(self): pat = gmPerson.gmCurrentPatient() emr = pat.get_emr() if self._PRW_hospital_stay.GetData() is None: stay = None epi = self._PRW_episode.GetData() loc = self._PRW_location.GetValue().strip() else: stay = self._PRW_hospital_stay.GetData() epi = gmEMRStructItems.cHospitalStay(aPK_obj = stay)['pk_episode'] loc = None proc = emr.add_performed_procedure ( episode = epi, location = loc, hospital_stay = stay, procedure = self._PRW_procedure.GetValue().strip() ) proc['clin_when'] = self._DPRW_date.data.get_pydt() if self._DPRW_end.GetData() is None: proc['clin_end'] = None else: proc['clin_end'] = self._DPRW_end.GetData().get_pydt() proc['is_ongoing'] = self._CHBOX_ongoing.IsChecked() proc.save() self.data = proc return True #---------------------------------------------------------------- def _save_as_update(self): self.data['clin_when'] = self._DPRW_date.data.get_pydt() if self._DPRW_end.GetData() is None: self.data['clin_end'] = None else: self.data['clin_end'] = self._DPRW_end.GetData().get_pydt() self.data['is_ongoing'] = self._CHBOX_ongoing.IsChecked() if self._PRW_hospital_stay.GetData() is None: self.data['pk_hospital_stay'] = None self.data['clin_where'] = self._PRW_location.GetValue().strip() self.data['pk_episode'] = self._PRW_episode.GetData() else: self.data['pk_hospital_stay'] = self._PRW_hospital_stay.GetData() self.data['clin_where'] = None stay = gmEMRStructItems.cHospitalStay(aPK_obj = self._PRW_hospital_stay.GetData()) self.data['pk_episode'] = stay['pk_episode'] self.data['performed_procedure'] = self._PRW_procedure.GetValue().strip() self.data.save() return True #---------------------------------------------------------------- def _refresh_as_new(self): self._DPRW_date.SetText() self._DPRW_end.SetText() self._CHBOX_ongoing.SetValue(False) self._CHBOX_ongoing.Enable(True) self._PRW_hospital_stay.SetText() self._PRW_location.SetText() self._PRW_episode.SetText() self._PRW_procedure.SetText() self._PRW_procedure.SetFocus() #---------------------------------------------------------------- def _refresh_from_existing(self): self._DPRW_date.SetData(data = self.data['clin_when']) if self.data['clin_end'] is None: self._DPRW_end.SetText() self._CHBOX_ongoing.Enable(True) self._CHBOX_ongoing.SetValue(self.data['is_ongoing']) else: self._DPRW_end.SetData(data = self.data['clin_end']) self._CHBOX_ongoing.Enable(False) now = gmDateTime.pydt_now_here() if self.data['clin_end'] > now: self._CHBOX_ongoing.SetValue(True) else: self._CHBOX_ongoing.SetValue(False) self._PRW_episode.SetText(value = self.data['episode'], data = self.data['pk_episode']) self._PRW_procedure.SetText(value = self.data['performed_procedure'], data = self.data['performed_procedure']) if self.data['pk_hospital_stay'] is None: self._PRW_hospital_stay.SetText() self._LBL_hospital_details.SetLabel(u'') self._PRW_location.SetText(value = self.data['clin_where'], data = self.data['clin_where']) else: self._PRW_hospital_stay.SetText(value = self.data['clin_where'], data = self.data['pk_hospital_stay']) self._LBL_hospital_details.SetLabel(gmEMRStructItems.cHospitalStay(aPK_obj = self.data['pk_hospital_stay']).format()) self._PRW_location.SetText() self._PRW_procedure.SetFocus() #---------------------------------------------------------------- def _refresh_as_new_from_existing(self): self._refresh_as_new() self._PRW_episode.SetText(value = self.data['episode'], data = self.data['pk_episode']) if self.data['pk_hospital_stay'] is None: self._PRW_hospital_stay.SetText() self._PRW_location.SetText(value = self.data['clin_where'], data = self.data['clin_where']) else: self._PRW_hospital_stay.SetText(value = self.data['clin_where'], data = self.data['pk_hospital_stay']) self._PRW_location.SetText() self._PRW_procedure.SetFocus() #---------------------------------------------------------------- # event handlers #---------------------------------------------------------------- def _on_add_hospital_stay_button_pressed(self, evt): # FIXME: this would benefit from setting the created stay edit_hospital_stay(parent = self.GetParent()) evt.Skip() #---------------------------------------------------------------- def _on_ongoing_checkbox_checked(self, event): if self._CHBOX_ongoing.IsChecked(): end = self._DPRW_end.GetData() if end is None: self._DPRW_end.display_as_valid(True) else: end = end.get_pydt() now = gmDateTime.pydt_now_here() if end > now: self._DPRW_end.display_as_valid(True) else: self._DPRW_end.display_as_valid(False) else: self._DPRW_end.is_valid_timestamp() event.Skip() #================================================================ # hospital stay related widgets/functions #---------------------------------------------------------------- def manage_hospital_stays(parent=None): pat = gmPerson.gmCurrentPatient() emr = pat.get_emr() if parent is None: parent = wx.GetApp().GetTopWindow() #----------------------------------------- def edit(stay=None): return edit_hospital_stay(parent = parent, hospital_stay = stay) #----------------------------------------- def delete(stay=None): if gmEMRStructItems.delete_hospital_stay(stay = stay['pk_hospital_stay']): return True gmDispatcher.send ( signal = u'statustext', msg = _('Cannot delete hospital stay.'), beep = True ) return False #----------------------------------------- def refresh(lctrl): stays = emr.get_hospital_stays() items = [ [ s['admission'].strftime('%Y-%m-%d'), gmTools.coalesce(s['discharge'], u'', function_initial = ('strftime', '%Y-%m-%d')), s['episode'], gmTools.coalesce(s['hospital'], u'') ] for s in stays ] lctrl.set_string_items(items = items) lctrl.set_data(data = stays) #----------------------------------------- gmListWidgets.get_choices_from_list ( parent = parent, msg = _('\nSelect the hospital stay you want to edit !\n'), caption = _('Editing hospital stays ...'), columns = [_('Admission'), _('Discharge'), _('Reason'), _('Hospital')], single_selection = True, edit_callback = edit, new_callback = edit, delete_callback = delete, refresh_callback = refresh ) #---------------------------------------------------------------- def edit_hospital_stay(parent=None, hospital_stay=None): ea = cHospitalStayEditAreaPnl(parent = parent, id = -1) ea.data = hospital_stay ea.mode = gmTools.coalesce(hospital_stay, 'new', 'edit') dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = True) dlg.SetTitle(gmTools.coalesce(hospital_stay, _('Adding a hospital stay'), _('Editing a hospital stay'))) if dlg.ShowModal() == wx.ID_OK: dlg.Destroy() return True dlg.Destroy() return False #---------------------------------------------------------------- class cHospitalStayPhraseWheel(gmPhraseWheel.cPhraseWheel): """Phrasewheel to allow selection of a hospital stay. """ def __init__(self, *args, **kwargs): gmPhraseWheel.cPhraseWheel.__init__ (self, *args, **kwargs) ctxt = {'ctxt_pat': {'where_part': u'pk_patient = %(pat)s and', 'placeholder': u'pat'}} mp = gmMatchProvider.cMatchProvider_SQL2 ( queries = [ u""" select pk_hospital_stay, descr from ( select distinct on (pk_hospital_stay) pk_hospital_stay, descr from (select pk_hospital_stay, ( to_char(admission, 'YYYY-Mon-DD') || coalesce((' (' || hospital || '):'), ': ') || episode || coalesce((' (' || health_issue || ')'), '') ) as descr from clin.v_pat_hospital_stays where %(ctxt_pat)s hospital %(fragment_condition)s or episode %(fragment_condition)s or health_issue %(fragment_condition)s ) as the_stays ) as distinct_stays order by descr limit 25 """ ], context = ctxt ) mp.setThresholds(3, 4, 6) mp.set_context('pat', gmPerson.gmCurrentPatient().ID) self.matcher = mp self.selection_only = True #---------------------------------------------------------------- from Gnumed.wxGladeWidgets import wxgHospitalStayEditAreaPnl class cHospitalStayEditAreaPnl(wxgHospitalStayEditAreaPnl.wxgHospitalStayEditAreaPnl, gmEditArea.cGenericEditAreaMixin): def __init__(self, *args, **kwargs): wxgHospitalStayEditAreaPnl.wxgHospitalStayEditAreaPnl.__init__(self, *args, **kwargs) gmEditArea.cGenericEditAreaMixin.__init__(self) #---------------------------------------------------------------- # generic Edit Area mixin API #---------------------------------------------------------------- def _valid_for_save(self): valid = True if not self._PRW_admission.is_valid_timestamp(allow_empty = False): valid = False wxps.Publisher().sendMessage ( topic = 'statustext', data = {'msg': _('Missing admission data. Cannot save hospital stay.'), 'beep': True} ) if self._PRW_discharge.is_valid_timestamp(allow_empty = True): if self._PRW_discharge.date is not None: if not self._PRW_discharge.date > self._PRW_admission.date: valid = False self._PRW_discharge.display_as_valid(False) wxps.Publisher().sendMessage ( topic = 'statustext', data = {'msg': _('Discharge date must be empty or later than admission. Cannot save hospital stay.'), 'beep': True} ) if self._PRW_episode.GetValue().strip() == u'': valid = False self._PRW_episode.display_as_valid(False) wxps.Publisher().sendMessage ( topic = 'statustext', data = {'msg': _('Must select an episode or enter a name for a new one. Cannot save hospital stay.'), 'beep': True} ) return (valid is True) #---------------------------------------------------------------- def _save_as_new(self): pat = gmPerson.gmCurrentPatient() emr = pat.get_emr() stay = emr.add_hospital_stay(episode = self._PRW_episode.GetData(can_create = True)) stay['hospital'] = gmTools.none_if(self._PRW_hospital.GetValue().strip(), u'') stay['admission'] = self._PRW_admission.GetData() stay['discharge'] = self._PRW_discharge.GetData() stay.save_payload() self.data = stay return True #---------------------------------------------------------------- def _save_as_update(self): self.data['pk_episode'] = self._PRW_episode.GetData(can_create = True) self.data['hospital'] = gmTools.none_if(self._PRW_hospital.GetValue().strip(), u'') self.data['admission'] = self._PRW_admission.GetData() self.data['discharge'] = self._PRW_discharge.GetData() self.data.save_payload() return True #---------------------------------------------------------------- def _refresh_as_new(self): self._PRW_hospital.SetText(value = u'') self._PRW_episode.SetText(value = u'') self._PRW_admission.SetText(data = pydt.datetime.now()) self._PRW_discharge.SetText() #---------------------------------------------------------------- def _refresh_from_existing(self): if self.data['hospital'] is not None: self._PRW_hospital.SetText(value = self.data['hospital']) if self.data['pk_episode'] is not None: self._PRW_episode.SetText(value = self.data['episode'], data = self.data['pk_episode']) self._PRW_admission.SetText(data = self.data['admission']) self._PRW_discharge.SetText(data = self.data['discharge']) #---------------------------------------------------------------- def _refresh_as_new_from_existing(self): print "this was not expected to be used in this edit area" #================================================================ # encounter related widgets/functions #---------------------------------------------------------------- def start_new_encounter(emr=None): emr.start_new_encounter() gmDispatcher.send(signal = 'statustext', msg = _('Started a new encounter for the active patient.'), beep = True) time.sleep(0.5) gmGuiHelpers.gm_show_info ( _('\nA new encounter was started for the active patient.\n'), _('Start of new encounter') ) #---------------------------------------------------------------- from Gnumed.wxGladeWidgets import wxgEncounterEditAreaDlg def edit_encounter(parent=None, encounter=None): if parent is None: parent = wx.GetApp().GetTopWindow() # FIXME: use generic dialog 2 dlg = cEncounterEditAreaDlg(parent = parent, encounter = encounter) if dlg.ShowModal() == wx.ID_OK: dlg.Destroy() return True dlg.Destroy() return False #---------------------------------------------------------------- def select_encounters(parent=None, patient=None, single_selection=True, encounters=None, ignore_OK_button=False): if patient is None: patient = gmPerson.gmCurrentPatient() if not patient.connected: gmDispatcher.send(signal = 'statustext', msg = _('Cannot list encounters. No active patient.')) return False if parent is None: parent = wx.GetApp().GetTopWindow() emr = patient.get_emr() #-------------------- def refresh(lctrl): if encounters is None: encs = emr.get_encounters() else: encs = encounters items = [ [ e['started'].strftime('%x %H:%M'), e['last_affirmed'].strftime('%H:%M'), e['l10n_type'], gmTools.coalesce(e['reason_for_encounter'], u''), gmTools.coalesce(e['assessment_of_encounter'], u''), gmTools.bool2subst(e.has_clinical_data(), u'', gmTools.u_checkmark_thin), e['pk_encounter'] ] for e in encs ] lctrl.set_string_items(items = items) lctrl.set_data(data = encs) #-------------------- def new(): enc = gmEMRStructItems.create_encounter(fk_patient = patient.ID) return edit_encounter(parent = parent, encounter = enc) #-------------------- def edit(enc=None): return edit_encounter(parent = parent, encounter = enc) #-------------------- return gmListWidgets.get_choices_from_list ( parent = parent, msg = _('\nBelow find the relevant encounters of the patient.\n'), caption = _('Encounters ...'), columns = [_('Started'), _('Ended'), _('Type'), _('Reason for Encounter'), _('Assessment of Encounter'), _('Empty'), '#'], can_return_empty = True, single_selection = single_selection, refresh_callback = refresh, edit_callback = edit, new_callback = new, ignore_OK_button = ignore_OK_button ) #---------------------------------------------------------------- def ask_for_encounter_continuation(msg=None, caption=None, encounter=None, parent=None): """This is used as the callback when the EMR detects that the patient was here rather recently and wants to ask the provider whether to continue the recent encounter. """ if parent is None: parent = wx.GetApp().GetTopWindow() dlg = gmGuiHelpers.c2ButtonQuestionDlg ( parent = None, id = -1, caption = caption, question = msg, button_defs = [ {'label': _('Continue'), 'tooltip': _('Continue the existing recent encounter.'), 'default': False}, {'label': _('Start new'), 'tooltip': _('Start a new encounter. The existing one will be closed.'), 'default': True} ], show_checkbox = False ) result = dlg.ShowModal() dlg.Destroy() if result == wx.ID_YES: return True return False #---------------------------------------------------------------- def manage_encounter_types(parent=None): if parent is None: parent = wx.GetApp().GetTopWindow() #-------------------- def edit(enc_type=None): return edit_encounter_type(parent = parent, encounter_type = enc_type) #-------------------- def delete(enc_type=None): if gmEMRStructItems.delete_encounter_type(description = enc_type['description']): return True gmDispatcher.send ( signal = u'statustext', msg = _('Cannot delete encounter type [%s]. It is in use.') % enc_type['l10n_description'], beep = True ) return False #-------------------- def refresh(lctrl): enc_types = gmEMRStructItems.get_encounter_types() lctrl.set_string_items(items = enc_types) #-------------------- gmListWidgets.get_choices_from_list ( parent = parent, msg = _('\nSelect the encounter type you want to edit !\n'), caption = _('Managing encounter types ...'), columns = [_('Local name'), _('Encounter type')], single_selection = True, edit_callback = edit, new_callback = edit, delete_callback = delete, refresh_callback = refresh ) #---------------------------------------------------------------- def edit_encounter_type(parent=None, encounter_type=None): ea = cEncounterTypeEditAreaPnl(parent = parent, id = -1) ea.data = encounter_type ea.mode = gmTools.coalesce(encounter_type, 'new', 'edit') dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea) dlg.SetTitle(gmTools.coalesce(encounter_type, _('Adding new encounter type'), _('Editing local encounter type name'))) if dlg.ShowModal() == wx.ID_OK: return True return False #---------------------------------------------------------------- class cEncounterTypePhraseWheel(gmPhraseWheel.cPhraseWheel): """Phrasewheel to allow selection of encounter type. - user input interpreted as encounter type in English or local language - data returned is pk of corresponding encounter type or None """ def __init__(self, *args, **kwargs): gmPhraseWheel.cPhraseWheel.__init__ (self, *args, **kwargs) mp = gmMatchProvider.cMatchProvider_SQL2 ( queries = [ u""" select pk, l10n_description from ( select distinct on (pk) * from ( (select pk, _(description) as l10n_description, 1 as rank from clin.encounter_type where _(description) %(fragment_condition)s ) union all ( select pk, _(description) as l10n_description, 2 as rank from clin.encounter_type where description %(fragment_condition)s ) ) as q_distinct_pk ) as q_ordered order by rank, l10n_description """ ] ) mp.setThresholds(2, 4, 6) self.matcher = mp self.selection_only = True self.picklist_delay = 50 #---------------------------------------------------------------- class cEncounterTypeEditAreaPnl(wxgEncounterTypeEditAreaPnl.wxgEncounterTypeEditAreaPnl, gmEditArea.cGenericEditAreaMixin): def __init__(self, *args, **kwargs): wxgEncounterTypeEditAreaPnl.wxgEncounterTypeEditAreaPnl.__init__(self, *args, **kwargs) gmEditArea.cGenericEditAreaMixin.__init__(self) # self.__register_interests() #------------------------------------------------------- # generic edit area API #------------------------------------------------------- def _valid_for_save(self): if self.mode == 'edit': if self._TCTRL_l10n_name.GetValue().strip() == u'': self.display_tctrl_as_valid(tctrl = self._TCTRL_l10n_name, valid = False) return False self.display_tctrl_as_valid(tctrl = self._TCTRL_l10n_name, valid = True) return True no_errors = True if self._TCTRL_l10n_name.GetValue().strip() == u'': if self._TCTRL_name.GetValue().strip() == u'': self.display_tctrl_as_valid(tctrl = self._TCTRL_l10n_name, valid = False) no_errors = False else: self.display_tctrl_as_valid(tctrl = self._TCTRL_l10n_name, valid = True) else: self.display_tctrl_as_valid(tctrl = self._TCTRL_l10n_name, valid = True) if self._TCTRL_name.GetValue().strip() == u'': if self._TCTRL_l10n_name.GetValue().strip() == u'': self.display_tctrl_as_valid(tctrl = self._TCTRL_name, valid = False) no_errors = False else: self.display_tctrl_as_valid(tctrl = self._TCTRL_name, valid = True) else: self.display_tctrl_as_valid(tctrl = self._TCTRL_name, valid = True) return no_errors #------------------------------------------------------- def _save_as_new(self): enc_type = gmEMRStructItems.create_encounter_type ( description = gmTools.none_if(self._TCTRL_name.GetValue().strip(), u''), l10n_description = gmTools.coalesce ( gmTools.none_if(self._TCTRL_l10n_name.GetValue().strip(), u''), self._TCTRL_name.GetValue().strip() ) ) if enc_type is None: return False self.data = enc_type return True #------------------------------------------------------- def _save_as_update(self): enc_type = gmEMRStructItems.update_encounter_type ( description = self._TCTRL_name.GetValue().strip(), l10n_description = self._TCTRL_l10n_name.GetValue().strip() ) if enc_type is None: return False self.data = enc_type return True #------------------------------------------------------- def _refresh_as_new(self): self._TCTRL_l10n_name.SetValue(u'') self._TCTRL_name.SetValue(u'') self._TCTRL_name.Enable(True) #------------------------------------------------------- def _refresh_from_existing(self): self._TCTRL_l10n_name.SetValue(self.data['l10n_description']) self._TCTRL_name.SetValue(self.data['description']) # disallow changing type on all encounters by editing system name self._TCTRL_name.Enable(False) #------------------------------------------------------- def _refresh_as_new_from_existing(self): self._TCTRL_l10n_name.SetValue(self.data['l10n_description']) self._TCTRL_name.SetValue(self.data['description']) self._TCTRL_name.Enable(True) #------------------------------------------------------- # internal API #------------------------------------------------------- # def __register_interests(self): # return #---------------------------------------------------------------- from Gnumed.wxGladeWidgets import wxgEncounterEditAreaPnl class cEncounterEditAreaPnl(wxgEncounterEditAreaPnl.wxgEncounterEditAreaPnl): def __init__(self, *args, **kwargs): try: self.__encounter = kwargs['encounter'] del kwargs['encounter'] except KeyError: self.__encounter = None try: msg = kwargs['msg'] del kwargs['msg'] except KeyError: msg = None wxgEncounterEditAreaPnl.wxgEncounterEditAreaPnl.__init__(self, *args, **kwargs) self.refresh(msg = msg) #-------------------------------------------------------- # external API #-------------------------------------------------------- def refresh(self, encounter=None, msg=None): if msg is not None: self._LBL_instructions.SetLabel(msg) if encounter is not None: self.__encounter = encounter if self.__encounter is None: return True # getting the patient via the encounter allows us to act # on any encounter regardless of the currently active patient pat = gmPerson.cPatient(aPK_obj = self.__encounter['pk_patient']) self._LBL_patient.SetLabel(pat.get_description_gender()) self._PRW_encounter_type.SetText(self.__encounter['l10n_type'], data=self.__encounter['pk_type']) fts = gmDateTime.cFuzzyTimestamp ( timestamp = self.__encounter['started'], accuracy = gmDateTime.acc_minutes ) self._PRW_start.SetText(fts.format_accurately(), data=fts) fts = gmDateTime.cFuzzyTimestamp ( timestamp = self.__encounter['last_affirmed'], accuracy = gmDateTime.acc_minutes ) self._PRW_end.SetText(fts.format_accurately(), data=fts) self._TCTRL_rfe.SetValue(gmTools.coalesce(self.__encounter['reason_for_encounter'], '')) self._TCTRL_aoe.SetValue(gmTools.coalesce(self.__encounter['assessment_of_encounter'], '')) if self.__encounter['last_affirmed'] == self.__encounter['started']: self._PRW_end.SetFocus() else: self._TCTRL_aoe.SetFocus() return True #-------------------------------------------------------- def __is_valid_for_save(self): if self._PRW_encounter_type.GetData() is None: self._PRW_encounter_type.SetBackgroundColour('pink') self._PRW_encounter_type.Refresh() self._PRW_encounter_type.SetFocus() return False self._PRW_encounter_type.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) self._PRW_encounter_type.Refresh() if (not self._PRW_start.is_valid_timestamp()) or (self._PRW_start.GetValue().strip() == u''): self._PRW_start.SetFocus() return False if (not self._PRW_end.is_valid_timestamp()) or (self._PRW_end.GetValue().strip() == u''): self._PRW_end.SetFocus() return False return True #-------------------------------------------------------- def save(self): if not self.__is_valid_for_save(): return False self.__encounter['pk_type'] = self._PRW_encounter_type.GetData() self.__encounter['started'] = self._PRW_start.GetData().get_pydt() self.__encounter['last_affirmed'] = self._PRW_end.GetData().get_pydt() self.__encounter['reason_for_encounter'] = gmTools.none_if(self._TCTRL_rfe.GetValue().strip(), u'') self.__encounter['assessment_of_encounter'] = gmTools.none_if(self._TCTRL_aoe.GetValue().strip(), u'') self.__encounter.save_payload() # FIXME: error checking return True #---------------------------------------------------------------- # FIXME: use generic dialog 2 class cEncounterEditAreaDlg(wxgEncounterEditAreaDlg.wxgEncounterEditAreaDlg): def __init__(self, *args, **kwargs): encounter = kwargs['encounter'] del kwargs['encounter'] try: button_defs = kwargs['button_defs'] del kwargs['button_defs'] except KeyError: button_defs = None try: msg = kwargs['msg'] del kwargs['msg'] except KeyError: msg = None wxgEncounterEditAreaDlg.wxgEncounterEditAreaDlg.__init__(self, *args, **kwargs) self.SetSize((450, 280)) self.SetMinSize((450, 280)) if button_defs is not None: self._BTN_save.SetLabel(button_defs[0][0]) self._BTN_save.SetToolTipString(button_defs[0][1]) self._BTN_close.SetLabel(button_defs[1][0]) self._BTN_close.SetToolTipString(button_defs[1][1]) self.Refresh() self._PNL_edit_area.refresh(encounter = encounter, msg = msg) self.Fit() #-------------------------------------------------------- def _on_save_button_pressed(self, evt): if self._PNL_edit_area.save(): if self.IsModal(): self.EndModal(wx.ID_OK) else: self.Close() #================================================================ # episode related widgets/functions #---------------------------------------------------------------- def edit_episode(parent=None, episode=None): ea = cEpisodeEditAreaPnl(parent = parent, id = -1) ea.data = episode ea.mode = gmTools.coalesce(episode, 'new', 'edit') dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = True) dlg.SetTitle(gmTools.coalesce(episode, _('Adding a new episode'), _('Editing an episode'))) if dlg.ShowModal() == wx.ID_OK: return True return False #---------------------------------------------------------------- def promote_episode_to_issue(parent=None, episode=None, emr=None): created_new_issue = False try: issue = gmEMRStructItems.cHealthIssue(name = episode['description'], patient = episode['pk_patient']) except gmExceptions.NoSuchBusinessObjectError: issue = None if issue is None: issue = emr.add_health_issue(issue_name = episode['description']) created_new_issue = True else: # issue exists already, so ask user dlg = gmGuiHelpers.c3ButtonQuestionDlg ( parent, -1, caption = _('Promoting episode to health issue'), question = _( 'There already is a health issue\n' '\n' ' %s\n' '\n' 'What do you want to do ?' ) % issue['description'], button_defs = [ {'label': _('Use existing'), 'tooltip': _('Move episode into existing health issue'), 'default': False}, {'label': _('Create new'), 'tooltip': _('Create a new health issue with another name'), 'default': True} ] ) use_existing = dlg.ShowModal() dlg.Destroy() if use_existing == wx.ID_CANCEL: return # user wants to create new issue with alternate name if use_existing == wx.ID_NO: # loop until name modified but non-empty or cancelled issue_name = episode['description'] while issue_name == episode['description']: dlg = wx.TextEntryDialog ( parent = parent, message = _('Enter a short descriptive name for the new health issue:'), caption = _('Creating a new health issue ...'), defaultValue = issue_name, style = wx.OK | wx.CANCEL | wx.CENTRE ) decision = dlg.ShowModal() if decision != wx.ID_OK: dlg.Destroy() return issue_name = dlg.GetValue().strip() dlg.Destroy() if issue_name == u'': issue_name = episode['description'] issue = emr.add_health_issue(issue_name = issue_name) created_new_issue = True # eventually move the episode to the issue if not move_episode_to_issue(episode = episode, target_issue = issue, save_to_backend = True): # user cancelled the move so delete just-created issue if created_new_issue: # shouldn't fail as it is completely new gmEMRStructItems.delete_health_issue(health_issue = issue) return return #---------------------------------------------------------------- def move_episode_to_issue(episode=None, target_issue=None, save_to_backend=False): """Prepare changing health issue for an episode. Checks for two-open-episodes conflict. When this function succeeds, the pk_health_issue has been set on the episode instance and the episode should - for all practical purposes - be ready for save_payload(). """ # episode is closed: should always work if not episode['episode_open']: episode['pk_health_issue'] = target_issue['pk_health_issue'] if save_to_backend: episode.save_payload() return True # un-associate: should always work, too if target_issue is None: episode['pk_health_issue'] = None if save_to_backend: episode.save_payload() return True # try closing possibly expired episode on target issue if any db_cfg = gmCfg.cCfgSQL() epi_ttl = int(db_cfg.get2 ( option = u'episode.ttl', workplace = gmSurgery.gmCurrentPractice().active_workplace, bias = 'user', default = 60 # 2 months )) if target_issue.close_expired_episode(ttl=epi_ttl) is True: gmDispatcher.send(signal='statustext', msg=_('Closed episodes older than %s days on health issue [%s]') % (epi_ttl, target_issue['description'])) existing_epi = target_issue.get_open_episode() # no more open episode on target issue: should work now if existing_epi is None: episode['pk_health_issue'] = target_issue['pk_health_issue'] if save_to_backend: episode.save_payload() return True # don't conflict on SELF ;-) if existing_epi['pk_episode'] == episode['pk_episode']: episode['pk_health_issue'] = target_issue['pk_health_issue'] if save_to_backend: episode.save_payload() return True # we got two open episodes at once, ask user move_range = episode.get_access_range() exist_range = existing_epi.get_access_range() question = _( 'You want to associate the running episode:\n\n' ' "%(new_epi_name)s" (%(new_epi_start)s - %(new_epi_end)s)\n\n' 'with the health issue:\n\n' ' "%(issue_name)s"\n\n' 'There already is another episode running\n' 'for this health issue:\n\n' ' "%(old_epi_name)s" (%(old_epi_start)s - %(old_epi_end)s)\n\n' 'However, there can only be one running\n' 'episode per health issue.\n\n' 'Which episode do you want to close ?' ) % { 'new_epi_name': episode['description'], 'new_epi_start': move_range[0].strftime('%m/%y'), 'new_epi_end': move_range[1].strftime('%m/%y'), 'issue_name': target_issue['description'], 'old_epi_name': existing_epi['description'], 'old_epi_start': exist_range[0].strftime('%m/%y'), 'old_epi_end': exist_range[1].strftime('%m/%y') } dlg = gmGuiHelpers.c3ButtonQuestionDlg ( parent = None, id = -1, caption = _('Resolving two-running-episodes conflict'), question = question, button_defs = [ {'label': _('old episode'), 'default': True, 'tooltip': _('close existing episode "%s"') % existing_epi['description']}, {'label': _('new episode'), 'default': False, 'tooltip': _('close moving (new) episode "%s"') % episode['description']} ] ) decision = dlg.ShowModal() if decision == wx.ID_CANCEL: # button 3: move cancelled by user return False elif decision == wx.ID_YES: # button 1: close old episode existing_epi['episode_open'] = False existing_epi.save_payload() elif decision == wx.ID_NO: # button 2: close new episode episode['episode_open'] = False else: raise ValueError('invalid result from c3ButtonQuestionDlg: [%s]' % decision) episode['pk_health_issue'] = target_issue['pk_health_issue'] if save_to_backend: episode.save_payload() return True #---------------------------------------------------------------- class cEpisodeListSelectorDlg(gmListWidgets.cGenericListSelectorDlg): # FIXME: support pre-selection def __init__(self, *args, **kwargs): episodes = kwargs['episodes'] del kwargs['episodes'] gmListWidgets.cGenericListSelectorDlg.__init__(self, *args, **kwargs) self.SetTitle(_('Select the episodes you are interested in ...')) self._LCTRL_items.set_columns([_('Episode'), _('Status'), _('Health Issue')]) self._LCTRL_items.set_string_items ( items = [ [ epi['description'], gmTools.bool2str(epi['episode_open'], _('ongoing'), u''), gmTools.coalesce(epi['health_issue'], u'') ] for epi in episodes ] ) self._LCTRL_items.set_column_widths() self._LCTRL_items.set_data(data = episodes) #---------------------------------------------------------------- class cEpisodeDescriptionPhraseWheel(gmPhraseWheel.cPhraseWheel): """Let user select an episode *description*. The user can select an episode description from the previously used descriptions across all episodes across all patients. Selection is done with a phrasewheel so the user can type the episode name and matches will be shown. Typing "*" will show the entire list of episodes. If the user types a description not existing yet a new episode description will be returned. """ def __init__(self, *args, **kwargs): mp = gmMatchProvider.cMatchProvider_SQL2 ( queries = [u""" select distinct on (description) description, description, 1 from clin.episode where description %(fragment_condition)s order by description limit 30""" ] ) gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) self.matcher = mp #---------------------------------------------------------------- class cEpisodeSelectionPhraseWheel(gmPhraseWheel.cPhraseWheel): """Let user select an episode. The user can select an episode from the existing episodes of a patient. Selection is done with a phrasewheel so the user can type the episode name and matches will be shown. Typing "*" will show the entire list of episodes. Closed episodes will be marked as such. If the user types an episode name not in the list of existing episodes a new episode can be created from it if the programmer activated that feature. If keyword