gnumed-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Gnumed-devel] updateable emrBrowser


From: catmat
Subject: [Gnumed-devel] updateable emrBrowser
Date: Wed, 08 Dec 2004 20:31:21 +1100
User-agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.7.3) Gecko/20040913



I added this code to allow health issues and episodes to be added in when using an EMRBrowser to view the patient. (With episodes, the node label is put into a clin_narrative with is_aoe = 't' and soap_cat = 'a' , so it's implying that if a user adds an episode via the tree, he already has made an assessment of the episode from this encounter and that it is assessed as belonging to the health issue the episode node is created under).

It seems to work

attached are the files. It can be tried out by copying them over in the right directories, and later delete them
and re-update from the cvs .

I tested it out quite a lot ; there is a possibility of leaving a new episode node on the tree which the user didn't enter the aoe narrative, but it doesn't get written to the database. The bug is a bit random, I can get it sometimes by creating a new episode, and then clicking on the parent health issue label. Most of the time, the focus event handler on the edit text control for the tree will pickup unedited labels and remove the node.

Trying to understand why this bug occurs, it seems possible for the edit control to lose focus without firing a kill focus event, or that elsewhere there is code written (not mine) that will consume the kill focus event without calling event.Skip(). (IMHO, it's asking a bit much from library users to account for why this is occuring).

I was thinking of adding a wxTimer for the tree to periodically sweep the tree for default new node labels, and if it is on a node which is not the edit_node, then to queue it for removal using wxCallAfter.

I'd like to commit it, but the work belongs to Carlos and Karsten , so I'm seeing if they are happy with it.





Index: business/gmClinicalRecord.py
===================================================================
RCS file: /cvsroot/gnumed/gnumed/gnumed/client/business/gmClinicalRecord.py,v
retrieving revision 1.150
diff -r1.150 gmClinicalRecord.py
1364a1365
>
1373,1385c1374,1375
<               h_iss = issue_id
<               epis = episode_id
<               if issue_id is not None:
<                       h_iss = [issue_id]
<               if episode_id is not None:
<                       epis = [episode_id]
< encounters = self.get_encounters(issues=h_iss, episodes=epis)
<               if encounters is None or len(encounters) == 0:
< _log.Log(gmLog.lErr, 'cannot retrieve first encounter for episodes [%s], issues [%s] (patient ID [%s])' % (str(episodes), str(issues), self.id_patient))
<                       return None
<               # FIXME: this does not scale particularly well
< encounters.sort(lambda x,y: cmp(x['started'], y['started']))
<               return encounters[0]
---
>               return  self._get_encounters()[0]
>
1393a1384,1387
>               return self._get_encounters()[-1]
>
>       #--------------------------------------------------------
>       def _get_encounters(self, issue_id=None, episode_id=None):
1402,1403c1396,1399
< _log.Log(gmLog.lErr, 'cannot retrieve last encounter for episodes [%s], issues [%s]. Patient ID [%s]' %(str(episodes), str(issues), self.id_patient))
<                       return None
---
>                       episodes = epis
>                       issues = h_iss
> _log.Log(gmLog.lErr, 'cannot retrieve first encounter for episodes [%s], issues [%s] (patient ID [%s])' % (str(episodes), str(issues), self.id_patient))
>                       return [None]
1406c1402
<               return encounters[-1]
---
>               return encounters
Index: business/gmEMRStructItems.py
===================================================================
RCS file: /cvsroot/gnumed/gnumed/gnumed/client/business/gmEMRStructItems.py,v
retrieving revision 1.24
diff -r1.24 gmEMRStructItems.py
237a238,239
>               #print "found episode on select = ", episode
>
243,244c245,253
< cmd = "insert into clin_episode (fk_patient, fk_health_issue, description) values (%s, %s, %s)"
<       queries.append((cmd, [id_patient, pk_health_issue, episode_name]))
---
>
>       # meet constraint standalone episode
>       if not pk_health_issue is None: id_patient = None
>
> cmd = "insert into clin_episode (fk_patient, fk_health_issue) values (%s, %s)"
>       print cmd, id_patient, pk_health_issue
>
>       queries.append((cmd, [id_patient, pk_health_issue ]))
>
249a259,299
>               print "got msg", msg
>               return (False, msg)
>
>       pk_episode = result[0][0]
>
>
>       import gmPatient, gmClinNarrative
>       pat = gmPatient.gmCurrentPatient()
>       rec = pat.get_clinical_record()
>
>       pkEncounter = rec.get_active_encounter().pk_obj
>
> #this doesn't work and is hard to debug, because tracing doesn't go back to the sql statement and error > #narrative = gmClinNarrative.create_clin_narrative(str(episode_name), 'a', pk_episode, pkEncounter)
>       #narrative['is_aoe'] = True
>       #narrative.save_payload()
>
>       queries2=[]
>
>
>       cmd = """
> insert into clin_narrative(fk_encounter, fk_episode , narrative, soap_cat, is_aoe) values (
>       %s, %s, %s, %s, true)
>       """
> queries2.append((cmd, [pkEncounter,pk_episode, str(episode_name), 'a'])) > print "insert is ", cmd, [pkEncounter, pk_episode, str(episode_name), 'a']
>
>
> cmd = "update clin_episode set fk_clin_narrative = (select currval('clin_narrative_pk_seq')) where pk=%s"
>
>       queries2.append( (cmd, [ pk_episode]) )
>
>       #print "cmd is " , cmd, narrative['pk'], pk_episode
>
>       #queries2.append( (cmd, [ narrative.pk_obj , pk_episode] ) )
>
>
>
>       result, msg = gmPG.run_commit('historica', queries2, True)
>       if result is None:
>               print "msg=", msg
250a301
>
252c303
<               episode = cEpisode(aPK_obj = result[0][0])
---
>               episode = cEpisode(aPK_obj = pk_episode)
255a307,308
>
>
cvs diff: Diffing code_examples
cvs diff: Diffing connectors
cvs diff: Diffing data
cvs diff: Diffing data/config-definitions
cvs diff: Diffing data/drugObject-definitions
cvs diff: Diffing doc
cvs diff: Diffing doc/TODO
cvs diff: Diffing doc/TODO/spec
cvs diff: Diffing doc/developer-manual
cvs diff: Diffing doc/developer-manual/snapshots
cvs diff: Diffing doc/gnumed
cvs diff: Diffing doc/gnumed/stylesheet-images
cvs diff: Diffing doc/man-pages
cvs diff: Diffing doc/medical_knowledge
cvs diff: Diffing doc/medical_knowledge/de
cvs diff: Diffing doc/medical_knowledge/de/STIKO
cvs diff: Diffing doc/random-notes
cvs diff: Diffing doc/user-manual
cvs diff: Diffing etc
cvs diff: Diffing etc/config-definitions
cvs diff: Diffing etc/drugObject-definitions
cvs diff: Diffing exporters
Index: exporters/gmPatientExporter.py
===================================================================
RCS file: /cvsroot/gnumed/gnumed/gnumed/client/exporters/gmPatientExporter.py,v
retrieving revision 1.36
diff -r1.36 gmPatientExporter.py
526a527
>
cvs diff: Diffing importers
cvs diff: Diffing locale
cvs diff: Diffing locale/de
cvs diff: Diffing locale/de/LC_MESSAGES
cvs diff: Diffing manual
cvs diff: Diffing pycommon
cvs diff: Diffing pycommon/tools
cvs diff: Diffing python-addons
cvs diff: Diffing python-addons/linux
cvs diff: Diffing python-addons/windows
cvs diff: Diffing python-common
cvs diff: Diffing python-common/tools
cvs diff: Diffing python-tools
cvs diff: Diffing testing
cvs diff: Diffing wxpython
Index: wxpython/gmEMRBrowser.py
===================================================================
RCS file: /cvsroot/gnumed/gnumed/gnumed/client/wxpython/gmEMRBrowser.py,v
retrieving revision 1.6
diff -r1.6 gmEMRBrowser.py
48a49,50
>
>               self.__init_popup()
104c106
<
---
>
124a127,131
>
>
>               self.popup.SetPopupContext(sel_item)
>
>
181a189,213
>       def get_emr_tree(self):
>               return self.__emr_tree
>
>       def get_EMR_item(self, selected_tree_item):
>               return self.__emr_tree.GetPyData(selected_tree_item)
>
>       def get_parent_EMR_item(self, selected_tree_item):
> return self.__emr_tree.GetPyData(self.__emr_tree.GetItemParent(selected_tree_item))
>
>       def repopulate(self):
>               self._populate_with_data()
>
> #------------POPUP methods -------------------------------
>       def __init_popup(self):
>               """
>               initializes the popup for the tree
>               """
>               self.popup=gmPopupMenuEMRBrowser(self)
>               wx.EVT_RIGHT_DOWN(self.__emr_tree, self.__show_popup)
>
>
>
>       def __show_popup(self, event):
>                self.PopupMenu(self.popup, (event.GetX(), event.GetY() ))
>
216a249,269
>
>
> class gmPopupMenuEMRBrowser(wx.wxMenu):
>       """
>       popup menu for updating the EMR tree.
>       """
>       def __init__(self , browser):
>               wx.wxMenu.__init__(self)
>               self.ID_NEW_ENCOUNTER=1
>               self.ID_NEW_HEALTH_ISSUE=2
>               self.ID_NEW_EPISODE=3
>               self.__browser = browser
>               self.__mediator = NarrativeTreeItemMediator1(browser)
> wx.EVT_MENU(self.__browser, self.ID_NEW_HEALTH_ISSUE , self.__mediator.new_health_issue) > wx.EVT_MENU(self.__browser, self.ID_NEW_EPISODE , self.__mediator.new_episode)
>
>       def Clear(self):
>               for item in self.GetMenuItems():
>                       self.Remove(item.GetId())
>
>       def SetPopupContext( self, sel_item):
217a271,450
>               self.Clear()
>
>               sel_item_obj = self.__browser.get_EMR_item(sel_item)
>
>               if(isinstance(sel_item_obj, gmEMRStructItems.cEncounter)):
>                       header = _('Encounter\n=========\n\n')
>
> self.__append_new_encounter_menuitem(episode=self.__browser.get_parent_EMR_item(sel_item) )
>
>
> elif (isinstance(sel_item_obj, gmEMRStructItems.cEpisode)):
>                       header = _('Episode\n=======\n\n')
>
> self.__append_new_encounter_menuitem(episode=self.__browser.get_EMR_item(sel_item) )
>
> self.__append_new_episode_menuitem(health_issue=self.__browser.get_parent_EMR_item(sel_item))
>
>
> elif (isinstance(sel_item_obj, gmEMRStructItems.cHealthIssue)):
>                       header = _('Health Issue\n============\n\n')
>
> self.__append_new_episode_menuitem(health_issue=self.__browser.get_EMR_item(sel_item))
>
> self.Append(self.ID_NEW_HEALTH_ISSUE, "New Health Issue")
>
>
>               else:
>                       header = _('Summary\n=======\n\n')
> self.Append(self.ID_NEW_HEALTH_ISSUE, "New Health Issue")
>
>
>       def __append_new_encounter_menuitem(self, episode):
> self.Append(self.ID_NEW_ENCOUNTER, "New Encounter (of episode '%s')" % episode['description'] )
>
>       def __append_new_episode_menuitem(self, health_issue):
> self.Append(self.ID_NEW_EPISODE, "New Episode(of health issue '%s')" % health_issue['description'] )
>
>
>
> class NarrativeTreeItemMediator1:
>       """
>       handler for popup menu actions.
> Handles the unchanged new item problem , where no tree events are fired, by listening
>       on the edit control events.
>       """
>       def __init__(self, browser):
>
>               self.__browser = browser
> wx.EVT_TREE_END_LABEL_EDIT(self.get_emr_tree(), self.get_emr_tree().GetId(), self.__end_label_edit)
>
>               self.HEALTH_ISSUE_START_LABEL="NEW HEALTH ISSUE"
>               self.EPISODE_START_LABEL="NEW EPISODE"
>
>       def get_browser(self):
>               return self.__browser
>
>
>       def get_emr_tree(self):
>               return self.__browser.get_emr_tree()
>
>       def new_health_issue(self, menu_event):
>               """
>               entry from MenuItem New Health Issue
>               """
>               self.start_edit_root_node( self.HEALTH_ISSUE_START_LABEL)
>
>       def new_episode(self, menu_event):
>               """
>               entry from MenuItem New Episode Issue
>               """
>               self.start_edit_child_node( self.EPISODE_START_LABEL)
>
>       def start_edit_child_node(self, start_edit_text):
>               root_node = self.get_emr_tree().GetSelection()
>               if start_edit_text == self.EPISODE_START_LABEL and \
> isinstance(self.get_browser().get_EMR_item(root_node), gmEMRStructItems.cEpisode): > root_node = self.get_emr_tree().GetItemParent(root_node)
>
>               self.start_edit_node( root_node, start_edit_text)
>
>       def start_edit_root_node(self, start_edit_text ):
>               """
> this handles the problem of no event fired if label unchanged. > By detecting for the return key on the edit control, the start text
>               can be compared, and if no change, the node is deleted
>               in the __key_down handler
>               """
>               root_node = self.get_emr_tree().GetRootItem()
>               self.start_edit_node( root_node, start_edit_text)
>
>
>       def start_edit_node( self, root_node, start_edit_text):
> node= self.get_emr_tree().AppendItem(root_node, start_edit_text)
>               self.get_emr_tree().EnsureVisible(node)
>               self.get_emr_tree().EditLabel(node)
>               self.edit_control = self.get_emr_tree().GetEditControl()
>               print self.edit_control
>               wx.EVT_KEY_DOWN( self.edit_control,  self.__key_down)
>               wx.EVT_KILL_FOCUS(self.edit_control, self.__kill_focus)
>               self.start_edit_text = start_edit_text
>               self.edit_node = node
>
>       def __end_label_edit(self, tree_event):
>               """
> check to see if editing cancelled , and if not, then do update for each kind of label
>               """
>               print "end label edit Handled"
>               print "tree_event is ", tree_event
>               print "after ", tree_event.__dict__
>               print "label is ", tree_event.GetLabel()
>
>
> if tree_event.IsEditCancelled() or len(tree_event.GetLabel().strip()) == 0:
>                       tree_event.Skip()
>                       self.__item_to_delete = tree_event.GetItem()
>                       wx.wxCallAfter(self.__delete_item)
>               else:
> if self.start_edit_text == self.HEALTH_ISSUE_START_LABEL: > wx.wxCallAfter(self.__add_new_health_issue_to_record)
>
> elif self.start_edit_text == self.EPISODE_START_LABEL: > wx.wxCallAfter(self.__add_new_episode_to_record)
>
>       def __key_down(self, event):
>               """
>               this event on the EditControl needs to be handled because
> 1) pressing enter whilst no change in label , does not fire any END_LABEL event, so tentative
>               tree items cannot be deleted.
>
>                .
>               """
>               print "Item is ", event.GetKeyCode()
>               event.Skip()
>
>               if event.GetKeyCode() == wx.WXK_RETURN:
>                       self.__check_for_unchanged_item()
>
>
>
>       def __kill_focus(self, event):
>               print "kill focuse"
>
>               self.__check_for_unchanged_item()
>               event.Skip()
>
>       def __check_for_unchanged_item(self):
>                       text = self.edit_control.GetValue().strip()
>                       print "Text was ", text
> print "Text unchanged ", text == self.start_edit_text
>
> #if the text is unchanged, then delete the new node.
>                       if text == self.start_edit_text:
>                               self.__item_to_delete = self.edit_node
>                               wx.wxCallAfter(self.__delete_item)
>
>       def __delete_item(self):
>               self.get_emr_tree().Delete(self.__item_to_delete)
>
>       def __add_new_health_issue_to_record(self):
>               print "add new health issue to record"
>               pat = gmPatient.gmCurrentPatient()
>               rec = pat.get_clinical_record()
> issue = rec.add_health_issue( self.get_emr_tree().GetItemText(self.edit_node).strip() ) > if not issue is None and isinstance(issue, gmEMRStructItems.cHealthIssue): > self.get_emr_tree().SetPyData( self.edit_node, issue)
>
>
>
>       def __add_new_episode_to_record(self):
>               print "add new episode to record"
>               pat = gmPatient.gmCurrentPatient()
>               rec = pat.get_clinical_record()
> print "health_issue pk = ", self.get_browser().get_parent_EMR_item(self.edit_node).pk_obj > print "text = ", self.get_emr_tree().GetItemText(self.edit_node).strip()
>
> episode = rec.add_episode( self.get_emr_tree().GetItemText(self.edit_node).strip(), self.get_browser().get_parent_EMR_item(self.edit_node).pk_obj )
>
> if not episode is None and isinstance(episode, gmEMRStructItems.cEpisode): > self.get_emr_tree().SetPyData( self.edit_node, episode)
>
cvs diff: Diffing wxpython/bitmaps
cvs diff: Diffing wxpython/gui
cvs diff: Diffing wxpython/gui-de
cvs diff: Diffing wxpython/patient

"""GnuMed health related business object.

license: GPL
"""
#============================================================
__version__ = "$Revision: 1.24 $"
__author__ = "Carlos Moro <address@hidden>"

import types, sys

from Gnumed.pycommon import gmLog, gmPG, gmExceptions
from Gnumed.business import gmClinItem, gmClinNarrative
from Gnumed.pycommon.gmPyCompat import *

import mx.DateTime as mxDT

_log = gmLog.gmDefLog
_log.Log(gmLog.lInfo, __version__)
#============================================================
class cHealthIssue(gmClinItem.cClinItem):
        """Represents one health issue.
        """
        _cmd_fetch_payload = """select *, xmin from clin_health_issue where 
id=%s"""
        _cmds_lock_rows_for_update = [
                """select 1 from clin_health_issue where id=%(id)s and 
xmin=%(xmin)s for update"""
        ]
        _cmds_store_payload = [
                """update clin_health_issue set
                                description=%(description)s
                        where id=%(id)s"""
        ]
        _updatable_fields = [
                'description'
        ]
        #--------------------------------------------------------
        def __init__(self, aPK_obj=None, patient_id=None, name='xxxDEFAULTxxx'):
                pk = aPK_obj
                if pk is None:
                        cmd = "select id from clin_health_issue where 
id_patient=%s and description=%s"
                        rows = gmPG.run_ro_query('historica', cmd, None, 
patient_id, name)
                        if rows is None:
                                raise gmExceptions.ConstructorError, 'error 
getting health issue for [%s:%s]' % (patient_id, name)
                        if len(rows) == 0:
                                raise gmExceptions.NoSuchClinItemError, 'no 
health issue for [%s:%s]' % (patient_id, name)
                        pk = rows[0][0]
                # instantiate class
                gmClinItem.cClinItem.__init__(self, aPK_obj=pk)
        #--------------------------------------------------------
        def get_patient(self):
                return self._payload[self._idx['id_patient']]
#============================================================
class cEpisode(gmClinItem.cClinItem):
        """Represents one clinical episode.
        """
        _cmd_fetch_payload = """select *, xmin_clin_episode from v_pat_episodes 
where pk_episode=%s"""
        _cmds_lock_rows_for_update = [
                """select 1 from clin_episode where pk=%(pk)s and 
xmin=%(xmin_clin_episode)s for update"""
        ]
        _cmds_store_payload = [
                """update clin_episode set
                                description=%(description)s,
                                fk_health_issue=%(pk_health_issue)s,
                                fk_patient=%(pk_patient)s
                        where pk=%(pk)s"""
        ]
        _updatable_fields = [
                'description',
                'pk_health_issue',
                'fk_patient'
        ]
        #--------------------------------------------------------
        def __init__(self, aPK_obj=None, id_patient=None, name='xxxDEFAULTxxx'):
                pk = aPK_obj
                if pk is None:
                        cmd = "select pk_episode from v_pat_episodes where 
id_patient=%s and description=%s limit 1"
                        rows = gmPG.run_ro_query('historica', cmd, None, 
id_patient, name)
                        if rows is None:
                                raise gmExceptions.ConstructorError, 'error 
getting episode for [%s:%s]' % (id_patient, name)
                        if len(rows) == 0:
                                raise gmExceptions.NoSuchClinItemError, 'no 
episode for [%s:%s]' % (id_patient, name)
                        pk = rows[0][0]
                # instantiate class
                gmClinItem.cClinItem.__init__(self, aPK_obj=pk)
        #--------------------------------------------------------
        def get_patient(self):
                return self._payload[self._idx['id_patient']]
        #--------------------------------------------------------
        def set_active(self):
                cmd1 = """
                        delete from last_act_episode
                        where id_patient=(select id_patient from 
clin_health_issue where id=%s)"""
                cmd2 = """
                        insert into last_act_episode(fk_episode, id_patient)
                        values (%s,     (select id_patient from 
clin_health_issue where id=%s))"""
                success, msg = gmPG.run_commit('historica', [
                        (cmd1, [self._payload[self._idx['pk_health_issue']]]),
                        (cmd2, [self.pk_obj, 
self._payload[self._idx['pk_health_issue']]])
                ], True)
                if not success:
                        _log.Log(gmLog.lErr, 'cannot record episode [%s] as 
most recently used one for health issue [%s]' % (self.pk_obj, 
self._payload[self._idx['pk_health_issue']]))
                        _log.Log(gmLog.lErr, str(msg))
                        return False
                return True
#============================================================
class cEncounter(gmClinItem.cClinItem):
        """Represents one encounter.
        """
        _cmd_fetch_payload = """
                select *, xmin_clin_encounter from v_pat_encounters
                where pk_encounter=%s"""
        _cmds_lock_rows_for_update = [
                """select 1 from clin_encounter where id=%(pk_encounter)s and 
xmin=%(xmin_clin_encounter)s for update"""
        ]
        _cmds_store_payload = [
                """update clin_encounter set
                                description=%(description)s,
                                started=%(started)s,
                                last_affirmed=%(last_affirmed)s,
                                pk_location=%(pk_location)s,
                                pk_provider=%(pk_provider)s,
                                pk_type=%(pk_type)s
                        where id=%(pk_encounter)s"""
        ]
        _updatable_fields = [
                'description',
                'started',
                'last_affirmed',
                'pk_location',
                'pk_provider',
                'pk_type'
        ]
        #--------------------------------------------------------
        def set_active(self, staff_id=None):
                """Set the enconter as the active one.

                "Setting active" means making sure the encounter
                row has the youngest "last_affirmed" timestamp of
                all encounter rows for this patient.

                staff_id - Provider's primary key
                """
                cmd = """update clin_encounter set
                                        fk_provider=%s,
                                        last_affirmed=now()
                                where id=%s"""
                success, msg = gmPG.run_commit('historica', [(cmd, [staff_id, 
self.pk_obj])], True)
                if not success:
                        _log.Log(gmLog.lErr, 'cannot reaffirm encounter [%s]' % 
self.pk_obj)
                        _log.Log(gmLog.lErr, str(msg))
                        return False
                return True
        #--------------------------------------------------------
        def get_rfes(self):
                """
            Get RFEs pertinent to this encounter.
                """
                vals = {'enc': self.pk_obj}
                cmd = """select pk_narrative from v_pat_rfe where 
pk_encounter=%(enc)s"""
                rows = gmPG.run_ro_query('historica', cmd, None, vals)
                if rows is None:
                        _log.Log(gmLog.lErr, 'cannot get RFEs for encounter 
[%s]' % (self.pk_obj))
                        return None
                rfes = []
                for row in rows:
                    rfes.append(gmClinNarrative.cRFE(aPK_obj=row[0]))
                return rfes
        #--------------------------------------------------------
        def get_aoes(self):
                """
            Get AOEs pertinent to this encounter.
                """
                vals = {'enc': self.pk_obj}
                cmd = """select pk_narrative from v_pat_aoe where 
pk_encounter=%(enc)s"""
                rows = gmPG.run_ro_query('historica', cmd, None, vals)
                if rows is None:
                        _log.Log(gmLog.lErr, 'cannot get AOEs for encounter 
[%s]' % (self.pk_obj))
                        return None
                aoes = []
                for row in rows:
                  aoes.append(gmClinNarrative.cAOE(aPK_obj=row[0]))
                return aoes
        #--------------------------------------------------------
        def set_attached_to(self, staff_id=None):
#               """Attach staff/provider to the encounter.
#
#               staff_id - Staff/provider's primary key
#               """
#               cmd = """update clin_encounter set fk_provider=%s where id=%s"""
#               success, msg = gmPG.run_commit('historica', [(cmd, [staff_id, 
self.pk_obj])], True)
#               if not success:
#                       _log.Log(gmLog.lErr, 'cannot attach to encounter [%s]' 
% self.pk_obj)
#                       _log.Log(gmLog.lErr, str(msg))
#                       return False
#               return True
                pass
