[Top][All Lists]
[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
#
- [Gnumed-devel] updateable emrBrowser,
catmat <=