#============================================================
# convenience functions
#------------------------------------------------------------   
def create_health_issue(patient_id=None, description=None):
        """Creates a new health issue for a given patient.

        patient_id - given patient PK
        description - health issue name
        """
        # already there ?
        try:
                h_issue = cHealthIssue(patient_id=patient_id, name=description)
                return (True, h_issue)
        except gmExceptions.ConstructorError, msg:
                _log.LogException(str(msg), sys.exc_info(), verbose=0)
        # insert new health issue
        queries = []
        cmd = "insert into clin_health_issue (id_patient, description) values 
(%s, %s)"
        queries.append((cmd, [patient_id, description]))
        # get PK of inserted row
        cmd = "select currval('clin_health_issue_id_seq')"
        queries.append((cmd, []))
        result, msg = gmPG.run_commit('historica', queries, True)
        if result is None:
                return (False, msg)
        try:
                h_issue = cHealthIssue(aPK_obj = result[0][0])
        except gmExceptions.ConstructorError:
                _log.LogException('cannot instantiate health issue [%s]' % 
(result[0][0]), sys.exc_info, verbose=0)
                return (False, _('internal error, check log'))
        return (True, h_issue)
#-----------------------------------------------------------
def create_episode(id_patient = None, pk_health_issue = None, 
episode_name='xxxDEFAULTxxx'):
        """Creates a new episode for a given patient's health issue.

    id_patient - patient PK
        pk_health_issue - given health issue PK
        episode_name - health issue name
        """
        # already there ?
        try:
                episode = cEpisode(id_patient=id_patient, name=episode_name)
                #print "found episode on select = ", episode
                
                return (True, episode)
        except gmExceptions.ConstructorError, msg:
                _log.LogException(str(msg), sys.exc_info(), verbose=0)
        # insert new episode
        queries = []

        # meet constraint standalone episode
        if not pk_health_issue is None: id_patient = None
        
        cmd = "insert into clin_episode (fk_patient, fk_health_issue) values 
(%s, %s)"
        print cmd, id_patient, pk_health_issue
        
        queries.append((cmd, [id_patient, pk_health_issue ]))
        
        # get PK of inserted row
        cmd = "select currval('clin_episode_pk_seq')"
        queries.append((cmd, []))
        result, msg = gmPG.run_commit('historica', queries, True)
        if result is None:
                print "got msg", msg
                return (False, msg)
        
        pk_episode = result[0][0]
        
        
        import gmPatient, gmClinNarrative
        pat = gmPatient.gmCurrentPatient()
        rec = pat.get_clinical_record()
        
        pkEncounter = rec.get_active_encounter().pk_obj
        
        #this doesn't work and is hard to debug, because tracing doesn't go 
back to the sql statement and error
        #narrative = gmClinNarrative.create_clin_narrative(str(episode_name), 
'a', pk_episode, pkEncounter)
        #narrative['is_aoe'] = True
        #narrative.save_payload()
        
        queries2=[] 
        
        
        cmd = """
        insert into clin_narrative(fk_encounter, fk_episode , narrative, 
soap_cat, is_aoe) values (
        %s, %s, %s, %s, true)
        """
        queries2.append((cmd, [pkEncounter,pk_episode, str(episode_name), 'a']))
        print "insert is ", cmd, [pkEncounter, pk_episode, str(episode_name), 
'a']
        
        
        cmd = "update clin_episode set fk_clin_narrative = (select 
currval('clin_narrative_pk_seq')) where pk=%s"
        
        queries2.append( (cmd, [ pk_episode]) )
        
        #print "cmd is " , cmd, narrative['pk'], pk_episode
        
        #queries2.append( (cmd, [ narrative.pk_obj , pk_episode] ) )
        
        
                
        result, msg = gmPG.run_commit('historica', queries2, True)
        if result is None:
                print "msg=", msg
                return (False, msg)
                
        try:
                episode = cEpisode(aPK_obj = pk_episode)
        except gmExceptions.ConstructorError:
                _log.LogException('cannot instantiate episode [%s]' % 
(result[0][0]), sys.exc_info, verbose=0)
                return (False, _('internal error, check log'))
                        
                
        return (True, episode)
#-----------------------------------------------------------
def create_encounter(fk_patient=None, fk_location=-1, fk_provider=None, 
description=None, enc_type=None):
        """Creates a new encounter for a patient.

        fk_patient - patient PK
        fk_location - encounter location
        fk_provider - who was the patient seen by
        description - name or description for the encounter
        enc_type - type of encounter

        FIXME: we don't deal with location yet
        """
        # sanity check:
        if description is None:
                description = 'auto-created %s' % mxDT.today().Format('%A 
%Y-%m-%d %H:%M')
        # FIXME: look for MRU/MCU encounter type here
        if enc_type is None:
                enc_type = 'chart review'
        # insert new encounter
        queries = []
        try:
                enc_type = int(enc_type)
                cmd = """
                        insert into clin_encounter (
                                fk_patient, fk_location, fk_provider, 
description, fk_type
                        ) values (
                                %s, -1, %s, %s, %s
                        )"""
        except ValueError:
                enc_type = str(enc_type)
                cmd = """
                        insert into clin_encounter (
                                fk_patient, fk_location, fk_provider, 
description, fk_type
                        ) values (
                                %s, -1, %s, %s, coalesce((select pk from 
encounter_type where description=%s), 0)
                        )"""
        queries.append((cmd, [fk_patient, fk_provider, description, enc_type]))
        cmd = "select currval('clin_encounter_id_seq')"
        queries.append((cmd, []))
        result, msg = gmPG.run_commit('historica', queries, True)
        if result is None:
                return (False, msg)
        try:
                encounter = cEncounter(aPK_obj = result[0][0])
        except gmExceptions.ConstructorError:
                _log.LogException('cannot instantiate encounter [%s]' % 
(result[0][0]), sys.exc_info, verbose=0)
                return (False, _('internal error, check log'))
        return (True, encounter)
#============================================================
# main - unit testing
#------------------------------------------------------------
if __name__ == '__main__':
        import sys
        _log = gmLog.gmDefLog
        _log.SetAllLogLevels(gmLog.lData)
        from Gnumed.pycommon import gmPG
        gmPG.set_default_client_encoding('latin1')

        print "\nhealth issue test"
        print "-----------------"
        h_issue = cHealthIssue(aPK_obj=1)
        print h_issue
        fields = h_issue.get_fields()
        for field in fields:
                print field, ':', h_issue[field]
        print "updatable:", h_issue.get_updatable_fields()
        

        print "\nepisode test"
        print "------------"
        episode = cEpisode(aPK_obj=1)
        print episode
        fields = episode.get_fields()
        for field in fields:
                print field, ':', episode[field]
        print "updatable:", episode.get_updatable_fields()

        print "\nencounter test"
        print "--------------"
        encounter = cEncounter(aPK_obj=1)
        print encounter
        fields = encounter.get_fields()
        for field in fields:
                print field, ':', encounter[field]
        print "updatable:", encounter.get_updatable_fields()
        rfes = encounter.get_rfes()
        print "rfes: "
        for rfe in rfes:
            print "\n   rfe test"
            print "   --------"
            for field in rfe.get_fields():
                print '  ', field, ':', rfe[field]
            print "   updatable:", rfe.get_updatable_fields()
            
        aoes = encounter.get_aoes()
        for aoe in aoes:
            print "\n   aoe test"
            print "   --------"
            for field in aoe.get_fields():
                print '  ', field, ':', aoe[field]
            print "   updatable:", aoe.get_updatable_fields()
            print "   is diagnosis: " , aoe.is_diagnosis()
            if aoe.is_diagnosis():
                print "   diagnosis: ", aoe.get_diagnosis()
            
#============================================================
# $Log: gmEMRStructItems.py,v $
# Revision 1.24  2004/11/03 22:32:34  ncq
# - support _cmds_lock_rows_for_update in business object base class
#
# Revision 1.23  2004/09/19 15:02:29  ncq
# - episode: id -> pk, support fk_patient
# - no default name in create_health_issue
#
# Revision 1.22  2004/07/05 10:24:46  ncq
# - use v_pat_rfe/aoe, by Carlos
#
# Revision 1.21  2004/07/04 15:09:40  ncq
# - when refactoring need to fix imports, too
#
# Revision 1.20  2004/07/04 13:24:31  ncq
# - add cRFE/cAOE
# - use in get_rfes(), get_aoes()
#
# Revision 1.19  2004/06/30 20:34:37  ncq
# - cEncounter.get_RFEs()
#
# Revision 1.18  2004/06/26 23:45:50  ncq
# - cleanup, id_* -> fk/pk_*
#
# Revision 1.17  2004/06/26 07:33:55  ncq
# - id_episode -> fk/pk_episode
#
# Revision 1.16  2004/06/08 00:44:41  ncq
# - v_pat_episodes now has description, not episode for name of episode
#
# Revision 1.15  2004/06/02 22:12:48  ncq
# - cleanup
#
# Revision 1.14  2004/06/02 13:45:19  sjtan
#
# episode->description for update statement as well.
#
# Revision 1.13  2004/06/02 13:18:48  sjtan
#
# revert, as backend view definition was changed yesterday to be more 
consistent.
#
# Revision 1.12  2004/06/02 12:48:56  sjtan
#
# map episode to description in cursor.description, so can find as 
episode['description']
# and also save.
#
# Revision 1.11  2004/06/01 23:53:56  ncq
# - v_pat_episodes.episode -> *.description
#
# Revision 1.10  2004/06/01 08:20:14  ncq
# - limit in get_lab_results
#
# Revision 1.9  2004/05/30 20:10:31  ncq
# - cleanup
#
# Revision 1.8  2004/05/22 12:42:54  ncq
# - add create_episode()
# - cleanup add_episode()
#
# Revision 1.7  2004/05/18 22:36:52  ncq
# - need mx.DateTime
# - fix fields updatable in episode
# - fix delete action in episode.set_active()
#
# Revision 1.6  2004/05/18 20:35:42  ncq
# - cleanup
#
# Revision 1.5  2004/05/17 19:02:26  ncq
# - encounter.set_active()
# - improve create_encounter()
#
# Revision 1.4  2004/05/16 15:47:51  ncq
# - add episode.set_active()
#
# Revision 1.3  2004/05/16 14:31:27  ncq
# - cleanup
# - allow health issue to be instantiated by name/patient
# - create_health_issue()/create_encounter
# - based on Carlos' work
#
# Revision 1.2  2004/05/12 14:28:53  ncq
# - allow dict style pk definition in __init__ for multicolum primary keys 
(think views)
# - self.pk -> self.pk_obj
# - __init__(aPKey) -> __init__(aPK_obj)
#
# Revision 1.1  2004/04/17 12:18:50  ncq
# - health issue, episode, encounter classes
#

"""GnuMed preliminary clinical patient record.

This is a clinical record object intended to let a useful
client-side API crystallize from actual use in true XP fashion.

Make sure to call set_func_ask_user() and set_encounter_ttl()
early on in your code (before cClinicalRecord.__init__() is
called for the first time).
"""
#============================================================
# $Source: /cvsroot/gnumed/gnumed/gnumed/client/business/gmClinicalRecord.py,v $
# $Id: gmClinicalRecord.py,v 1.150 2004/10/27 12:09:28 ncq Exp $
__version__ = "$Revision: 1.150 $"
__author__ = "K.Hilbert <address@hidden>"
__license__ = "GPL"

# standard libs
import sys, string, time, copy

# 3rd party
import mx.DateTime as mxDT

from Gnumed.pycommon import gmLog, gmExceptions, gmPG, gmSignals, gmDispatcher, 
gmWhoAmI, gmI18N
from Gnumed.business import gmPathLab, gmAllergy, gmVaccination, 
gmEMRStructItems, gmClinNarrative
from Gnumed.pycommon.gmPyCompat import *

_log = gmLog.gmDefLog
_log.Log(gmLog.lData, __version__)
_whoami = gmWhoAmI.cWhoAmI()

# in AU the soft timeout better be 4 hours as of 2004
_encounter_soft_ttl = mxDT.TimeDelta(hours=4)
_encounter_hard_ttl = mxDT.TimeDelta(hours=6)

_func_ask_user = None
#============================================================
class cClinicalRecord:

        # handlers for __getitem__()
        _get_handler = {}

        def __init__(self, aPKey = None):
                """Fails if

                - no connection to database possible
                - patient referenced by aPKey does not exist
                """
                self._conn_pool = gmPG.ConnectionPool()

                self.id_patient = aPKey                 # == identity.id == 
primary key
                if not self.__patient_exists():
                        raise gmExceptions.ConstructorError, "No patient with 
ID [%s] in database." % aPKey

                if not self.__provider_exists():
                        raise gmExceptions.ConstructorError, "cannot make sure 
provider [%s] is in service 'historica'" % _whoami.get_staff_ID()

                self.__db_cache = {
                        'vaccinations': {}
                }

                self.__health_issue = None

                # what episode did we work on last time we saw this patient ?
                # also load corresponding health issue
                t1 = time.time()
                if not self.__load_last_active_episode():
                        raise gmExceptions.ConstructorError, "cannot activate 
an episode for patient [%s]" % aPKey
                duration = time.time() - t1
                _log.Log(gmLog.lData, '__load_last_active_episode() took %s 
seconds' % duration)

                # load current or create new encounter
                # FIXME: this should be configurable (for explanation see the 
method source)
                t1 = time.time()
                if not self.__initiate_active_encounter():
                        raise gmExceptions.ConstructorError, "cannot activate 
an encounter for patient [%s]" % aPKey
                duration = time.time() - t1
                _log.Log(gmLog.lData, '__initiate_active_encounter() took %s 
seconds' % duration)

                # register backend notification interests
                # (keep this last so we won't hang on threads when
                #  failing this constructor for other reasons ...)
                t1 = time.time()
                if not self._register_interests():
                        raise gmExceptions.ConstructorError, "cannot register 
signal interests"
                duration = time.time() - t1
                _log.Log(gmLog.lData, '_register_interests() took %s seconds' % 
duration)

                _log.Log(gmLog.lData, 'Instantiated clinical record for patient 
[%s].' % self.id_patient)
        #--------------------------------------------------------
        def __del__(self):
                pass
        #--------------------------------------------------------
        def cleanup(self):
                self.__episode.set_active()
                _log.Log(gmLog.lData, 'cleaning up after clinical record for 
patient [%s]' % self.id_patient)
                sig = "%s:%s" % (gmSignals.health_issue_change_db(), 
self.id_patient)
                self._conn_pool.Unlisten(service = 'historica', signal = sig, 
callback = self._health_issues_modified)
                sig = "%s:%s" % (gmSignals.vacc_mod_db(), self.id_patient)
                self._conn_pool.Unlisten(service = 'historica', signal = sig, 
callback = self.db_callback_vaccs_modified)
                sig = "%s:%s" % (gmSignals.allg_mod_db(), self.id_patient)
                self._conn_pool.Unlisten(service = 'historica', signal = sig, 
callback = self._db_callback_allg_modified)
        #--------------------------------------------------------
        # internal helpers
        #--------------------------------------------------------
        def __patient_exists(self):
                """Does this patient exist ?

                - true/false
                """
                # patient in demographic database ?
                cmd = "select exists(select id from identity where id = %s)"
                result = gmPG.run_ro_query('personalia', cmd, None, 
self.id_patient)
                if result is None:
                        _log.Log(gmLog.lErr, 'unable to check for patient [%s] 
existence in demographic database' % self.id_patient)
                        return False
                exists = result[0][0]
                if not exists:
                        _log.Log(gmLog.lErr, "patient [%s] not in demographic 
database" % self.id_patient)
                        return False
                # patient linked in our local clinical database ?
                cmd = "select exists(select pk from xlnk_identity where 
xfk_identity = %s)"
                result = gmPG.run_ro_query('historica', cmd, None, 
self.id_patient)
                if result is None:
                        _log.Log(gmLog.lErr, 'unable to check for patient [%s] 
existence in clinical database' % self.id_patient)
                        return False
                exists = result[0][0]
                if not exists:
                        _log.Log(gmLog.lInfo, "patient [%s] not in clinical 
database" % self.id_patient)
                        cmd1 = "insert into xlnk_identity (xfk_identity, pupic) 
values (%s, %s)"
                        cmd2 = "select currval('xlnk_identity_pk_seq')"
                        status = gmPG.run_commit('historica', [
                                (cmd1, [self.id_patient, self.id_patient]),
                                (cmd2, [])
                        ])
                        if status is None:
                                _log.Log(gmLog.lErr, 'cannot insert patient 
[%s] into clinical database' % self.id_patient)
                                return False
                        if status != 1:
                                _log.Log(gmLog.lData, 'inserted patient [%s] 
into clinical database with local id [%s]' % (self.id_patient, status[0][0]))
                return True
        #--------------------------------------------------------
        def __provider_exists(self):
                """Make sure provider is linked in clinical database.
                """
                pk_provider = _whoami.get_staff_ID()
                # provider linked in our local clinical database ?
                cmd = "select exists(select pk from xlnk_identity where 
xfk_identity = %s)"
                exists = gmPG.run_ro_query('historica', cmd, None, pk_provider)
                if exists is None:
                        _log.Log(gmLog.lErr, 'unable to check for provider [%s] 
existence in clinical database' % pk_provider)
                        return False
                if not exists[0][0]:
                        _log.Log(gmLog.lInfo, "provider [%s] not in clinical 
database" % pk_provider)
                        cmd1 = "insert into xlnk_identity (xfk_identity, pupic) 
values (%s, %s)"
                        cmd2 = "select currval('xlnk_identity_pk_seq')"
                        status = gmPG.run_commit('historica', [
                                (cmd1, [pk_provider, pk_provider]),
                                (cmd2, [])
                        ])
                        if status is None:
                                _log.Log(gmLog.lErr, 'cannot insert provider 
[%s] into clinical database' % pk_provider)
                                return False
                        if status != 1:
                                _log.Log(gmLog.lData, 'inserted provider [%s] 
into clinical database with local id [%s]' % (pk_provider, status[0][0]))
                return True
        #--------------------------------------------------------
        # messaging
        #--------------------------------------------------------
        def _register_interests(self):
                # backend notifications
                sig = "%s:%s" % (gmSignals.vacc_mod_db(), self.id_patient)
                if not self._conn_pool.Listen('historica', sig, 
self.db_callback_vaccs_modified):
                        return None
                sig = "%s:%s" % (gmSignals.allg_mod_db(), self.id_patient)
                if not self._conn_pool.Listen(service = 'historica', signal = 
sig, callback = self._db_callback_allg_modified):
                        return None
                sig = "%s:%s" % (gmSignals.health_issue_change_db(), 
self.id_patient)
                if not self._conn_pool.Listen(service = 'historica', signal = 
sig, callback = self._health_issues_modified):
                        return None
                return 1
        #--------------------------------------------------------
        def db_callback_vaccs_modified(self, **kwds):
                try:
                        self.__db_cache['vaccinations'] = {}
                except KeyError:
                        pass
                gmDispatcher.send(signal = gmSignals.vaccinations_updated(), 
sender = self.__class__.__name__)
                return True
        #--------------------------------------------------------
        def _db_callback_allg_modified(self):
                try:
                        del self.__db_cache['allergies']
                except KeyError:
                        pass
                gmDispatcher.send(signal = gmSignals.allergy_updated(), sender 
= self.__class__.__name__)
                return 1
        #--------------------------------------------------------
        def _health_issues_modified(self):
                try:
                        del self.__db_cache['health issues']
                except KeyError:
                        pass
                gmDispatcher.send(signal = gmSignals.health_issue_updated(), 
sender = self.__class__.__name__)
                return 1
        #--------------------------------------------------------
        def _clin_item_modified(self):
                _log.Log(gmLog.lData, 'DB: clin_root_item modification')
        #--------------------------------------------------------
        # Narrative API
        #--------------------------------------------------------
        def add_clin_narrative(self, note = '', soap_cat='s'):
                if note.strip() == '':
                        _log.Log(gmLog.lInfo, 'will not create empty clinical 
note')
                        return None
                status, data = gmClinNarrative.create_clin_narrative (
                        narrative=note,
                        soap_cat=soap_cat,
                        episode_id=self.__episode['pk_episode'],
                        encounter_id=self.__encounter['pk_encounter']
                )
                if not status:
                        _log.Log(gmLog.lErr, str(data))
                        return None
                return data
        #--------------------------------------------------------
        def get_clin_narrative(self, since=None, until=None, encounters=None,
                episodes=None, issues=None, soap_cats=None, 
exclude_rfe_aoe=False):
                """Get SOAP notes pertinent to this encounter.

                        since
                                - initial date for narrative items
                        until
                                - final date for narrative items
                        encounters
                                - list of encounters whose narrative are to be 
retrieved
                        episodes
                                - list of episodes whose narrative are to be 
retrieved
                        issues
                                - list of health issues whose narrative are to 
be retrieved
                        soap_cats
                                - list of SOAP categories of the narrative to 
be retrived
                        exclude_rfe_aoe
                                -  when True, filter out RFE and AOE narrative
                """
                try:
                        self.__db_cache['narrative']
                except KeyError:
                        self.__db_cache['narrative'] = []
                        cmd = "select * from v_pat_narrative where 
pk_patient=%s order by date"
                        rows, idx = gmPG.run_ro_query('historica', cmd, True, 
self.id_patient)
                        if rows is None:
                                _log.Log(gmLog.lErr, 'cannot load narrative for 
patient [%s]' % self.id_patient)
                                del self.__db_cache['narrative']
                                return None
                        # Instantiate narrative items and keep cache
                        for row in rows:
                                narr_row = {
                                        'pk_field': 'pk_narrative',
                                        'idx': idx,
                                        'data': row
                                }
                                try:
                                        narr = 
gmClinNarrative.cNarrative(row=narr_row)
                                        
self.__db_cache['narrative'].append(narr)
                                except gmExceptions.ConstructorError:
                                        _log.LogException('narrative error on 
[%s] for patient [%s]' % (row[0], self.id_patient) , sys.exc_info(), verbose=0)

                # ok, let's constrain our list
                filtered_narrative = []
                filtered_narrative.extend(self.__db_cache['narrative'])
                if since is not None:
                        filtered_narrative = filter(lambda narr: narr['date'] 
>= since, filtered_narrative)
                if until is not None:
                        filtered_narrative = filter(lambda narr: narr['date'] < 
until, filtered_narrative)
                if issues is not None:
                        filtered_narrative = filter(lambda narr: 
narr['pk_health_issue'] in issues, filtered_narrative)
                if episodes is not None:
                        filtered_narrative = filter(lambda narr: 
narr['pk_episode'] in episodes, filtered_narrative)
                if encounters is not None:
                        filtered_narrative = filter(lambda narr: 
narr['pk_encounter'] in encounters, filtered_narrative)
                if soap_cats is not None:
                        soap_cats = map(lambda c:string.lower(c), soap_cats)
                        filtered_narrative = filter(lambda narr: 
narr['soap_cat'] in soap_cats, filtered_narrative)
                if exclude_rfe_aoe:
                        filtered_narrative = filter(lambda narr: True not in 
[narr['is_rfe'], narr['is_aoe']], filtered_narrative)

                return filtered_narrative
        #--------------------------------------------------------
        # __getitem__ handling
        #--------------------------------------------------------
        def __getitem__(self, aVar = None):
                """Return any attribute if known how to retrieve it.
                """
                try:
                        return cClinicalRecord._get_handler[aVar](self)
                except KeyError:
                        _log.LogException('Missing get handler for [%s]' % 
aVar, sys.exc_info())
                        return None
        #--------------------------------------------------------
        def get_text_dump_old(self):
                # don't know how to invalidate this by means of
                # a notify without catching notifies from *all*
                # child tables, the best solution would be if
                # inserts in child tables would also fire triggers
                # of ancestor tables, but oh well,
                # until then the text dump will not be cached ...
                try:
                        return self.__db_cache['text dump old']
                except KeyError:
                        pass
                # not cached so go get it
                fields = [
                        'age',
                        "to_char(modified_when, 'YYYY-MM-DD @ HH24:MI') as 
modified_when",
                        'modified_by',
                        'clin_when',
                        "case is_modified when false then '%s' else '%s' end as 
modified_string" % (_('original entry'), _('modified entry')),
                        'pk_item',
                        'pk_encounter',
                        'pk_episode',
                        'pk_health_issue',
                        'src_table'
                ]
                cmd = "select %s from v_patient_items where id_patient=%%s 
order by src_table, age" % string.join(fields, ', ')
                ro_conn = self._conn_pool.GetConnection('historica')
                curs = ro_conn.cursor()
                if not gmPG.run_query(curs, None, cmd, self.id_patient):
                        _log.Log(gmLog.lErr, 'cannot load item links for 
patient [%s]' % self.id_patient)
                        curs.close()
                        return None
                rows = curs.fetchall()
                view_col_idx = gmPG.get_col_indices(curs)

                # aggregate by src_table for item retrieval
                items_by_table = {}
                for item in rows:
                        src_table = item[view_col_idx['src_table']]
                        pk_item = item[view_col_idx['pk_item']]
                        if not items_by_table.has_key(src_table):
                                items_by_table[src_table] = {}
                        items_by_table[src_table][pk_item] = item

                # get mapping for issue/episode IDs
                issues = self.get_health_issues()
                issue_map = {}
                for issue in issues:
                        issue_map[issue['id']] = issue['description']
                episodes = self.get_episodes()
                episode_map = {}
                for episode in episodes:
                        episode_map[episode['pk_episode']] = 
episode['description']
                emr_data = {}
                # get item data from all source tables
                for src_table in items_by_table.keys():
                        item_ids = items_by_table[src_table].keys()
                        # we don't know anything about the columns of
                        # the source tables but, hey, this is a dump
                        if len(item_ids) == 0:
                                _log.Log(gmLog.lInfo, 'no items in table [%s] 
?!?' % src_table)
                                continue
                        elif len(item_ids) == 1:
                                cmd = "select * from %s where pk_item=%%s order 
by modified_when" % src_table
                                if not gmPG.run_query(curs, None, cmd, 
item_ids[0]):
                                        _log.Log(gmLog.lErr, 'cannot load items 
from table [%s]' % src_table)
                                        # skip this table
                                        continue
                        elif len(item_ids) > 1:
                                cmd = "select * from %s where pk_item in %%s 
order by modified_when" % src_table
                                if not gmPG.run_query(curs, None, cmd, 
(tuple(item_ids),)):
                                        _log.Log(gmLog.lErr, 'cannot load items 
from table [%s]' % src_table)
                                        # skip this table
                                        continue
                        rows = curs.fetchall()
                        table_col_idx = gmPG.get_col_indices(curs)
                        # format per-table items
                        for row in rows:
                                # FIXME: make this get_pkey_name()
                                pk_item = row[table_col_idx['pk_item']]
                                view_row = items_by_table[src_table][pk_item]
                                age = view_row[view_col_idx['age']]
                                # format metadata
                                try:
                                        episode_name = 
episode_map[view_row[view_col_idx['pk_episode']]]
                                except:
                                        episode_name = 
view_row[view_col_idx['pk_episode']]
                                try:
                                        issue_name = 
issue_map[view_row[view_col_idx['pk_health_issue']]]
                                except:
                                        issue_name = 
view_row[view_col_idx['pk_health_issue']]

                                if not emr_data.has_key(age):
                                        emr_data[age] = []

                                emr_data[age].append(
                                        _('%s: encounter (%s)') % (
                                                
view_row[view_col_idx['clin_when']],
                                                
view_row[view_col_idx['pk_encounter']]
                                        )
                                )
                                emr_data[age].append(_('health issue: %s') % 
issue_name)
                                emr_data[age].append(_('episode     : %s') % 
episode_name)
                                # format table specific data columns
                                # - ignore those, they are metadata, some
                                #   are in v_patient_items data already
                                cols2ignore = [
                                        'pk_audit', 'row_version', 
'modified_when', 'modified_by',
                                        'pk_item', 'id', 'fk_encounter', 
'fk_episode'
                                ]
                                col_data = []
                                for col_name in table_col_idx.keys():
                                        if col_name in cols2ignore:
                                                continue
                                        emr_data[age].append("=> %s:" % 
col_name)
                                        
emr_data[age].append(row[table_col_idx[col_name]])
                                
emr_data[age].append("----------------------------------------------------")
                                emr_data[age].append("-- %s from table %s" % (
                                        
view_row[view_col_idx['modified_string']],
                                        src_table
                                ))
                                emr_data[age].append("-- written %s by %s" % (
                                        view_row[view_col_idx['modified_when']],
                                        view_row[view_col_idx['modified_by']]
                                ))
                                
emr_data[age].append("----------------------------------------------------")
                curs.close()
                self._conn_pool.ReleaseConnection('historica')
                return emr_data
        #--------------------------------------------------------
        def get_text_dump(self, since=None, until=None, encounters=None, 
episodes=None, issues=None):
                # don't know how to invalidate this by means of
                # a notify without catching notifies from *all*
                # child tables, the best solution would be if
                # inserts in child tables would also fire triggers
                # of ancestor tables, but oh well,
                # until then the text dump will not be cached ...
                try:
                        return self.__db_cache['text dump']
                except KeyError:
                        pass
                # not cached so go get it
                # -- get the data --
                fields = [
                        'age',
                        "to_char(modified_when, 'YYYY-MM-DD @ HH24:MI') as 
modified_when",
                        'modified_by',
                        'clin_when',
                        "case is_modified when false then '%s' else '%s' end as 
modified_string" % (_('original entry'), _('modified entry')),
                        'pk_item',
                        'pk_encounter',
                        'pk_episode',
                        'pk_health_issue',
                        'src_table'
                ]
                select_from = "select %s from v_patient_items" % ', 
'.join(fields)
                # handle constraint conditions
                where_snippets = []
                params = {}
                where_snippets.append('id_patient=%(pat_id)s')
                params['pat_id'] = self.id_patient
                if not since is None:
                        where_snippets.append('clin_when >= %(since)s')
                        params['since'] = since
                if not until is None:
                        where_snippets.append('clin_when <= %(until)s')
                        params['until'] = until
                # FIXME: these are interrelated, eg if we constrain encounter
                # we automatically constrain issue/episode, so handle that,
                # encounters
                if not encounters is None and len(encounters) > 0:
                        params['enc'] = encounters
                        if len(encounters) > 1:
                                where_snippets.append('fk_encounter in %(enc)s')
                        else:
                                where_snippets.append('fk_encounter=%(enc)s')
                # episodes
                if not episodes is None and len(episodes) > 0:
                        params['epi'] = episodes
                        if len(episodes) > 1:
                                where_snippets.append('fk_episode in %(epi)s')
                        else:
                                where_snippets.append('fk_episode=%(epi)s')
                # health issues
                if not issues is None and len(issues) > 0:
                        params['issue'] = issues
                        if len(issues) > 1:
                                where_snippets.append('fk_health_issue in 
%(issue)s')
                        else:
                                
where_snippets.append('fk_health_issue=%(issue)s')

                where_clause = ' and '.join(where_snippets)
                order_by = 'order by src_table, age'
                cmd = "%s where %s %s" % (select_from, where_clause, order_by)

                rows, view_col_idx = gmPG.run_ro_query('historica', cmd, 1, 
params)
                if rows is None:
                        _log.Log(gmLog.lErr, 'cannot load item links for 
patient [%s]' % self.id_patient)
                        return None

                # -- sort the data --
                # FIXME: by issue/encounter/episode, eg formatting
                # aggregate by src_table for item retrieval
                items_by_table = {}
                for item in rows:
                        src_table = item[view_col_idx['src_table']]
                        pk_item = item[view_col_idx['pk_item']]
                        if not items_by_table.has_key(src_table):
                                items_by_table[src_table] = {}
                        items_by_table[src_table][pk_item] = item

                # get mapping for issue/episode IDs
                issues = self.get_health_issues()
                issue_map = {}
                for issue in issues:
                        issue_map[issue['id']] = issue['description']
                episodes = self.get_episodes()
                episode_map = {}
                for episode in episodes:
                        episode_map[episode['pk_episode']] = 
episode['description']
                emr_data = {}
                # get item data from all source tables
                ro_conn = self._conn_pool.GetConnection('historica')
                curs = ro_conn.cursor()
                for src_table in items_by_table.keys():
                        item_ids = items_by_table[src_table].keys()
                        # we don't know anything about the columns of
                        # the source tables but, hey, this is a dump
                        if len(item_ids) == 0:
                                _log.Log(gmLog.lInfo, 'no items in table [%s] 
?!?' % src_table)
                                continue
                        elif len(item_ids) == 1:
                                cmd = "select * from %s where pk_item=%%s order 
by modified_when" % src_table
                                if not gmPG.run_query(curs, None, cmd, 
item_ids[0]):
                                        _log.Log(gmLog.lErr, 'cannot load items 
from table [%s]' % src_table)
                                        # skip this table
                                        continue
                        elif len(item_ids) > 1:
                                cmd = "select * from %s where pk_item in %%s 
order by modified_when" % src_table
                                if not gmPG.run_query(curs, None, cmd, 
(tuple(item_ids),)):
                                        _log.Log(gmLog.lErr, 'cannot load items 
from table [%s]' % src_table)
                                        # skip this table
                                        continue
                        rows = curs.fetchall()
                        table_col_idx = gmPG.get_col_indices(curs)
                        # format per-table items
                        for row in rows:
                                # FIXME: make this get_pkey_name()
                                pk_item = row[table_col_idx['pk_item']]
                                view_row = items_by_table[src_table][pk_item]
                                age = view_row[view_col_idx['age']]
                                # format metadata
                                try:
                                        episode_name = 
episode_map[view_row[view_col_idx['pk_episode']]]
                                except:
                                        episode_name = 
view_row[view_col_idx['pk_episode']]
                                try:
                                        issue_name = 
issue_map[view_row[view_col_idx['pk_health_issue']]]
                                except:
                                        issue_name = 
view_row[view_col_idx['pk_health_issue']]

                                if not emr_data.has_key(age):
                                        emr_data[age] = []

                                emr_data[age].append(
                                        _('%s: encounter (%s)') % (
                                                
view_row[view_col_idx['clin_when']],
                                                
view_row[view_col_idx['pk_encounter']]
                                        )
                                )
                                emr_data[age].append(_('health issue: %s') % 
issue_name)
                                emr_data[age].append(_('episode     : %s') % 
episode_name)
                                # format table specific data columns
                                # - ignore those, they are metadata, some
                                #   are in v_patient_items data already
                                cols2ignore = [
                                        'pk_audit', 'row_version', 
'modified_when', 'modified_by',
                                        'pk_item', 'id', 'fk_encounter', 
'fk_episode', 'pk'
                                ]
                                col_data = []
                                for col_name in table_col_idx.keys():
                                        if col_name in cols2ignore:
                                                continue
                                        emr_data[age].append("=> %s: %s" % 
(col_name, row[table_col_idx[col_name]]))
                                
emr_data[age].append("----------------------------------------------------")
                                emr_data[age].append("-- %s from table %s" % (
                                        
view_row[view_col_idx['modified_string']],
                                        src_table
                                ))
                                emr_data[age].append("-- written %s by %s" % (
                                        view_row[view_col_idx['modified_when']],
                                        view_row[view_col_idx['modified_by']]
                                ))
                                
emr_data[age].append("----------------------------------------------------")
                curs.close()
                self._conn_pool.ReleaseConnection('historica')
                return emr_data
        #--------------------------------------------------------
        def get_patient_ID(self):
                return self.id_patient
        #--------------------------------------------------------
        # allergy API
        #--------------------------------------------------------
        def get_allergies(self, remove_sensitivities=None, since=None, 
until=None, encounters=None, episodes=None, issues=None, ID_list=None):
                """Retrieves patient allergy items.

                        remove_sensitivities
                                - retrieve real allergies only, without 
sensitivities
                        since
                                - initial date for allergy items
                        until
                                - final date for allergy items
                        encounters
                                - list of encounters whose allergies are to be 
retrieved
                        episodes
                                - list of episodes whose allergies are to be 
retrieved
                        issues
                                - list of health issues whose allergies are to 
be retrieved
        """
                try:
                        self.__db_cache['allergies']
                except KeyError:
                        # FIXME: check allergy_state first, then cross-check 
with select exists(... from allergy)
                        self.__db_cache['allergies'] = []
                        cmd = "select pk_allergy from v_pat_allergies where 
pk_patient=%s"
                        rows = gmPG.run_ro_query('historica', cmd, None, 
self.id_patient)
                        if rows is None:
                                _log.Log(gmLog.lErr, 'cannot load allergies for 
patient [%s]' % self.id_patient)
                                del self.__db_cache['allergies']
                                # better fail here contrary to what we do 
elsewhere
                                return None
                        # Instantiate allergy items and keep cache
                        for row in rows:
                                try:
                                        
self.__db_cache['allergies'].append(gmAllergy.cAllergy(aPK_obj=row[0]))
                                except gmExceptions.ConstructorError:
                                        _log.LogException('allergy error on 
[%s] for patient [%s]' % (row[0], self.id_patient) , sys.exc_info(), verbose=0)
                                        _log.Log(gmLog.lInfo, 'better to report 
an error than rely on incomplete allergy information')
                                        del self.__db_cache['allergies']
                                        return None

                # ok, let's constrain our list
                filtered_allergies = []
                filtered_allergies.extend(self.__db_cache['allergies'])
                if ID_list is not None:
                        filtered_allergies = filter(lambda allg: 
allg['pk_allergy'] in ID_list, filtered_allergies)
                        if len(filtered_allergies) == 0:
                                _log.Log(gmLog.lErr, 'no allergies of list [%s] 
found for patient [%s]' % (str(ID_list), self.id_patient))
                                # better fail here contrary to what we do 
elsewhere
                                return None
                        else:
                                return filtered_allergies
                if remove_sensitivities is not None:
                        filtered_allergies = filter(lambda allg: allg['type'] 
== 'allergy', filtered_allergies)
                if since is not None:
                        filtered_allergies = filter(lambda allg: allg['date'] 
>= since, filtered_allergies)
                if until is not None:
                        filtered_allergies = filter(lambda allg: allg['date'] < 
until, filtered_allergies)
                if issues is not None:
                        filtered_allergies = filter(lambda allg: 
allg['pk_health_issue'] in issues, filtered_allergies)
                if episodes is not None:
                        filtered_allergies = filter(lambda allg: 
allg['pk_episode'] in episodes, filtered_allergies)
                if encounters is not None:
                        filtered_allergies = filter(lambda allg: 
allg['pk_encounter'] in encounters, filtered_allergies)

                return filtered_allergies
        #--------------------------------------------------------
        def add_allergy(self, substance=None, allg_type=None, 
encounter_id=None, episode_id=None):
                if encounter_id is None:
                        encounter_id = self.__encounter['pk_encounter']
                if episode_id is None:
                        episode_id = self.__episode['pk_episode']
                status, data = gmAllergy.create_allergy(
                        substance=substance,
                        allg_type=allg_type,
                        encounter_id=encounter_id,
                        episode_id=episode_id
                )
                if not status:
                        _log.Log(gmLog.lErr, str(data))
                        return None
                return data
        #--------------------------------------------------------
        def set_allergic_state(self, status=None):
                pass
        #--------------------------------------------------------
        # episodes API
        #--------------------------------------------------------
        def get_active_episode(self):
                return self.__episode
        #--------------------------------------------------------
        def get_episodes(self, id_list=None, issues = None):
                """Fetches from backend patient episodes.

                id_list - Episodes' PKs
                issues - Health issues' PKs to filter episodes by
                """
                try:
                        self.__db_cache['episodes']
                except KeyError:
                        self.__db_cache['episodes'] = []

                        cmd = "select pk_episode from v_pat_episodes where 
id_patient=%s"
                        rows = gmPG.run_ro_query('historica', cmd, None, 
self.id_patient)
                        if rows is None:
                                _log.Log(gmLog.lErr, 'error loading episodes 
for patient [%s]' % self.id_patient)
                                del self.__db_cache['episodes']
                                return None
                        for row in rows:
                                try:
                                        
self.__db_cache['episodes'].append(gmEMRStructItems.cEpisode(aPK_obj=row[0]))
                                except gmExceptions.ConstructorError, msg:
                                        _log.LogException(str(msg), 
sys.exc_info(), verbose=0)

                if id_list is None and issues is None:
                        return self.__db_cache['episodes']
                # ok, let's filter episode list
                filtered_episodes = []
                filtered_episodes.extend(self.__db_cache['episodes'])
                if issues is not None:
                        filtered_episodes = filter(lambda epi: 
epi['pk_health_issue'] in issues, filtered_episodes)
                if id_list is not None:
                        if id_list == []:
                                _log.Log(gmLog.lErr, 'id_list to filter by is 
empty, most likely a programming error')
                        filtered_episodes = filter(lambda epi: 
epi['pk_episode'] in id_list, filtered_episodes)
                return filtered_episodes
        #------------------------------------------------------------------
        def add_episode(self, episode_name = 'xxxDEFAULTxxx', pk_health_issue = 
None):
                """Add episode 'episode_name' for a patient's health issue.

                - pk_health_issue - health issue PK, may be None
                - episode_name - episode name

                - adds default episode if no name given
                - silently returns if episode already exists
                """
                try:
                        self.__db_cache['episodes']
                except KeyError:
                        self.get_episodes()
                # try to create it
                success, episode = 
gmEMRStructItems.create_episode(id_patient=self.id_patient, 
pk_health_issue=pk_health_issue, episode_name=episode_name)
                if not success:
                        _log.Log(gmLog.lErr, 'cannot create episode [%s] for 
patient [%s] and health issue [%s]' % (episode_name, self.id_patient, 
pk_health_issue))
                        return None
                return episode
        #--------------------------------------------------------
        def __load_last_active_episode(self):
                # check if there's an active episode
                episode = None
                cmd = "select fk_episode from last_act_episode where 
id_patient=%s limit 1"
                rows = gmPG.run_ro_query('historica', cmd, None, 
self.id_patient)
                if rows is None:
                        _log.Log(gmLog.lErr, 'error getting last_act_episode 
for patient [%s]' % self.id_patient)
                else:
                        if len(rows) != 0:
                                try:
                                        episode = 
gmEMRStructItems.cEpisode(aPK_obj=rows[0][0])
                                except gmExceptions.ConstructorError, msg:
                                        _log.LogException(str(msg), 
sys.exc_info(), verbose=0)

                # no last_active_episode recorded, so try to find the
                # episode with the most recently modified clinical item
                if episode is None:
                        cmd = """
select pk
from clin_episode
where pk=(
        select distinct on(pk_episode) pk_episode
        from v_patient_items
        where
                id_patient=%s
                        and
                modified_when=(
                        select max(vpi.modified_when)
                        from v_patient_items vpi
                        where vpi.id_patient=%s
                )
        )"""
                        rows = gmPG.run_ro_query('historica', cmd, None, 
self.id_patient, self.id_patient)
                        if rows is None:
                                _log.Log(gmLog.lErr, 'error getting most recent 
episode from v_patient_items for patient [%s]' % self.id_patient)
                        else:
                                if len(rows) != 0:
                                        try:
                                                episode = 
gmEMRStructItems.cEpisode(aPK_obj=rows[0][0])
                                                episode.set_active()
                                        except gmExceptions.ConstructorError, 
msg:
                                                _log.LogException(str(msg), 
sys.exc_info(), verbose=0)
                                                episode = None

                # no clinical items recorded, so try to find
                # the youngest episode for this patient
                if episode is None:
                        cmd = """
select vpe0.pk_episode
from
        v_pat_episodes vpe0
where
        vpe0.id_patient = %s
                and
        vpe0.episode_modified_when = (
                select max(vpe1.episode_modified_when)
                from v_pat_episodes vpe1
                where vpe1.pk_episode=vpe0.pk_episode
        )"""
                        rows = gmPG.run_ro_query('historica', cmd, None, 
self.id_patient)
                        if rows is None:
                                _log.Log(gmLog.lErr, 'error getting most 
recently touched episode on patient [%s]' % self.id_patient)
                        else:
                                if len(rows) != 0:
                                        try:
                                                episode = 
gmEMRStructItems.cEpisode(aPK_obj=rows[0][0])
                                                episode.set_active()
                                        except gmExceptions.ConstructorError, 
msg:
                                                _log.Log(str(msg), 
sys.exc_info(), verbose=0)
                                                episode = None

                # none found whatsoever
                if episode is None:
                        # so try to create default episode ...
                        success, result = 
gmEMRStructItems.create_episode(pk_health_issue=self.__health_issue['id'])
                        if not success:
                                _log.Log(gmLog.lErr, 'cannot even activate 
default episode for patient [%s], aborting' %  self.id_patient)
                                _log.Log(gmLog.lErr, result)
                                return False
                        episode = result

                self.__episode = episode
                # load corresponding health issue
                self.__health_issue = None
                if self.__episode['pk_health_issue'] is not None:
                        self.__health_issue = 
self.get_health_issues(id_list=[self.__episode['pk_health_issue']])
                        if self.__health_issue is None:
                                _log.Log(gmLog.lErr, 'cannot activate health 
issue linked from episode [%s]' % str(self.__episode))

                return True
        #--------------------------------------------------------
        def set_active_episode(self, ep_name='xxxDEFAULTxxx'):
                if self.get_episodes() is None:
                        _log.Log(gmLog.lErr, 'cannot activate episode [%s], 
cannot get episode list' % ep_name)
                        return False
                for episode in self.__db_cache['episodes']:
                        if episode['description'] == ep_name:
                                episode.set_active()
                                return True
                _log.Log(gmLog.lErr, 'cannot activate episode [%s], not found 
in list' % ep_name)
                return False
        #--------------------------------------------------------
        # health issues API
        #--------------------------------------------------------
        def get_health_issues(self, id_list = None):
                try:
                        self.__db_cache['health issues']
                except KeyError:
                        self.__db_cache['health issues'] = []
                        cmd = "select id from clin_health_issue where 
id_patient=%s"
                        rows = gmPG.run_ro_query('historica', cmd, None, 
self.id_patient)
                        if rows is None:
                                _log.Log(gmLog.lErr, 'cannot load health issues 
for patient [%s]' % self.id_patient)
                                del self.__db_cache['health issues']
                                return None
                        for row in rows:
                                try:
                                        self.__db_cache['health 
issues'].append(gmEMRStructItems.cHealthIssue(aPK_obj=row[0]))
                                except gmExceptions.ConstructorError, msg:
                                        _log.LogException(str(msg), 
sys.exc_info(), verbose=0)
                if id_list is None:
                        return self.__db_cache['health issues']
                if id_list == []:
                        _log.Log(gmLog.lErr, 'id_list to filter by is empty, 
most likely a programming error')
                filtered_issues = []
                for issue in self.__db_cache['health issues']:
                        if issue['id'] in id_list:
                                filtered_issues.append(issue)
                return filtered_issues
        #------------------------------------------------------------------
        def add_health_issue(self, health_issue_name=None):
                """Adds patient health issue.

                - silently returns if it already exists
                """
                try:
                        self.__db_cache['health issues']
                except KeyError:
                        self.get_health_issues()
                # try to create it
                success, issue = 
gmEMRStructItems.create_health_issue(patient_id=self.id_patient, 
description=health_issue_name)
                if not success:
                        _log.Log(gmLog.lErr, 'cannot create health issue [%s] 
for patient [%s]' % (health_issue_name, self.id_patient))
                        return None
                return issue

        #--------------------------------------------------------
        # vaccinations API
        #--------------------------------------------------------
        def get_scheduled_vaccination_regimes(self, ID=None, indications=None):
                """Retrieves vaccination regimes the patient is on.

                        optional:
                        * ID - PK of the vaccination regime                     
        
                        * indications - indications we want to retrieve 
vaccination
                                regimes for, must be primary language, not 
l10n_indication
                """
                try:
                        self.__db_cache['vaccinations']['scheduled regimes']
                except KeyError:
                        # retrieve vaccination regimes definitions
                        self.__db_cache['vaccinations']['scheduled regimes'] = 
[]
                        cmd = """select distinct on(pk_regime) pk_regime
                                         from v_vaccs_scheduled4pat
                                         where pk_patient=%s"""
                        rows = gmPG.run_ro_query('historica', cmd, None, 
self.id_patient)
                        if rows is None:
                                _log.Log(gmLog.lErr, 'cannot retrieve scheduled 
vaccination regimes')
                                del self.__db_cache['vaccinations']['scheduled 
regimes']
                                return None
                        # Instantiate vaccination items and keep cache
                        for row in rows:
                                try:
                                        
self.__db_cache['vaccinations']['scheduled 
regimes'].append(gmVaccination.cVaccinationRegime(aPK_obj=row[0]))
                                except gmExceptions.ConstructorError:
                                        _log.LogException('vaccination regime 
error on [%s] for patient [%s]' % (row[0], self.id_patient) , sys.exc_info(), 
verbose=0)

                # ok, let's constrain our list
                filtered_regimes = []
                
filtered_regimes.extend(self.__db_cache['vaccinations']['scheduled regimes'])
                if ID is not None:
                        filtered_regimes = filter(lambda regime: 
regime['pk_regime'] == ID, filtered_regimes)
                        if len(filtered_regimes) == 0:
                                _log.Log(gmLog.lErr, 'no vaccination regime 
[%s] found for patient [%s]' % (ID, self.id_patient))
                                return []
                        else:
                                return filtered_regimes[0]
                if indications is not None:
                        filtered_regimes = filter(lambda regime: 
regime['indication'] in indications, filtered_regimes)

                return filtered_regimes
        #--------------------------------------------------------
        def get_vaccinated_indications(self):
                """Retrieves patient vaccinated indications list.

                Note that this does NOT rely on the patient being on
                some schedule or other but rather works with what the
                patient has ACTUALLY been vaccinated against. This is
                deliberate !
                """
                # most likely, vaccinations will be fetched close
                # by so it makes sense to count on the cache being
                # filled (or fill it for nearby use)
                vaccinations = self.get_vaccinations()
                if vaccinations is None:
                        _log.Log(gmLog.lErr, 'cannot load vaccinated 
indications for patient [%s]' % self.id_patient)
                        return (False, [[_('ERROR: cannot retrieve vaccinated 
indications'), _('ERROR: cannot retrieve vaccinated indications')]])
                if len(vaccinations) == 0:
                        return (True, [[_('no vaccinations recorded'), _('no 
vaccinations recorded')]])
                v_indications = []
                for vacc in vaccinations:
                        tmp = [vacc['indication'], vacc['l10n_indication']]
                        # remove duplicates
                        if tmp in v_indications:
                                continue
                        v_indications.append(tmp)
                return (True, v_indications)
        #--------------------------------------------------------
        def get_vaccinations(self, ID=None, indications=None, since=None, 
until=None, encounters=None, episodes=None, issues=None):
                """Retrieves list of vaccinations the patient has received.

                optional:
                * ID - PK of a vaccination
                * indications - indications we want to retrieve vaccination
                        items for, must be primary language, not l10n_indication
        * since - initial date for allergy items
        * until - final date for allergy items
        * encounters - list of encounters whose allergies are to be retrieved
        * episodes - list of episodes whose allergies are to be retrieved
        * issues - list of health issues whose allergies are to be retrieved
                """
                try:
                        self.__db_cache['vaccinations']['vaccinated']
                except KeyError:                        
                        self.__db_cache['vaccinations']['vaccinated'] = []
                        # Important fetch ordering by indication, date to know 
if a vaccination is booster
                        cmd= """select * from v_pat_vacc4ind
                                        where pk_patient=%s
                                        order by indication, date"""
                        rows, idx  = gmPG.run_ro_query('historica', cmd, True, 
self.id_patient)
                        if rows is None:
                                _log.Log(gmLog.lErr, 'cannot load given 
vaccinations for patient [%s]' % self.id_patient)
                                del 
self.__db_cache['vaccinations']['vaccinated']
                                return None
                        # Instantiate vaccination items
                        vaccs_by_ind = {}
                        for row in rows:
                                vacc_row = {
                                        'pk_field': 'pk_vaccination',
                                        'idx': idx,
                                        'data': row
                                }
                                try:
                                        vacc = 
gmVaccination.cVaccination(row=vacc_row)
                                        
self.__db_cache['vaccinations']['vaccinated'].append(vacc)
                                except gmExceptions.ConstructorError:
                                        _log.LogException('vaccination error on 
[%s] for patient [%s]' % (row, self.id_patient), sys.exc_info(), verbose=0)
                                # keep them, ordered by indication
                                try:
                                        
vaccs_by_ind[vacc['indication']].append(vacc)
                                except KeyError:
                                        vaccs_by_ind[vacc['indication']] = 
[vacc]

                        # calculate sequence number and is_booster
                        for ind in vaccs_by_ind.keys():
                                vacc_regimes = 
self.get_scheduled_vaccination_regimes(indications = [ind])
                                for vacc in vaccs_by_ind[ind]:
                                        # due to the "order by indication, 
date" the vaccinations are in the
                                        # right temporal order inside the 
indication-keyed dicts
                                        seq_no = vaccs_by_ind[ind].index(vacc) 
+ 1
                                        vacc['seq_no'] = seq_no
                                        # if no active schedule for indication 
we cannot
                                        # check for booster status (eg. seq_no 
> max_shot)
                                        if (vacc_regimes is None) or 
(len(vacc_regimes) == 0):
                                                continue
                                        if seq_no > vacc_regimes[0]['shots']:
                                                vacc['is_booster'] = True
                        del vaccs_by_ind

                # ok, let's constrain our list
                filtered_shots = []
                
filtered_shots.extend(self.__db_cache['vaccinations']['vaccinated'])
                if ID is not None:
                        filtered_shots = filter(lambda shot: 
shot['pk_vaccination'] == ID, filtered_shots)
                        if len(filtered_shots) == 0:
                                _log.Log(gmLog.lErr, 'no vaccination [%s] found 
for patient [%s]' % (ID, self.id_patient))
                                return None
                        else:
                                return filtered_shots[0]
                if since is not None:
                        filtered_shots = filter(lambda shot: shot['date'] >= 
since, filtered_shots)
                if until is not None:
                        filtered_shots = filter(lambda shot: shot['date'] < 
until, filtered_shots)
                if issues is not None:
                        filtered_shots = filter(lambda shot: 
shot['pk_health_issue'] in issues, filtered_shots)
                if episodes is not None:
                        filtered_shots = filter(lambda shot: shot['pk_episode'] 
in episodes, filtered_shots)
                if encounters is not None:
                        filtered_shots = filter(lambda shot: 
shot['pk_encounter'] in encounters, filtered_shots)
                if indications is not None:
                        filtered_shots = filter(lambda shot: shot['indication'] 
in indications, filtered_shots)
                return filtered_shots
        #--------------------------------------------------------
        def get_scheduled_vaccinations(self, indications=None):
                """Retrieves vaccinations scheduled for a regime a patient is 
on.

                The regime is referenced by its indication (not l10n)

                * indications - List of indications (not l10n) of regimes we 
want scheduled
                                vaccinations to be fetched for
                """
                try:
                        self.__db_cache['vaccinations']['scheduled']
                except KeyError:
                        self.__db_cache['vaccinations']['scheduled'] = []
                        cmd = """select * from v_vaccs_scheduled4pat where 
pk_patient=%s"""
                        rows, idx = gmPG.run_ro_query('historica', cmd, True, 
self.id_patient)
                        if rows is None:
                                _log.Log(gmLog.lErr, 'cannot load scheduled 
vaccinations for patient [%s]' % self.id_patient)
                                del self.__db_cache['vaccinations']['scheduled']
                                return None
                        # Instantiate vaccination items
                        for row in rows:
                                vacc_row = {
                                        'pk_field': 'pk_vacc_def',
                                        'idx': idx,
                                        'data': row
                                }
                                try:
                                        
self.__db_cache['vaccinations']['scheduled'].append(gmVaccination.cScheduledVaccination(row
 = vacc_row))
                                except gmExceptions.ConstructorError:
                                        _log.LogException('vaccination error on 
[%s] for patient [%s]' % (row[0], self.id_patient), sys.exc_info(), verbose=0)

                # ok, let's constrain our list
                if indications is None:
                        return self.__db_cache['vaccinations']['scheduled']
                filtered_shots = []
                
filtered_shots.extend(self.__db_cache['vaccinations']['scheduled'])
                filtered_shots = filter(lambda shot: shot['indication'] in 
indications, filtered_shots)
                return filtered_shots
        #--------------------------------------------------------
        def get_missing_vaccinations(self, indications=None):
                try:
                        self.__db_cache['vaccinations']['missing']
                except KeyError:
                        self.__db_cache['vaccinations']['missing'] = {}
                        # 1) non-booster
                        self.__db_cache['vaccinations']['missing']['due'] = []
                        # get list of (indication, seq_no) tuples
                        cmd = "select indication, seq_no from 
v_pat_missing_vaccs where pk_patient=%s"
                        rows = gmPG.run_ro_query('historica', cmd, None, 
self.id_patient)
                        if rows is None:
                                _log.Log(gmLog.lErr, 'error loading 
(indication, seq_no) for due/overdue vaccinations for patient [%s]' % 
self.id_patient)
                                return None
                        pk_args = {'pat_id': self.id_patient}
                        if rows is not None:
                                for row in rows:
                                        pk_args['indication'] = row[0]
                                        pk_args['seq_no'] = row[1]
                                        try:
                                                
self.__db_cache['vaccinations']['missing']['due'].append(gmVaccination.cMissingVaccination(aPK_obj=pk_args))
                                        except gmExceptions.ConstructorError:
                                                _log.LogException('vaccination 
error on [%s] for patient [%s]' % (row[0], self.id_patient) , sys.exc_info(), 
verbose=0)
                        # 2) boosters
                        self.__db_cache['vaccinations']['missing']['boosters'] 
= []
                        # get list of indications
                        cmd = "select indication, seq_no from 
v_pat_missing_boosters where pk_patient=%s"
                        rows = gmPG.run_ro_query('historica', cmd, None, 
self.id_patient)
                        if rows is None:
                                _log.Log(gmLog.lErr, 'error loading indications 
for missing boosters for patient [%s]' % self.id_patient)
                                return None
                        pk_args = {'pat_id': self.id_patient}
                        if rows is not None:
                                for row in rows:
                                        pk_args['indication'] = row[0]
                                        try:
                                                
self.__db_cache['vaccinations']['missing']['boosters'].append(gmVaccination.cMissingBooster(aPK_obj=pk_args))
                                        except gmExceptions.ConstructorError:
                                                _log.LogException('booster 
error on [%s] for patient [%s]' % (row[0], self.id_patient) , sys.exc_info(), 
verbose=0)
                # if any filters ...
                if indications is None:
                        return self.__db_cache['vaccinations']['missing']
                if len(indications) == 0:
                        return self.__db_cache['vaccinations']['missing']
                # ... apply them
                filtered_shots = {
                        'due': [],
                        'boosters': []
                }
                for due_shot in 
self.__db_cache['vaccinations']['missing']['due']:
                        if due_shot['indication'] in indications: #and due_shot 
not in filtered_shots['due']:
                                filtered_shots['due'].append(due_shot)
                for due_shot in 
self.__db_cache['vaccinations']['missing']['boosters']:
                        if due_shot['indication'] in indications: #and due_shot 
not in filtered_shots['boosters']:
                                filtered_shots['boosters'].append(due_shot)
                return filtered_shots
        #--------------------------------------------------------
        def add_vaccination(self, vaccine=None):
                """Creates a new vaccination entry in backend."""
                return gmVaccination.create_vaccination (
                        patient_id = self.id_patient,
                        episode_id = self.__episode['pk_episode'],
                        encounter_id = self.__encounter['pk_encounter'],
                        staff_id = _whoami.get_staff_ID(),
                        vaccine = vaccine
                )
        #------------------------------------------------------------------
        # encounter API
        #------------------------------------------------------------------
        def __initiate_active_encounter(self):
                # 1) "very recent" encounter recorded ?
                if self.__activate_very_recent_encounter():
                        return True
                # 2) "fairly recent" encounter recorded ?
                if self.__activate_fairly_recent_encounter():
                        return True
                # 3) no encounter yet or too old, create new one
                result = gmEMRStructItems.create_encounter(
                        fk_patient = self.id_patient,
                        fk_provider = _whoami.get_staff_ID()
                )
                if result is False:
                        return False
                self.__encounter = result
                return True
        #------------------------------------------------------------------
        def __activate_very_recent_encounter(self):
                """Try to attach to a "very recent" encounter if there is one.

                returns:
                        False: no "very recent" encounter, create new one
                True: success
                """
                days, seconds = _encounter_soft_ttl.absvalues()
                sttl = '%s days %s seconds' % (days, seconds)
                cmd = """
                        select pk_encounter
                        from v_most_recent_encounters
                        where
                                pk_patient=%s
                                        and
                                age(last_affirmed) < %s::interval
                        """
                enc_rows = gmPG.run_ro_query('historica', cmd, None, 
self.id_patient, sttl)
                # error
                if enc_rows is None:
                        _log.Log(gmLog.lErr, 'error accessing encounter tables')
                        return False
                # none found
                if len(enc_rows) == 0:
                        return False
                # attach to existing
                try:
                        self.__encounter = 
gmEMRStructItems.cEncounter(aPK_obj=enc_rows[0][0])
                except gmExceptions.ConstructorError, msg:
                        _log.LogException(str(msg), sys.exc_info(), verbose=0)
                        return False
                self.__encounter.set_active(staff_id = _whoami.get_staff_ID())
                return True
        #------------------------------------------------------------------
        def __activate_fairly_recent_encounter(self):
                """Try to attach to a "fairly recent" encounter if there is one.

                returns:
                        False: no "fairly recent" encounter, create new one
                True: success
                """
                # if we find one will we even be able to ask the user ?
                if _func_ask_user is None:
                        return False
                days, seconds = _encounter_soft_ttl.absvalues()
                sttl = '%s days %s seconds' % (days, seconds)
                days, seconds = _encounter_hard_ttl.absvalues()
                httl = '%s days %s seconds' % (days, seconds)
                cmd = """
                        select  pk_encounter
                        from v_most_recent_encounters
                        where
                                pk_patient=%s
                                        and
                                age(last_affirmed) between %s::interval and 
%s::interval
                        """
                enc_rows = gmPG.run_ro_query('historica', cmd, None, 
self.id_patient, sttl, httl)
                # error
                if enc_rows is None:
                        _log.Log(gmLog.lErr, 'error accessing encounter tables')
                        return False
                # none found
                if len(enc_rows) == 0:
                        return False
                try:
                        encounter = 
gmEMRStructItems.cEncounter(aPK_obj=enc_rows[0][0])
                except gmExceptions.ConstructorError:
                        return False
                # ask user whether to attach or not
                cmd = """
                        select title, firstnames, lastnames, gender, dob
                        from v_basic_person     where i_id=%s"""
                pat = gmPG.run_ro_query('personalia', cmd, None, 
self.id_patient)
                if (pat is None) or (len(pat) == 0):
                        _log.Log(gmLog.lErr, 'cannot access patient [%s]' % 
self.id_patient)
                        return False
                pat_str = '%s %s %s (%s), %s, #%s' % (
                        pat[0][0][:5],
                        pat[0][1][:12],
                        pat[0][2][:12],
                        pat[0][3],
                        pat[0][4].Format('%Y-%m-%d'),
                        self.id_patient
                )
                msg = _(
                        'A fairly recent encounter exists for patient:\n'
                        ' %s\n'
                        'started    : %s\n'
                        'affirmed   : %s\n'
                        'type       : %s\n'
                        'description: %s\n\n'
                        'Do you want to reactivate this encounter ?\n'
                        'Hitting "No" will start a new one.'
                ) % (pat_str, encounter['started'], encounter['last_affirmed'], 
encounter['l10n_type'], encounter['description'])
                title = _('recording patient encounter')
                attach = False
                try:
                        attach = _func_ask_user(msg, title)
                except:
                        _log.LogException('cannot ask user for guidance', 
sys.exc_info(), verbose=0)
                        return False
                if not attach:
                        return False
                # attach to existing
                self.__encounter = encounter
                self.__encounter.set_active(staff_id = _whoami.get_staff_ID())
                return True
        #------------------------------------------------------------------
        def get_active_encounter(self):
                return self.__encounter
        #------------------------------------------------------------------
        def attach_to_encounter(self, anID = None):
#               """Attach to an encounter but do not activate it.
#               """
                # FIXME: this is the correct implementation but I
                # think the concept of attach_to_encounter is flawed,
                # eg we don't need it
#               if anID is None:
#                       return False
#               encounter = gmEMRStructItems.cEncounter(aPK_obj= anID)
#               if encounter is None:
#                       _log.Log(gmLog.lWarn, 'cannot instantiante encounter 
[%s]' % anID)
#                       return False
#               if not encounter.set_attached_to(staff_id = 
_whoami.get_staff_ID()):
#                   _log.Log(gmLog.lWarn, 'cannot attach to encounter [%s]' % 
anID)
#                   return False
#               self.encounter = encounter
#               return True
                pass
        #--------------------------------------------------------
        def get_encounters(self, since=None, until=None, id_list=None, 
episodes=None, issues=None):
                """
                Retrieves patient's encounters

                id_list - PKs of encounters to fetch
                since - initial date for encounter items, DateTime instance
                until - final date for encounter items, DateTime instance
                episodes - PKs of the episodes the encounters belong to 
(many-to-many relation)
                issues - PKs of the health issues the encounters belong to 
(many-to-many relation)
                """
                try:
                        self.__db_cache['encounters']
                except KeyError:
                        # fetch all encounters for patient
                        self.__db_cache['encounters'] = []
                        cmd = "select id from clin_encounter where 
fk_patient=%s order by started"
                        rows = gmPG.run_ro_query('historica', cmd, None, 
self.id_patient)
                        if rows is None:
                                _log.Log(gmLog.lErr, 'cannot load encounters 
for patient [%s]' % self.id_patient)
                                del self.__db_cache['encounters']
                                return None
                        for row in rows:
                                try:
                                        
self.__db_cache['encounters'].append(gmEMRStructItems.cEncounter(aPK_obj=row[0]))
                                except gmExceptions.ConstructorError, msg:
                                        _log.LogException(str(msg), 
sys.exc_info(), verbose=0)                                  
                # we've got the encounters, start filtering
                filtered_encounters = []
                filtered_encounters.extend(self.__db_cache['encounters'])
                if id_list is not None:
                        filtered_encounters = filter(lambda enc: 
enc['pk_encounter'] in id_list, filtered_encounters)
                if since is not None:
                        filtered_encounters = filter(lambda enc: enc['started'] 
>= since, filtered_encounters)
                if until is not None:
                        filtered_encounters = filter(lambda enc: 
enc['last_affirmed'] <= until, filtered_encounters)
                if issues is not None and len(issues) > 0:
                        if len(issues) == 1:            # work around pyPgSQL 
IN() bug with one-element-tuples
                                issues.append(issues[0])
                        cmd = """
select distinct pk_encounter
from v_patient_items
where pk_health_issue in %s and id_patient = %s"""
                        rows = gmPG.run_ro_query('historica', cmd, None, 
(tuple(issues), self.id_patient))
                        if rows is None:
                                _log.Log(gmLog.lErr, 'cannot load encounters 
for issues [%s] (patient [%s])' % (str(issues), self.id_patient))
                        else:
                                enc_ids = map(lambda x:x[0], rows)
                                filtered_encounters = filter(lambda enc: 
enc['pk_encounter'] in enc_ids, filtered_encounters)
                if episodes is not None and len(episodes) > 0:
                        if len(episodes) == 1:
                                episodes.append(episodes[0])
                        cmd = """
select distinct pk_encounter
from v_patient_items
where pk_episode in %s and id_patient = %s"""
                        rows = gmPG.run_ro_query('historica', cmd, None, 
(tuple(episodes), self.id_patient))
                        if rows is None:
                                _log.Log(gmLog.lErr, 'cannot load encounters 
for episodes [%s] (patient [%s])' % (str(episodes), self.id_patient))
                        else:
                                epi_ids = map(lambda x:x[0], rows)
                                filtered_encounters = filter(lambda enc: 
enc['pk_encounter'] in epi_ids, filtered_encounters)

                return filtered_encounters

        #--------------------------------------------------------               
        def get_first_encounter(self, issue_id=None, episode_id=None):
                """
                        Retrieves first encounter for a particular issue and/or 
episode

                        issue_id - First encounter associated health issue
                        episode - First encounter associated episode
                """
                return  self._get_encounters()[0]

        #--------------------------------------------------------               
        def get_last_encounter(self, issue_id=None, episode_id=None):
                """
                        Retrieves last encounter for a concrete issue and/or 
episode
                        
                        issue_id - Last encounter associated health issue
                        episode_id - Last encounter associated episode
                """
                return self._get_encounters()[-1]

        #--------------------------------------------------------               
        def _get_encounters(self, issue_id=None, episode_id=None):
                h_iss = issue_id
                epis = episode_id
                if issue_id is not None:
                        h_iss = [issue_id]
                if episode_id is not None:
                        epis = [episode_id]
                encounters = self.get_encounters(issues=h_iss, episodes=epis)
                if encounters is None or len(encounters) == 0:
                        episodes = epis
                        issues = h_iss
                        _log.Log(gmLog.lErr, 'cannot retrieve first encounter 
for episodes [%s], issues [%s] (patient ID [%s])' % (str(episodes), 
str(issues), self.id_patient))
                        return [None]
                # FIXME: this does not scale particularly well
                encounters.sort(lambda x,y: cmp(x['started'], y['started']))
                return encounters
        #------------------------------------------------------------------
        # lab data API
        #------------------------------------------------------------------
        def get_lab_results(self, limit=None, since=None, until=None, 
encounters=None, episodes=None, issues=None):
                """Retrieves lab result clinical items.

                limit - maximum number of results to retrieve
                since - initial date
                until - final date
                encounters - list of encounters
                episodes - list of episodes
                issues - list of health issues
                """
                try:
                        return self.__db_cache['lab results']
                except KeyError:
                        pass
                self.__db_cache['lab results'] = []
                if limit is None:
                        lim = ''
                else:
                        # only use limit if all other constraints are None
                        if since is None and until is None and encounters is 
None and episodes is None and issues is None:
                                lim = "limit %s" % limit
                        else:
                                lim = ''

                cmd = """select * from v_results4lab_req where pk_patient=%%s 
%s""" % lim
                rows, idx = gmPG.run_ro_query('historica', cmd, True, 
self.id_patient)
                if rows is None:
                        return False
                for row in rows:
                        lab_row = {
                                'pk_field': 'pk_result',
                                'idx': idx,
                                'data': row
                        }                       
                        try:
                                lab_result = gmPathLab.cLabResult(row=lab_row)
                                self.__db_cache['lab 
results'].append(lab_result)
                        except gmExceptions.ConstructorError:
                                _log.Log('lab result error', sys.exc_info())

                # ok, let's constrain our list
                filtered_lab_results = []
                filtered_lab_results.extend(self.__db_cache['lab results'])
                if since is not None:
                        filtered_lab_results = filter(lambda lres: 
lres['req_when'] >= since, filtered_lab_results)
                if until is not None:
                        filtered_lab_results = filter(lambda lres: 
lres['req_when'] < until, filtered_lab_results)
                if issues is not None:
                        filtered_lab_results = filter(lambda lres: 
lres['pk_health_issue'] in issues, filtered_lab_results)
                if episodes is not None:
                        filtered_lab_results = filter(lambda lres: 
lres['pk_episode'] in episodes, filtered_lab_results)
                if encounters is not None:
                        filtered_lab_results = filter(lambda lres: 
lres['pk_encounter'] in encounters, filtered_lab_results)
                return filtered_lab_results
        #------------------------------------------------------------------
        def get_lab_request(self, pk=None, req_id=None, lab=None):
                # FIXME: verify that it is our patient ? ...
                try:
                        req = gmPathLab.cLabRequest(aPK_obj=pk, req_id=req_id, 
lab=lab)
                except gmExceptions.ConstructorError:
                        _log.LogException('cannot get lab request', 
sys.exc_info())
                        return None
                return req
        #------------------------------------------------------------------
        def add_lab_request(self, lab=None, req_id=None, encounter_id=None, 
episode_id=None):
                if encounter_id is None:
                        encounter_id = self.__encounter['pk_encounter']
                if episode_id is None:
                        episode_id = self.__episode['pk_episode']
                status, data = gmPathLab.create_lab_request(
                        lab=lab,
                        req_id=req_id,
                        pat_id=self.id_patient,
                        encounter_id=encounter_id,
                        episode_id=episode_id
                )
                if not status:
                        _log.Log(gmLog.lErr, str(data))
                        return None
                return data
        #------------------------------------------------------------------
        # unchecked stuff
        #------------------------------------------------------------------
        def store_referral (self, cursor, text, form_id):
                """
                Stores a referral in the clinical narrative
                """
                cmd = """
                insert into referral (
                fk_encounter, fk_episode, narrative, fk_form
                ) values (
                %s, %s, %s, %s
                )
                """
                return gmPG.run_commit (cursor, [(cmd, 
[self.__encounter['pk_encounter'], self.__episode['pk_episode'], text, 
form_id])])

#============================================================
# convenience functions
#------------------------------------------------------------
def set_encounter_ttl(soft = None, hard = None):
        if soft is not None:
                global _encounter_soft_ttl
                _encounter_soft_ttl = soft
        if hard is not None:
                global _encounter_hard_ttl
                _encounter_hard_ttl = hard
#------------------------------------------------------------
def set_func_ask_user(a_func = None):
        if a_func is not None:
                global _func_ask_user
                _func_ask_user = a_func
#------------------------------------------------------------
# main
#------------------------------------------------------------
if __name__ == "__main__":
        import traceback
        gmLog.gmDefLog.SetAllLogLevels(gmLog.lData)
        gmPG.set_default_client_encoding('latin1')
        try:
                emr = cClinicalRecord(aPKey = 12)

                # Vacc regimes
                vacc_regimes = 
emr.get_scheduled_vaccination_regimes(indications = ['tetanus'])
                print '\nVaccination regimes: '
                for a_regime in vacc_regimes:
                        pass
                        #print a_regime
                vacc_regime = emr.get_scheduled_vaccination_regimes(ID=10)      
                
                #print vacc_regime
                
                # vaccination regimes and vaccinations for regimes
                scheduled_vaccs = emr.get_scheduled_vaccinations(indications = 
['tetanus'])
                print 'Vaccinations for the regime:'
                for a_scheduled_vacc in scheduled_vaccs:
                        pass
                        #print '   %s' %(a_scheduled_vacc)

                # vaccination next shot and booster
                vaccinations = emr.get_vaccinations()
                for a_vacc in vaccinations:
                        print '\nVaccination %s , date: %s, booster: %s, seq 
no: %s' %(a_vacc['batch_no'], a_vacc['date'].Format('%Y-%m-%d'), 
a_vacc['is_booster'], a_vacc['seq_no'])

                # first and last encounters
                first_encounter = emr.get_first_encounter(issue_id = 1)
                print '\nFirst encounter: ' + str(first_encounter)
                last_encounter = emr.get_last_encounter(episode_id = 1)
                print '\nLast encounter: ' + str(last_encounter)
                print ''
                
                # lab results
                lab = emr.get_lab_results()
                lab_file = open('lab-data.txt', 'wb')
                for lab_result in lab:
                        lab_file.write(str(lab_result))
                        lab_file.write('\n')
                lab_file.close()
                
                # soap notes
                narrative = emr.get_clin_narrative(
                        since = mxDT.DateTime(2000, 2, 18),
                        until = mxDT.DateTime(2010, 9, 18),
                        issues = [1],
                        episodes = [1],
                        encounters = [1,2],
                        soap_cats = ['s', 'p']
                )
                print '# of clinical notes:', str(len(narrative))
                for a_narr in narrative:
                        print '%s - %s - %s - %s - %s - %s\n' % (
                                a_narr['date'].Format('%Y-%m-%d'),
                                str(a_narr['pk_health_issue']),
                                str(a_narr['pk_episode']),
                                str(a_narr['pk_encounter']),
                                a_narr['soap_cat'],
                                a_narr['narrative']
                        )
                         
        #       dump = record.get_missing_vaccinations()
        #       f = open('vaccs.lst', 'wb')
        #       if dump is not None:
        #               print "=== due ==="
        #               f.write("=== due ===\n")
        #               for row in dump['due']:
        #                       print row
        #                       f.write(repr(row))
        #                       f.write('\n')
        #               print "=== overdue ==="
        #               f.write("=== overdue ===\n")
        #               for row in dump['overdue']:
        #                       print row
        #                       f.write(repr(row))
        #                       f.write('\n')
        #       f.close()
        except:
                traceback.print_exc(file=sys.stdout)
                _log.LogException('unhandled exception', sys.exc_info(), 
verbose=1)
        gmPG.ConnectionPool().StopListeners()
#============================================================
# $Log: gmClinicalRecord.py,v $
# Revision 1.150  2004/10/27 12:09:28  ncq
# - properly set booster/seq_no in the face of the patient
#   not being on any vaccination schedule
#
# Revision 1.149  2004/10/26 12:51:24  ncq
# - Carlos: bulk load lab results
#
# Revision 1.148  2004/10/20 21:50:29  ncq
# - return [] on no vacc regimes found
# - in get_vaccinations() handle case where patient is not on any schedule
#
# Revision 1.147  2004/10/20 12:28:25  ncq
# - revert back to Carlos' bulk loading code
# - keep some bits of what Syan added
# - he likes to force overwriting other peoples' commits
# - if that continues his CVS rights are at stake (to be discussed
#   on list when appropriate)
#
# Revision 1.144  2004/10/18 11:33:48  ncq
# - more bulk loading
#
# Revision 1.143  2004/10/12 18:39:12  ncq
# - first cut at using Carlos' bulk fetcher in get_vaccinations(),
#   seems to work fine so far ... please test and report lossage ...
#
# Revision 1.142  2004/10/12 11:14:51  ncq
# - improve get_scheduled_vaccination_regimes/get_vaccinations, mostly by Carlos
#
# Revision 1.141  2004/10/11 19:50:15  ncq
# - improve get_allergies()
#
# Revision 1.140  2004/09/28 12:19:15  ncq
# - any vaccination related data now cached under 'vaccinations' so
#   all of it is flushed when any change to vaccinations occurs
# - rewrite get_scheduled_vaccination_regimes() (Carlos)
# - in get_vaccinations() compute seq_no and is_booster status
#
# Revision 1.139  2004/09/19 15:07:01  ncq
# - we don't use a default health issue anymore
# - remove duplicate existence checks
# - cleanup, reformat/fix episode queries
#
# Revision 1.138  2004/09/13 19:07:00  ncq
# - get_scheduled_vaccination_regimes() returns list of lists
#   (indication, l10n_indication, nr_of_shots) - this allows to
#   easily build table of given/missing vaccinations
#
# Revision 1.137  2004/09/06 18:54:31  ncq
# - some cleanup
# - in get_first/last_encounter we do need to check issue/episode for None so
#   we won't be applying the "one-item -> two-duplicate-items for IN query" 
trick
#   to "None" which would yield the wrong results
#
# Revision 1.136  2004/08/31 19:19:43  ncq
# - Carlos added constraints to get_encounters()
# - he also added get_first/last_encounter()
#
# Revision 1.135  2004/08/23 09:07:58  ncq
# - removed unneeded get_vaccinated_regimes() - was faulty anyways
#
# Revision 1.134  2004/08/11 09:44:15  ncq
# - gracefully continue loading clin_narrative items if one fails
# - map soap_cats filter to lowercase in get_clin_narrative()
#
# Revision 1.133  2004/08/11 09:01:27  ncq
# - Carlos-contributed get_clin_narrative() with usual filtering
#   and soap_cat filtering based on v_pat_narrative
#
# Revision 1.132  2004/07/17 21:08:51  ncq
# - gmPG.run_query() now has a verbosity parameter, so use it
#
# Revision 1.131  2004/07/06 00:11:11  ncq
# - make add_clin_narrative use gmClinNarrative.create_clin_narrative()
#
# Revision 1.130  2004/07/05 22:30:01  ncq
# - improve get_text_dump()
#
# Revision 1.129  2004/07/05 22:23:38  ncq
# - log some timings to find time sinks
# - get_clinical_record now takes between 4 and 11 seconds
# - record active episode at clinical record *cleanup* instead of startup !
#
# Revision 1.128  2004/07/02 00:20:54  ncq
# - v_patient_items.id_item -> pk_item
#
# Revision 1.127  2004/06/30 20:33:40  ncq
# - add_clinical_note() -> add_clin_narrative()
#
# Revision 1.126  2004/06/30 15:31:22  shilbert
# - fk/pk issue fixed
#
# Revision 1.125  2004/06/28 16:05:42  ncq
# - fix spurious 'id' for episode -> pk_episode
#
# Revision 1.124  2004/06/28 12:18:41  ncq
# - more id_* -> fk_*
#
# Revision 1.123  2004/06/26 23:45:50  ncq
# - cleanup, id_* -> fk/pk_*
#
# Revision 1.122  2004/06/26 07:33:55  ncq
# - id_episode -> fk/pk_episode
#
# Revision 1.121  2004/06/20 18:39:30  ncq
# - get_encounters() added by Carlos
#
# Revision 1.120  2004/06/17 21:30:53  ncq
# - time range constraints in get()ters, by Carlos
#
# Revision 1.119  2004/06/15 19:08:15  ncq
# - self._backend -> self._conn_pool
# - remove instance level self._ro_conn_clin
# - cleanup
#
# Revision 1.118  2004/06/14 06:36:51  ncq
# - fix = -> == in filter(lambda ...)
#
# Revision 1.117  2004/06/13 08:03:07  ncq
# - cleanup, better separate vaccination code from general EMR code
#
# Revision 1.116  2004/06/13 07:55:00  ncq
# - create_allergy moved to gmAllergy
# - get_indications moved to gmVaccinations
# - many get_*()ers constrained by issue/episode/encounter
# - code by Carlos
#
# Revision 1.115  2004/06/09 14:33:31  ncq
# - cleanup
# - rewrite _db_callback_allg_modified()
#
# Revision 1.114  2004/06/08 00:43:26  ncq
# - add staff_id to add_allergy, unearthed by unittest
#
# Revision 1.113  2004/06/02 22:18:14  ncq
# - fix my broken streamlining
#
# Revision 1.112  2004/06/02 22:11:47  ncq
# - streamline Syan's check for failing create_episode() in 
__load_last_active_episode()
#
# Revision 1.111  2004/06/02 13:10:18  sjtan
#
# error handling , in case unsuccessful get_episodes.
#
# Revision 1.110  2004/06/01 23:51:33  ncq
# - id_episode/pk_encounter
#
# Revision 1.109  2004/06/01 08:21:56  ncq
# - default limit to all on get_lab_results()
#
# Revision 1.108  2004/06/01 08:20:14  ncq
# - limit in get_lab_results
#
# Revision 1.107  2004/05/30 20:51:34  ncq
# - verify provider in __init__, too
#
# Revision 1.106  2004/05/30 19:54:57  ncq
# - comment out attach_to_encounter(), actually, all relevant
#   methods should have encounter_id, episode_id kwds that
#   default to self.__*['id']
# - add_allergy()
#
# Revision 1.105  2004/05/28 15:46:28  ncq
# - get_active_episode()
#
# Revision 1.104  2004/05/28 15:11:32  ncq
# - get_active_encounter()
#
# Revision 1.103  2004/05/27 13:40:21  ihaywood
# more work on referrals, still not there yet
#
# Revision 1.102  2004/05/24 21:13:33  ncq
# - return better from add_lab_request()
#
# Revision 1.101  2004/05/24 20:52:18  ncq
# - add_lab_request()
#
# Revision 1.100  2004/05/23 12:28:58  ncq
# - fix missing : and episode reference before assignment
#
# Revision 1.99  2004/05/22 12:42:53  ncq
# - add create_episode()
# - cleanup add_episode()
#
# Revision 1.98  2004/05/22 11:46:15  ncq
# - some cleanup re allergy signal handling
# - get_health_issue_names doesn't exist anymore, so use get_health_issues
#
# Revision 1.97  2004/05/18 22:35:09  ncq
# - readd set_active_episode()
#
# Revision 1.96  2004/05/18 20:33:40  ncq
# - fix call to create_encounter() in __initiate_active_encounter()
#
# Revision 1.95  2004/05/17 19:01:40  ncq
# - convert encounter API to use encounter class
#
# Revision 1.94  2004/05/16 15:47:27  ncq
# - switch to use of episode class
#
# Revision 1.93  2004/05/16 14:34:45  ncq
# - cleanup, small fix in patient xdb checking
# - switch health issue handling to clin item class
#
# Revision 1.92  2004/05/14 13:16:34  ncq
# - cleanup, remove dead code
#
# Revision 1.91  2004/05/12 14:33:42  ncq
# - get_due_vaccinations -> get_missing_vaccinations + rewrite
#   thereof for value object use
# - __activate_fairly_recent_encounter now fails right away if it
#   cannot talk to the user anyways
#
# Revision 1.90  2004/05/08 17:41:34  ncq
# - due vaccs views are better now, so simplify get_due_vaccinations()
#
# Revision 1.89  2004/05/02 19:27:30  ncq
# - simplify get_due_vaccinations
#
# Revision 1.88  2004/04/24 12:59:17  ncq
# - all shiny and new, vastly improved vaccinations
#   handling via clinical item objects
# - mainly thanks to Carlos Moro
#
# Revision 1.87  2004/04/20 12:56:58  ncq
# - remove outdated get_due_vaccs(), use get_due_vaccinations() now
#
# Revision 1.86  2004/04/20 00:17:55  ncq
# - allergies API revamped, kudos to Carlos
#
# Revision 1.85  2004/04/17 11:54:16  ncq
# - v_patient_episodes -> v_pat_episodes
#
# Revision 1.84  2004/04/15 09:46:56  ncq
# - cleanup, get_lab_data -> get_lab_results
#
# Revision 1.83  2004/04/14 21:06:10  ncq
# - return cLabResult from get_lab_data()
#
# Revision 1.82  2004/03/27 22:18:43  ncq
# - 7.4 doesn't allow aggregates in subselects which refer to outer-query
#   columns only, therefor use explicit inner query from list
#
# Revision 1.81  2004/03/25 11:00:19  ncq
# test get_lab_data()
#
# Revision 1.80  2004/03/23 17:32:59  ncq
# - support "unified" test code/name on get_lab_data()
#
# Revision 1.79  2004/03/23 15:04:59  ncq
# - merge Carlos' constraints into get_text_dump
# - add gmPatient.export_data()
#
# Revision 1.78  2004/03/23 02:29:24  ncq
# - cleanup import/add pyCompat
# - get_lab_data()
# - unit test
#
# Revision 1.77  2004/03/20 19:41:59  ncq
# - gmClin* cClin*
#
# Revision 1.76  2004/03/19 11:55:38  ncq
# - in allergy.reaction -> allergy.narrative
#
# Revision 1.75  2004/03/04 19:35:01  ncq
# - AU has rules on encounter timeout, so use them
#
# Revision 1.74  2004/02/25 09:46:19  ncq
# - import from pycommon now, not python-common
#
# Revision 1.73  2004/02/18 15:25:20  ncq
# - rewrote encounter support
#   - __init__() now initiates encounter
#   - _encounter_soft/hard_ttl now global mx.DateTime.TimeDelta
#   - added set_encounter_ttl()
#   - added set_func_ask_user() for UI callback on "fairly recent"
#     encounter detection
#
# Revision 1.72  2004/02/17 04:04:34  ihaywood
# fixed patient creation refeential integrity error
#
# Revision 1.71  2004/02/14 00:37:10  ihaywood
# Bugfixes
#       - weeks = days / 7
#       - create_new_patient to maintain xlnk_identity in historica
#
# Revision 1.70  2004/02/12 23:39:33  ihaywood
# fixed parse errors on vaccine queries (I'm using postgres 7.3.3)
#
# Revision 1.69  2004/02/02 23:02:40  ncq
# - it's personalia, not demographica
#
# Revision 1.68  2004/02/02 16:19:03  ncq
# - rewrite get_due_vaccinations() taking advantage of indication-based tables
#
# Revision 1.67  2004/01/26 22:08:52  ncq
# - gracefully handle failure to retrive vacc_ind
#
# Revision 1.66  2004/01/26 21:48:48  ncq
# - v_patient_vacc4ind -> v_pat_vacc4ind
#
# Revision 1.65  2004/01/24 17:07:46  ncq
# - fix insertion into xlnk_identity
#
# Revision 1.64  2004/01/21 16:52:02  ncq
# - eventually do the right thing in get_vaccinations()
#
# Revision 1.63  2004/01/21 15:53:05  ncq
# - use deepcopy when copying dict as to leave original intact in 
get_vaccinations()
#
# Revision 1.62  2004/01/19 13:41:15  ncq
# - fix typos in SQL
#
# Revision 1.61  2004/01/19 13:30:46  ncq
# - substantially smarten up __load_last_active_episode() after cleaning it up
#
# Revision 1.60  2004/01/18 21:41:49  ncq
# - get_vaccinated_indications()
# - make get_vaccinations() work against v_patient_vacc4ind
# - don't store vacc_def/link on saving vaccination
# - update_vaccination()
#
# Revision 1.59  2004/01/15 15:05:13  ncq
# - verify patient id in xlnk_identity in _pkey_exists()
# - make set_active_episode() logic more consistent - don't create default 
episode ...
# - also, failing to record most_recently_used episode should prevent us
#   from still keeping things up
#
# Revision 1.58  2004/01/12 16:20:14  ncq
# - set_active_episode() does not read rows from run_commit()
# - need to check for argument aVacc keys *before* adding
#   corresponding snippets to where/cols clause else we would end
#   up with orphaned query parts
# - also, aVacc will always have all keys, it's just that they may
#   be empty (because editarea.GetValue() will always return something)
# - fix set_active_encounter: don't ask for column index
#
# Revision 1.57  2004/01/06 23:44:40  ncq
# - __default__ -> xxxDEFAULTxxx
#
# Revision 1.56  2004/01/06 09:56:41  ncq
# - default encounter name __default__ is nonsense, of course,
#   use mxDateTime.today().Format() instead
# - consolidate API:
#   - load_most_recent_episode() -> load_last_active_episode()
#   - _get_* -> get_*
#   - sort methods
# - convert more gmPG.run_query()
# - handle health issue on episode change as they are tighthly coupled
#
# Revision 1.55  2003/12/29 16:13:51  uid66147
# - listen to vaccination changes in DB
# - allow filtering by ID in get_vaccinations()
# - order get_due_vacc() by time_left/amount_overdue
# - add add_vaccination()
# - deal with provider in encounter handling
#
# Revision 1.54  2003/12/02 01:58:28  ncq
# - make get_due_vaccinations return the right thing on empty lists
#
# Revision 1.53  2003/12/01 01:01:05  ncq
# - add get_vaccinated_regimes()
# - allow regime_list filter in get_vaccinations()
# - handle empty lists better in get_due_vaccinations()
#
# Revision 1.52  2003/11/30 01:05:30  ncq
# - improve get_vaccinations
# - added get_vacc_regimes
#
# Revision 1.51  2003/11/28 10:06:18  ncq
# - remove dead code
#
# Revision 1.50  2003/11/28 08:08:05  ncq
# - improve get_due_vaccinations()
#
# Revision 1.49  2003/11/19 23:27:44  sjtan
#
# make _print()  a dummy function , so that  code reaching gmLog through this 
function works;
#
# Revision 1.48  2003/11/18 14:16:41  ncq
# - cleanup
# - intentionally comment out some methods and remove some code that
#   isn't fit for the main trunk such that it breaks and gets fixed
#   eventually
#
# Revision 1.47  2003/11/17 11:34:22  sjtan
#
# no ref to yaml
#
# Revision 1.46  2003/11/17 11:32:46  sjtan
#
# print ... -> _log.Log(gmLog.lInfo ...)
#
# Revision 1.45  2003/11/17 10:56:33  sjtan
#
# synced and commiting.
#
#
#
# uses gmDispatcher to send new currentPatient objects to toplevel gmGP_ 
widgets. Proprosal to use
# yaml serializer to store editarea data in  narrative text field of 
clin_root_item until
# clin_root_item schema stabilizes.
#
# Revision 1.44  2003/11/16 19:30:26  ncq
# - use clin_when in clin_root_item
# - pretty _print(EMR text dump)
#
# Revision 1.43  2003/11/11 20:28:59  ncq
# - get_allergy_names(), reimplemented
#
# Revision 1.42  2003/11/11 18:20:58  ncq
# - fix get_text_dump() to actually do what it suggests
#
# Revision 1.41  2003/11/09 22:51:29  ncq
# - don't close cursor prematurely in get_text_dump()
#
# Revision 1.40  2003/11/09 16:24:03  ncq
# - typo fix
#
# Revision 1.39  2003/11/09 03:29:11  ncq
# - API cleanup, __set/getitem__ deprecated
#
# Revision 1.38  2003/10/31 23:18:48  ncq
# - improve encounter business
#
# Revision 1.37  2003/10/26 11:27:10  ihaywood
# gmPatient is now the "patient stub", all demographics stuff in gmDemographics.
#
# manual edit areas modelled after r.terry's specs.
#
# Revision 1.36  2003/10/19 12:12:36  ncq
# - remove obsolete code
# - filter out sensitivities on get_allergy_names
# - start get_vacc_status
#
# Revision 1.35  2003/09/30 19:11:58  ncq
# - remove dead code
#
# Revision 1.34  2003/07/19 20:17:23  ncq
# - code cleanup
# - add cleanup()
# - use signals better
# - fix get_text_dump()
#
# Revision 1.33  2003/07/09 16:20:18  ncq
# - remove dead code
# - def_conn_ro -> ro_conn_clin
# - check for patient existence in personalia, not historica
# - listen to health issue changes, too
# - add _get_health_issues
#
# Revision 1.32  2003/07/07 08:34:31  ihaywood
# bugfixes on gmdrugs.sql for postgres 7.3
#
# Revision 1.31  2003/07/05 13:44:12  ncq
# - modify -> modified
#
# Revision 1.30  2003/07/03 15:20:55  ncq
# - lots od cleanup, some nice formatting for text dump of EMR
#
# Revision 1.29  2003/06/27 22:54:29  ncq
# - improved _get_text_dump()
# - added _get_episode/health_issue_names()
# - remove old workaround code
# - sort test output by age, oldest on top
#
# Revision 1.28  2003/06/27 16:03:50  ncq
# - no need for ; in DB-API queries
# - implement EMR text dump
#
# Revision 1.27  2003/06/26 21:24:49  ncq
# - cleanup re quoting + ";" and (cmd, arg) style
#
# Revision 1.26  2003/06/26 06:05:38  ncq
# - always add ; at end of sql queries but have space after %s
#
# Revision 1.25  2003/06/26 02:29:20  ihaywood
# Bugfix for searching for pre-existing health issue records
#
# Revision 1.24  2003/06/24 12:55:08  ncq
# - eventually make create_clinical_note() functional
#
# Revision 1.23  2003/06/23 22:28:22  ncq
# - finish encounter handling for now, somewhat tested
# - use gmPG.run_query changes where appropriate
# - insert_clin_note() finished but untested
# - cleanup
#
# Revision 1.22  2003/06/22 16:17:40  ncq
# - start dealing with encounter initialization
# - add create_clinical_note()
# - cleanup
#
# Revision 1.21  2003/06/19 15:22:57  ncq
# - fix spelling error in SQL in episode creation
#
# Revision 1.20  2003/06/03 14:05:05  ncq
# - start listening threads last in __init__ so we won't hang
#   if anything else fails in the constructor
#
# Revision 1.19  2003/06/03 13:17:32  ncq
# - finish default clinical episode/health issue handling, simple tests work
# - clinical encounter handling still insufficient
# - add some more comments to Syan's code
#
# Revision 1.18  2003/06/02 20:58:32  ncq
# - nearly finished with default episode/health issue stuff
#
# Revision 1.17  2003/06/01 16:25:51  ncq
# - preliminary code for episode handling
#
# Revision 1.16  2003/06/01 15:00:31  sjtan
#
# works with definite, maybe note definate.
#
# Revision 1.15  2003/06/01 14:45:31  sjtan
#
# definite and definate databases catered for, temporarily.
#
# Revision 1.14  2003/06/01 14:34:47  sjtan
#
# hopefully complies with temporary model; not using setData now ( but that did 
work).
# Please leave a working and tested substitute (i.e. select a patient , allergy 
list
# will change; check allergy panel allows update of allergy list), if still
# not satisfied. I need a working model-view connection ; trying to get at least
# a basically database updating version going .
#
# Revision 1.13  2003/06/01 14:15:48  ncq
# - more comments
#
# Revision 1.12  2003/06/01 14:11:52  ncq
# - added some comments
#
# Revision 1.11  2003/06/01 14:07:42  ncq
# - "select into" is an update command, too
#
# Revision 1.10  2003/06/01 13:53:55  ncq
# - typo fixes, cleanup, spelling definate -> definite
# - fix my obsolote handling of patient allergies tables
# - remove obsolete clin_transaction stuff
#
# Revision 1.9  2003/06/01 13:20:32  sjtan
#
# logging to data stream for debugging. Adding DEBUG tags when work out how to 
use vi
# with regular expression groups (maybe never).
#
# Revision 1.8  2003/06/01 12:55:58  sjtan
#
# sql commit may cause PortalClose, whilst connection.commit() doesnt?
#
# Revision 1.7  2003/06/01 01:47:32  sjtan
#
# starting allergy connections.
#
# Revision 1.6  2003/05/17 17:23:43  ncq
# - a little more testing in main()
#
# Revision 1.5  2003/05/05 00:06:32  ncq
# - make allergies work again after EMR rework
#
# Revision 1.4  2003/05/03 14:11:22  ncq
# - make allergy change signalling work properly
#
# Revision 1.3  2003/05/03 00:41:14  ncq
# - fetchall() returns list, not dict, so handle accordingly in "allergy names"
#
# Revision 1.2  2003/05/01 14:59:24  ncq
# - listen on allergy add/delete in backend, invalidate cache and notify 
frontend
# - "allergies", "allergy names" getters
#
# Revision 1.1  2003/04/29 12:33:20  ncq
# - first draft
#

"""GnuMed patient EMR tree browser.
"""
#================================================================
# $Source: /cvsroot/gnumed/gnumed/gnumed/client/wxpython/gmEMRBrowser.py,v $
# $Id: gmEMRBrowser.py,v 1.6 2004/10/31 00:37:13 cfmoro Exp $
__version__ = "$Revision: 1.6 $"
__author__ = "address@hidden"
__license__ = "GPL"

import os.path, sys

from wxPython import wx

from Gnumed.pycommon import gmLog, gmI18N, gmPG, gmDispatcher, gmSignals
from Gnumed.exporters import gmPatientExporter
from Gnumed.business import gmEMRStructItems, gmPatient
from Gnumed.wxpython import gmRegetMixin
from Gnumed.pycommon.gmPyCompat import *

_log = gmLog.gmDefLog
_log.Log(gmLog.lInfo, __version__)
#============================================================
class cEMRBrowserPanel(wx.wxPanel, gmRegetMixin.cRegetOnPaintMixin):

        def __init__(self, parent, id):
                """
                Contructs a new instance of EMR browser panel

                parent - Wx parent widget
                id - Wx widget id
                """
                # Call parents constructors
                wx.wxPanel.__init__ (
                        self,
                        parent,
                        id,
                        wx.wxPyDefaultPosition,
                        wx.wxPyDefaultSize,
                        wx.wxNO_BORDER
                )
                gmRegetMixin.cRegetOnPaintMixin.__init__(self)

                self.__pat = gmPatient.gmCurrentPatient()
                self.__exporter = gmPatientExporter.cEmrExport(patient = 
self.__pat)

                self.__do_layout()
                self.__register_interests()
                self.__reset_ui_content()
                
                self.__init_popup()

        #--------------------------------------------------------
        def __do_layout(self):
                """
                Arranges EMR browser layout
                """
                # splitter window
                self.__tree_narr_splitter = wx.wxSplitterWindow(self, -1)
                # emr tree
                self.__emr_tree = wx.wxTreeCtrl (
                        self.__tree_narr_splitter,
                        -1,
                        style=wx.wxTR_HAS_BUTTONS | wx.wxNO_BORDER
                )
                # narrative details text control
                self.__narr_TextCtrl = wx.wxTextCtrl (
                        self.__tree_narr_splitter,
                        -1,
                        style=wx.wxTE_MULTILINE | wx.wxTE_READONLY | 
wx.wxTE_DONTWRAP
                )
                # set up splitter
                # FIXME: read/save value from/into backend
                self.__tree_narr_splitter.SetMinimumPaneSize(20)
                self.__tree_narr_splitter.SplitVertically(self.__emr_tree, 
self.__narr_TextCtrl)

                self.__szr_main = wx.wxBoxSizer(wx.wxVERTICAL)
                self.__szr_main.Add(self.__tree_narr_splitter, 1, wx.wxEXPAND, 
0)

                self.SetAutoLayout(1)
                self.SetSizer(self.__szr_main)
                self.__szr_main.Fit(self)
                self.__szr_main.SetSizeHints(self)
        #--------------------------------------------------------
        # event handling
        #--------------------------------------------------------
        def __register_interests(self):
                """
                Configures enabled event signals
                """
                # wx.wxPython events
                wx.EVT_TREE_SEL_CHANGED(self.__emr_tree, 
self.__emr_tree.GetId(), self._on_tree_item_selected)
                # client internal signals
                gmDispatcher.connect(signal=gmSignals.patient_selected(), 
receiver=self._on_patient_selected)
        #--------------------------------------------------------
        def _on_patient_selected(self):
                """Patient changed."""
                self.__exporter.set_patient(self.__pat)
                self._schedule_data_reget()
        #--------------------------------------------------------
        def _on_tree_item_selected(self, event):
                """
                Displays information for a selected tree node
                """
                sel_item = event.GetItem()
                sel_item_obj = self.__emr_tree.GetPyData(sel_item)
        
                if(isinstance(sel_item_obj, gmEMRStructItems.cEncounter)):
                        header = _('Encounter\n=========\n\n')
                        epi = 
self.__emr_tree.GetPyData(self.__emr_tree.GetItemParent(sel_item))
                        txt = self.__exporter.dump_encounter_info(episode=epi, 
encounter=sel_item_obj)

                elif (isinstance(sel_item_obj, gmEMRStructItems.cEpisode)):
                        header = _('Episode\n=======\n\n')
                        txt = 
self.__exporter.dump_episode_info(episode=sel_item_obj)

                elif (isinstance(sel_item_obj, gmEMRStructItems.cHealthIssue)):
                        header = _('Health Issue\n============\n\n')
                        txt = 
self.__exporter.dump_issue_info(issue=sel_item_obj)

                else:
                        header = _('Summary\n=======\n\n')
                        txt = self.__exporter.dump_summary_info()

                self.__narr_TextCtrl.Clear()
                self.__narr_TextCtrl.WriteText(header)
                self.__narr_TextCtrl.WriteText(txt)
                
                        
                self.popup.SetPopupContext(sel_item)
                 
                
        #--------------------------------------------------------
        # reget mixin API
        #--------------------------------------------------------
        def _populate_with_data(self):
                """
                Fills UI with data.
                """
                self.__reset_ui_content()
                if self.refresh_tree():
                        return True
                return False
                
        #--------------------------------------------------------
        # public API
        #--------------------------------------------------------               
        def refresh_tree(self):
                """
                Updates EMR browser data
                """
                # EMR tree root item
                demos = self.__pat.get_demographic_record()
                names = demos.get_names()
                root_item = self.__emr_tree.AddRoot(_('%s %s EMR') % 
(names['first'], names['last']))

                # Obtain all the tree from exporter
                self.__exporter.get_historical_tree(self.__emr_tree)

                # Expand root node and display patient summary info
                self.__emr_tree.Expand(root_item)
                self.__narr_TextCtrl.WriteText(_('Summary\n=======\n\n'))
                
self.__narr_TextCtrl.WriteText(self.__exporter.dump_summary_info(0))

                # Set sash position
                
self.__tree_narr_splitter.SetSashPosition(self.__tree_narr_splitter.GetSizeTuple()[0]/3,
 True)

                # FIXME: error handling
                return True

        #--------------------------------------------------------
        # internal API
        #--------------------------------------------------------
        def __reset_ui_content(self):
                """
                Clear all information displayed in browser (tree and details 
area)
                """
                self.__emr_tree.DeleteAllItems()
                self.__narr_TextCtrl.Clear()
        #--------------------------------------------------------
#       def set_patient(self, patient):
#               """
#               Configures EMR browser patient and instantiates exporter.
#               Appropiate for standalaone use.
#               patient - The patient to display EMR for
#               """
#               self.__patient = patient
#               self.__exporter.set_patient(patient)

        def get_emr_tree(self):
                return self.__emr_tree  

        def get_EMR_item(self, selected_tree_item):
                return self.__emr_tree.GetPyData(selected_tree_item)
                
        def get_parent_EMR_item(self, selected_tree_item):
                return  
self.__emr_tree.GetPyData(self.__emr_tree.GetItemParent(selected_tree_item))
                
        def repopulate(self):
                self._populate_with_data()      

#------------POPUP methods -------------------------------
        def __init_popup(self):
                """
                initializes the popup for the tree
                """
                self.popup=gmPopupMenuEMRBrowser(self)
                wx.EVT_RIGHT_DOWN(self.__emr_tree, self.__show_popup)
         

                
        def __show_popup(self, event):
                 self.PopupMenu(self.popup, (event.GetX(), event.GetY() ))

#== Module convenience functions (for standalone use) =======================
def prompted_input(prompt, default=None):
        """
        Obtains entry from standard input
        
        promp - Promt text to display in standard output
        default - Default value (for user to press only intro)
        """
        usr_input = raw_input(prompt)
        if usr_input == '':
                return default
        return usr_input
#------------------------------------------------------------                   
         
def askForPatient():
        """
                Main module application patient selection function.
        """
        
        # Variable initializations
        pat_searcher = gmPatient.cPatientSearcher_SQL()

        # Ask patient to dump and set in exporter object
        patient_term = prompted_input("\nPatient search term (or 'bye' to exit) 
(eg. Kirk): ")
        if patient_term == 'bye':
                return None
        search_ids = pat_searcher.get_patient_ids(search_term = patient_term)
        if search_ids is None or len(search_ids) == 0:
                prompted_input("No patient matches the query term. Press any 
key to continue.")
                return None
        elif len(search_ids) > 1:
                prompted_input("Various patients match the query term. Press 
any key to continue.")
                return None
        patient_id = search_ids[0]
        patient = gmPatient.gmCurrentPatient(patient_id)
        return patient


class gmPopupMenuEMRBrowser(wx.wxMenu):
        """
        popup menu for updating the EMR tree.
        """
        def __init__(self , browser):
                wx.wxMenu.__init__(self)
                self.ID_NEW_ENCOUNTER=1 
                self.ID_NEW_HEALTH_ISSUE=2
                self.ID_NEW_EPISODE=3
                self.__browser = browser
                self.__mediator = NarrativeTreeItemMediator1(browser)
                wx.EVT_MENU(self.__browser, self.ID_NEW_HEALTH_ISSUE , 
self.__mediator.new_health_issue)
                wx.EVT_MENU(self.__browser, self.ID_NEW_EPISODE , 
self.__mediator.new_episode)
                
        def Clear(self):
                for item in self.GetMenuItems():
                        self.Remove(item.GetId())
                                
        def SetPopupContext( self, sel_item):
        
                self.Clear()
                
                sel_item_obj = self.__browser.get_EMR_item(sel_item)
                
                if(isinstance(sel_item_obj, gmEMRStructItems.cEncounter)):
                        header = _('Encounter\n=========\n\n')
                        
                        
self.__append_new_encounter_menuitem(episode=self.__browser.get_parent_EMR_item(sel_item)
 )
                        
                        
                elif (isinstance(sel_item_obj, gmEMRStructItems.cEpisode)):
                        header = _('Episode\n=======\n\n')
                        
                        
self.__append_new_encounter_menuitem(episode=self.__browser.get_EMR_item(sel_item)
 )
                        
                        
self.__append_new_episode_menuitem(health_issue=self.__browser.get_parent_EMR_item(sel_item))
                        
                        
                elif (isinstance(sel_item_obj, gmEMRStructItems.cHealthIssue)):
                        header = _('Health Issue\n============\n\n')
                        
                        
self.__append_new_episode_menuitem(health_issue=self.__browser.get_EMR_item(sel_item))
                        
                        self.Append(self.ID_NEW_HEALTH_ISSUE, "New Health 
Issue")
                        

                else:
                        header = _('Summary\n=======\n\n')
                        self.Append(self.ID_NEW_HEALTH_ISSUE, "New Health 
Issue")
                        

        def __append_new_encounter_menuitem(self, episode):
                self.Append(self.ID_NEW_ENCOUNTER, "New Encounter (of episode 
'%s')" % episode['description'] )
                
        def __append_new_episode_menuitem(self, health_issue):
                self.Append(self.ID_NEW_EPISODE, "New Episode(of health issue 
'%s')" % health_issue['description'] )

        
                
class NarrativeTreeItemMediator1:
        """
        handler for popup menu actions.
        Handles the unchanged new item problem , where no tree events are 
fired, by listening
        on the edit control events.
        """
        def __init__(self, browser):
                
                self.__browser = browser
                wx.EVT_TREE_END_LABEL_EDIT(self.get_emr_tree(), 
self.get_emr_tree().GetId(), self.__end_label_edit)
                
                self.HEALTH_ISSUE_START_LABEL="NEW HEALTH ISSUE"
                self.EPISODE_START_LABEL="NEW EPISODE"
                
        def get_browser(self):
                return self.__browser
        
                
        def get_emr_tree(self):
                return self.__browser.get_emr_tree()    
                
        def new_health_issue(self, menu_event):
                """
                entry from MenuItem New Health Issue
                """
                self.start_edit_root_node( self.HEALTH_ISSUE_START_LABEL)
        
        def new_episode(self, menu_event):
                """
                entry from MenuItem New Episode Issue
                """
                self.start_edit_child_node( self.EPISODE_START_LABEL)   
                
        def start_edit_child_node(self, start_edit_text):
                root_node = self.get_emr_tree().GetSelection()
                if start_edit_text == self.EPISODE_START_LABEL and \
                isinstance(self.get_browser().get_EMR_item(root_node), 
gmEMRStructItems.cEpisode):
                        root_node = self.get_emr_tree().GetItemParent(root_node)
                
                self.start_edit_node( root_node, start_edit_text)
                
        def start_edit_root_node(self, start_edit_text ):
                """
                this handles the problem of no event fired if label unchanged.
                By detecting for the return key on the edit control, the start 
text 
                can be compared, and if no change, the node is deleted
                in the __key_down handler
                """
                root_node = self.get_emr_tree().GetRootItem()
                self.start_edit_node( root_node, start_edit_text)
        
                        
        def start_edit_node( self, root_node, start_edit_text):
                node= self.get_emr_tree().AppendItem(root_node, start_edit_text)
                self.get_emr_tree().EnsureVisible(node)
                self.get_emr_tree().EditLabel(node)
                self.edit_control = self.get_emr_tree().GetEditControl()
                print self.edit_control
                wx.EVT_KEY_DOWN( self.edit_control,  self.__key_down)
                wx.EVT_KILL_FOCUS(self.edit_control, self.__kill_focus)
                self.start_edit_text = start_edit_text
                self.edit_node = node
                
        def __end_label_edit(self, tree_event):
                """
                check to see if editing cancelled , and if not, then do update 
for each kind of label
                """
                print "end label edit Handled" 
                print "tree_event is ", tree_event
                print "after ", tree_event.__dict__
                print "label is ", tree_event.GetLabel()
                 
                        
                if tree_event.IsEditCancelled() or 
len(tree_event.GetLabel().strip()) == 0:
                        tree_event.Skip()
                        self.__item_to_delete = tree_event.GetItem()
                        wx.wxCallAfter(self.__delete_item)
                else:
                        if self.start_edit_text == 
self.HEALTH_ISSUE_START_LABEL:
                                
wx.wxCallAfter(self.__add_new_health_issue_to_record)
                        
                        elif self.start_edit_text == self.EPISODE_START_LABEL:
                                
wx.wxCallAfter(self.__add_new_episode_to_record)        
                        
        def __key_down(self, event):
                """
                this event on the EditControl needs to be handled because
                1) pressing enter whilst no change in label , does not fire any 
END_LABEL event, so tentative 
                tree items cannot be deleted.
                
                 .
                """
                print "Item is ", event.GetKeyCode()
                event.Skip()
                
                if event.GetKeyCode() == wx.WXK_RETURN:
                        self.__check_for_unchanged_item()
                        
        
                                
        def __kill_focus(self, event):
                print "kill focuse"
                
                self.__check_for_unchanged_item()
                event.Skip()
        
        def __check_for_unchanged_item(self):   
                        text = self.edit_control.GetValue().strip() 
                        print "Text was ", text
                        print "Text unchanged ", text == self.start_edit_text
                        
                        #if the text is unchanged, then delete the new node.
                        if text == self.start_edit_text:
                                self.__item_to_delete = self.edit_node
                                wx.wxCallAfter(self.__delete_item)      
                
        def __delete_item(self):
                self.get_emr_tree().Delete(self.__item_to_delete)
        
        def __add_new_health_issue_to_record(self):
                print "add new health issue to record"
                pat = gmPatient.gmCurrentPatient()
                rec = pat.get_clinical_record()
                issue = rec.add_health_issue( 
self.get_emr_tree().GetItemText(self.edit_node).strip() )
                if not issue is None and isinstance(issue, 
gmEMRStructItems.cHealthIssue):
                        self.get_emr_tree().SetPyData( self.edit_node, issue)
                        
                 
                
        def __add_new_episode_to_record(self):
                print "add new episode to record"
                pat = gmPatient.gmCurrentPatient()
                rec = pat.get_clinical_record()
                print "health_issue pk = ", 
self.get_browser().get_parent_EMR_item(self.edit_node).pk_obj
                print "text = ", 
self.get_emr_tree().GetItemText(self.edit_node).strip()
                
                episode = rec.add_episode( 
self.get_emr_tree().GetItemText(self.edit_node).strip(), 
self.get_browser().get_parent_EMR_item(self.edit_node).pk_obj )
                 
                if not episode is None and isinstance(episode, 
gmEMRStructItems.cEpisode):
                        self.get_emr_tree().SetPyData( self.edit_node, episode) 
                
#================================================================
# MAIN
#----------------------------------------------------------------
if __name__ == '__main__':

        from Gnumed.pycommon import gmCfg

        _log.SetAllLogLevels(gmLog.lData)
        _log.Log (gmLog.lInfo, "starting emr browser...")

        _cfg = gmCfg.gmDefCfgFile        
        if _cfg is None:
                _log.Log(gmLog.lErr, "Cannot run without config file.")
                sys.exit("Cannot run without config file.")

        try:
                # make sure we have a db connection
                gmPG.set_default_client_encoding('latin1')
                pool = gmPG.ConnectionPool()
                
                # obtain patient
                patient = askForPatient()
                if patient is None:
                        print "No patient. Exiting gracefully..."
                        sys.exit(0)

                # display standalone browser
                application = wx.wxPyWidgetTester(size=(800,600))
                emr_browser = cEMRBrowserPanel(application.frame, -1)
#               emr_browser.set_patient(patient)                
                emr_browser.refresh_tree()
                
                application.frame.Show(True)
                application.MainLoop()
                
                # clean up
                if patient is not None:
                        try:
                                patient.cleanup()
                        except:
                                print "error cleaning up patient"
        except StandardError:
                _log.LogException("unhandled exception caught !", 
sys.exc_info(), 1)
                # but re-raise them
                raise
        try:
                pool.StopListeners()
        except:
                _log.LogException('unhandled exception caught', sys.exc_info(), 
verbose=1)
                raise

        _log.Log (gmLog.lInfo, "closing emr browser...")

#================================================================
# $Log: gmEMRBrowser.py,v $
# Revision 1.6  2004/10/31 00:37:13  cfmoro
# Fixed some method names. Refresh function made public for easy reload, eg. 
standalone. Refresh browser at startup in standalone mode
#
# Revision 1.5  2004/09/06 18:57:27  ncq
# - Carlos pluginized the lot ! :-)
# - plus some fixes/tabified it
#
# Revision 1.4  2004/09/01 22:01:45      ncq
# - actually use Carlos' issue/episode summary code
#
# Revision 1.3  2004/08/11 09:46:24      ncq
# - now that EMR exporter supports SOAP notes - display them
#
# Revision 1.2  2004/07/26 00:09:27      ncq
# - Carlos brings us data display for the encounters - can REALLY browse EMR 
now !
#
# Revision 1.1  2004/07/21 12:30:25      ncq
# - initial checkin
#


reply via email to

[Prev in Thread] Current Thread [Next in Thread]