[Top][All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Gnumed-devel] new gmDemographicRecord
From: |
Ian Haywood |
Subject: |
[Gnumed-devel] new gmDemographicRecord |
Date: |
Wed, 15 Dec 2004 14:39:25 +1100 |
User-agent: |
Mozilla Thunderbird 0.8 (X11/20041012) |
I have rewritten a lot of gmDemographicRecord, this is to improve speed
by caching and using few big queries, also it makes the interface look
more like cClinItem.
This is not fully tested, I just want comments on the general design.
Ian
"""GnuMed demographics object.
This is a patient object intended to let a useful client-side
API crystallize from actual use in true XP fashion.
license: GPL
"""
#============================================================
# $Source:
/cvsroot/gnumed/gnumed/gnumed/client/business/gmDemographicRecord.py,v $
# $Id: gmDemographicRecord.py,v 1.54 2004/08/18 09:05:07 ncq Exp $
__version__ = "$Revision: 1.54 $"
__author__ = "K.Hilbert <address@hidden>, I.Haywood"
# access our modules
import sys, os.path, time, string
from Gnumed.pycommon import gmLog, gmExceptions, gmPG, gmSignals, gmDispatcher,
gmMatchProvider, gmI18N
from Gnumed.business import gmMedDoc
from Gnumed.pycommon.gmPyCompat import *
_log = gmLog.gmDefLog
_log.Log(gmLog.lInfo, __version__)
# 3rd party
import mx.DateTime as mxDT
#============================================================
# map gender abbreviations in a GnuMed demographic service
# to a meaningful localised string
map_gender_gm2long = {
'm': _('male'),
'f': _('female'),
'tf': _('transsexual, female phenotype'),
'tm': _('transsexual, male phenotype')
}
#============================================================
class cCoreDemoObject_SQL:
"""This is the analogue of cClinIem
"""
def __init__(self, aPKey = None, aRow = None):
"""Fails if
- no connection to database possible
- patient referenced by aPKey does not exist
"""
self.__cache = {}
if aPKey:
aRow = self._get_row (aPKey)
if aRow is None:
raise gmExceptions.ConstructorError, "no
patient with ID [%s] in database" % aPKey
if aRow:
i = 0
for idx in self._idx:
self.__cache[idx] = aRow[i]
i += 1
self.__to_commit = []
self.__to_update = []
self.__cache['pk'] = self.__cache['id']
self.is_new = 0
_log.Log(gmLog.lData, 'instantiated from backend object
table %s ID %s' % (self._table, self.__cache['id']))
else:
# new object
self.__cache['id'] = "(select curr_val ('%s_id_seq'))"
% self._table
self.is_new = 1
_log.Log(gmLog.lData, 'created backend demographic
object %s ID %s' % (self._table, self.__cache['id']))
#--------------------------------------------------------
def cleanup(self):
"""Do cleanups before dying.
- note that this may be called in a thread
"""
_log.Log(gmLog.lData, 'cleaning up after patient [%s]' %
self.ID)
# FIXME: unlisten from signals etc.
#--------------------------------------------------------
# internal helper
#--------------------------------------------------------
def _get_row(self, key):
"""Return the database row for core data
"""
cmd = "select %s from %s where id = %%s" % (string.join
(self._idx, ","), self._view)
res = gmPG.run_ro_query('personalia', cmd, None, key)
if not res:
_log.Log(gmLog.lErr, 'check for person ID [%s]
existence failed' % key)
return None
return res[0]
#--------------------------------------------------------
# messaging
#--------------------------------------------------------
def _register_interests(self):
# backend
self._backend.Listen(
service = 'personalia',
signal = '"%s.%s"' % (gmSignals.patient_modified(),
self.ID),
callback = self._patient_modified
)
#--------------------------------------------------------
def _patient_modified(self):
# <DEBUG>
_log.Log(gmLog.lData, "patient_modified signal received from
backend")
# </DEBUG>
#--------------------------------------------------------
# core API
#--------------------------------------------------------
def __getitem__ (self, key):
if self.__cache.has_key (key):
return self.__cache[key]
else:
func = getattr (self, "get_%s" % key)
self.__cache[key] = func ()
return self.__cache[key]
#-----------------------------------------------------------
def __setitem__ (self, key, val):
if key in self._updateable_fields:
self.to_update.append ((key, val))
self.__cache[key] = val
else:
func = getattr (self, "set_%s" % key, None)
if func is None:
raise KeyError
if type (val) is types.TupleType:
func (*val)
else:
func (val)
#-----------------------------------------------------------
def save_payload (self):
if self.is_new:
cmd = "insert into %s (%s) values (%s)" % (self._table,
string.join ([key for key, val in self.to_update], ","), string.join ([val for
key, val in self.to_update], ","))
else:
cmd = "update %s set %s where id = %%s" % (string.join
(["%s=%%s" % key for key, val in self.to_update], ","), self._table)
args = [val for key, val in self.to_update]
args.append (self.__cache['id'])
self.to_commit.append ((cmd, args))
if self.is_new:
post_insert = []
for i in range (0, len (self.to_commit)-1):
x = 0
# grab all the commands which refer to our id
for j in range (0, len (self.to_commit[i][1])):
if self._table in
self.to_commit[i][1][j]:
x = 1
if x:
post_insert.append (self.to_commit[i])
# remove this command from the list
del self.to_commit[i]
# and put then *after* the insert added above
self.to_commit.extend (post_insert)
self.to_commit.append (("select curr_val ('%s_id_seq')"
% self._table, []))
result, data = gmPG.run_commit2 ('personalia',
self.to_commit)
if result:
self.__cache['id'] = data[0][0]
else:
result, data = gmPG.run_commit2 ('personalia',
self.to_commit)
return (result, data)
#------------------------------------------------------------
def delete (self):
cmd = "delete from %s where id = %%s" % self._table
self.to_commit.append ((cmd,[self.__cache['id']]))
#------------------------------------------------------------
#===================================================================
class cAddress (cCoreDemoObject_SQL):
_table = "v_basic_address"
_view = "v_basic_address"
_updateable_fields = ['number', 'street2', 'street', 'city',
'postcode', 'state']
_idx = ['id', 'number', 'street2', 'street', 'city', 'postcode',
'state']
#-------------------------------------------------------------
def set_type (self, typ):
self.__cache['type'] = typ
#-------------------------------------------------------------
def __init__ (self, aPKey = None, aRow = None):
cCoreDemoObject_SQL.__init__ (aPkey, aRow)
if aRow and len (aRow) == 8:
# add in type even though its not part of our backend
table
self.__cache['type'] = aRow[7]
#-------------------------------------------------------------
def __str__ (self):
return _("%(number)s %(street)s, %(urb)s %(postcode)s") % self
#===================================================================
class cComm (cCoreDemoObject_SQL):
_table = "comm_channel"
_view = "comm_channel"
_idx = ["id", "id_type", "url"]
_updateable_fields = ["url", "id_type"]
#===================================================================
class cOrg (cCoreDemoObject_SQL):
"""
Organisations
This is also the common anscestor of cPerson, self._table is used to
hide the difference.
The aim is to be able to sanely write code which doesn't care whether
its talking to an organisation or an individual"""
_table = "org"
_view = "org"
_idx = ["id_category", "description"]
#------------------------------------------------------------------
def export_demographics (self):
if not self.__cache.has_key ('addresses'):
self['addresses']
if not self.__cache.has_key ('comms'):
self['comms']
return self.__cache
#------------------------------------------------------------------
def get_addresses (self):
"""Returns a list of cAddress objects"""
cmd = """select
vba.addr_id,
vba.number,
vba.addendum,
vba.street,
vba.city,
vba.postcode,
at.type
from
v_basic_address vba,
lnk_person_org_address lpoa,
address_type at
where
lpoa.id_address = vba.addr_id
and lpoa.id_type = at.id
and lpoa.id_%s = %%s -- double %% lets us
survive
""" % self._table # this operator, then
passed to SQL layer
rows, idx = gmPG.run_ro_query('personalia', cmd, 1, self['id'])
if rows is None:
return []
elif len(rows) == 0:
return []
else:
return [cAddress (i) for i in rows]
#--------------------------------------------------------------------
def get_members (self):
"""
Returns a list of (cAddress, cPerson) tuples
"""
cmd = """select
vba.addr_id,
vba.number,
vba.addendum,
vba.street,
vba.city,
vba.postcode,
at.type,
vbp.i_id,
vbp.title,
vbp.firstnames,
vbp.lastnames,
vbp.dob,
vbp.cob,
vbp.gender
from
v_basic_address vba,
lnk_person_org_address lpoa,
address_type at,
v_basic_person vbp
where
lpoa.id_address = vba.addr_id
and lpoa.id_type = at.id
and lpoa.id_identity = vbp.i_id
and lpoa.id_org = %%s
"""
rows, idx = gmPG.run_ro_query('personalia', cmd, 1, self['id'])
if rows is None:
return []
elif len(rows) == 0:
return []
else:
return [(cAddress (i[:7]), cPerson (i[7:])) for i in
rows]
#-------------------------------------------------------
def get_names (self):
"""
Conscious imitation of cPerson.
"""
return self.__cache['description']
#------------------------------------------------------------
def set_member (self, person, address):
"""
Binds a person to this organisation at this address
"""
cmd = "insert into lnk_person_org_address ((select id from
address_type where type = %s), id_address, id_org, id_identity) values (%s, %s,
%s, %s)"
self.to_commit.append ((cmd, [address['type'], address['id'],
self.__cache['id'], person['id']]))
#------------------------------------------------------------
def unlink_person (self, person):
cmd = "delete from lnk_person_org_address where id_org = %s and
id_identity = %s"
self.to_commit.append ((cmd, [self.__cache['id'],
person['id']]))
#------------------------------------------------------------
def unlink_address (self, address):
cmd = "delete from lnk_person_org_address where id_address = %s
and id_%s = %%s" % self._table
self.to_commit.append ((cmd, [address['id'],
self.__cache['id']]))
#------------------------------------------------------------
def get_ext_ids (self):
"""
Returns a list of dictionaries of external IDs
Fields:
- origin [the origin code as returned by getExternalIDTypes]
- comment [a user comment]
- external_id [the actual external ID]
"""
cmd = "select fk_origin, comment, external_id from
lnk_%s2ext_id where id_%s = %%s" % (self._table, self._table)
rows = gmPG.run_ro_query ('personalia', cmd, None,
self.__cache['id'])
if rows is None:
return []
return [{'origin':row[0], 'comment':row[1],
'external_id':row[2]} for row in rows]
#----------------------------------------------------------------
def set_ext_id (self, fk_origin, ext_id, comment=None):
"""
Set ext_id to None to delete an external id
Remember comment matters if you have several IDs on one origin
[which is legal in AU, remember I have 5 provider numbers -- IH]
"""
if comment:
cmd = """delete from lnk_%s2ext_id where
id_%s = %%s and fk_origin = %%s
and comment = %%s""" % (self._table, self._table)
self.to_commit.append ((cmd, [self.__cache['id'],
fk_origin, comment]))
else:
cmd = """delete from lnk_%s2ext_id where
id_%s = %%s and fk_origin = %%s""" % (self._table,
self._table)
self.to_commit.append ((cmd, [self.__cache['id'],
fk_origin]))
if ext_id:
cmd = """insert into lnk_%s2ext_id (id_%s, fk_origin,
comment)
values (%%s, %%s, %%s)""" % (self._table, self._table)
self.to_commit.append ((cmd, [self.__cache['id'],
ext_id, comment]))
#-------------------------------------------------------
def get_comms (self):
"""A list of cComm objects"""
cmd = """select
cc.id,
cc.id_type,
cc.url
from
comm_channel cc,
lnk_%s_comm_channel lcc
where
lcc.id_%s = %%s and
lcc.id_comm_channel = cc.id
""" (self._table, self._table)
rows, idx = gmPG.run_ro_query('personalia', cmd, 1, self['id'])
if rows is None:
return []
elif len(rows) == 0:
return []
else:
return [cComm (i) for i in rows]
#--------------------------------------------------------------
def set_comm (self, comm):
"""
adds a new cComm object
"""
cmd = """insert into lnk_%s_comm_chan (id_%s, id_comm_chan)
values (%%s, %%s)
""" % (self._table, self._table)
self.to_commit.append ((cmd,[self.__cache[id], comm['id']]))
#---------------------------------------------------------------
def unlink_comm (self, comm):
cmd = "delete lnk_%s_comm_chan where id_%s = %%s and
id_comm_chan = %%s" % (self._table, self._table)
self.to_commit.append ((cmd,[self.__cache[id], comm['id']]))
#==============================================================================
class cPerson (cOrg):
_table = "identity"
_view = "v_basic_person"
_idx = ["id", "title", "firstnames", "lastnames", "dob", "cob",
"gender"]
_updateable_fields = ["title", "dob", "cob", "gender"]
#--------------------------------------------------------
def get_names(self, all =False):
if all:
cmd = """
select n.firstnames, n.lastnames, i.title
from names n, identity i
where n.id_identity=%s and i.id=%s"""
else:
return {'first': self.__cache['firstnames'], 'last':
self.__cache['lastnames'], 'title': self.__cache['title']}
rows, idx = gmPG.run_ro_query('personalia', cmd, 1,
self._cache['id'], self.__cache['id'])
if rows is None:
return None
if len(rows) == 0:
return [{'first': '**?**', 'last': '**?**', 'title':
'**?**'}]
else:
names = []
for row in rows:
names.append({'first': row[0], 'last': row[1],
'title': row[2]})
return names
#--------------------------------------------------------
def set_names(self, firstnames, lastnames, active=True):
"""Add a name """
cmd = "select add_name(%s, %s, %s, %s)"
active = (active and 't') or 'f'
self.to_commit.append ((cmd, [self.__cache['id'], firstnames,
lastnames, active]))
self.__cache['firstnames'] = firstnames
self.__cache['lastnames'] = lastnames
#------------------------------------------------------------
def set_address (self, address):
"""
Binds an address to this person
"""
cmd = "insert into lnk_person_org_address ((select id from
address_types where type = %s), id_address, id_identity) values (%s, %s, %s)"
self.to_commit.append ((cmd, [address['type'], address['id'],
self.__cache['id']]))
#----------------------------------------------------------------------
def get_marital_status (self):
cmd = "select name from marital_status, identity where
marital_status.id = identity.id_marital_status and identity.id = %s"
data = gmPG.run_ro_query ('personalia', cmd, None,
self.__cache['id'])
return data and data[0][0]
#--------------------------------------------------------------------
def set_marital_status (self, status):
cmd = "update identity set id_marital_status = (select id from
marital_status where name = %s) where id = %s"
self.to_commit.append ((cmd, [status, self.__cache['id']]))
#---------------------------------------------------------------------
def get_occupation (self):
"""
Currently we do not support more than one occupation
"""
cmd = "select o.name from occupation o, lnk_job2person lj2p
where o.id = lj2p.id_occupation and lj2p.id_identity = %s"
data = gmPG.run_ro_query ('personalia', cmd, None, self.ID)
return data and data[0][0]
#--------------------------------------------------------------------
def set_occupation (self, occupation):
# does occupation already exist ?
cmd = "select o.id from occupation o where o.name = %s"
data = gmPG.run_ro_query ('personalia', cmd, None, occupation)
# error
if data is None:
gmLog.gmDefLog.Log(gmLog.lErr, 'cannnot check for
occupation')
return None
# none found
if len(data) == 0:
(cmd1, [occupation])
# delete pre-existing link as required
cmd1 = """
delete from
lnk_job2person
where
id_identity = %s"""
# creating new link
cmd2 = """insert into lnk_job2person (id_identity,
id_occupation) values (%s, (select currval('occupation_id_seq')))"""
self.to_commit.extend([
(cmd1, [self.__cache['id']]),
(cmd2, [self.__cache['id'], id_occupation])
])
#----------------------------------------------------------------------
def get_relatives(self):
cmd = """
select
t.description, v.i_id, v.title, v.firstnames, v.lastnames, v.dob,
v.cob, v.gender
from
v_basic_person v, relation_types t, lnk_person2relative l
where
l.id_identity = %s and
v.id = l.id_relative and
t.id = l.id_relation_type
"""
data, idx = gmPG.run_ro_query('personalia', cmd, 1,
self.__cache['id'])
if data is None:
return []
if len(data) == 0:
return []
return [(r[0], cPerson (r[1:])) for r in data ]
#--------------------------------------------------------
def set_relative(self, rel_type, relation):
# and link the two
cmd = """
insert into lnk_person2relative (
id_identity, id_relative, id_relation_type
) values (
%s, %s, (select id from relation_types where
description = %s)
)"""
self.to_commit.append ((cmd, [relation['id'],
self.__cache['id'], rel_type]))
if success:
return relative_demographics
return None
#----------------------------------------------------------------------
def get_medical_age(self):
dob = self.__cache['dob']
if dob is None:
return '??'
return dob2medical_age(dob)
#----------------------------------------------------------------------
#================================================================
# convenience functions
#================================================================
def dob2medical_age(dob):
"""format patient age in a hopefully meaningful way"""
age = mxDT.Age(mxDT.now(), dob)
if age.years > 0:
return "%sy%sm" % (age.years, age.months)
weeks = age.days / 7
if weeks > 4:
return "%sm%sw" % (age.months, age.weeks)
if weeks > 1:
return "%sd" % age.days
if age.days > 1:
return "%sd (%sh)" % (age.days, age.hours)
if age.hours > 3:
return "%sh" % age.hours
if age.hours > 0:
return "%sh%sm" % (age.hours, age.minutes)
if age.minutes > 5:
return "%sm" % (age.minutes)
return "%sm%ss" % (age.minutes, age.seconds)
#----------------------------------------------------------------
def getAddressTypes():
"""Gets a simple list of address types."""
row_list = gmPG.run_ro_query('personalia', "select name from
address_type")
if row_list is None:
return []
if len(row_list) == 0:
return []
return [row[0] for row in row_list]
#----------------------------------------------------------------
def getMaritalStatusTypes():
"""Gets a simple list of types of marital status."""
row_list = gmPG.run_ro_query('personalia', "select name from
marital_status")
if row_list is None:
return []
if len(row_list) == 0:
return []
return [row[0] for row in row_list]
#------------------------------------------------------------------
def getExtIDTypes (context = 'p'):
"""Gets list of [code, ID type] from the backend for the given context
"""
# FIXME: error handling
rl = gmPG.run_ro_query('personalia', "select pk, name from
enum_ext_id_types where context = %s", None, context)
if rl is None:
return []
return rl
#----------------------------------------------------------------
def getCommChannelTypes():
"""Gets the dictionary of ID->comm channels"""
row_list = gmPG.run_ro_query('personalia', "select id, description from
enum_comm_types")
if row_list is None:
return None
if len (row_list) == 0:
return None
return dict(row_list)
EMAIL=1
FAX=2
HOME_PHONE=3
WORK_PHONE=4
MOBILE=5
WEB=6
JABBER=7
#----------------------------------------------------------------
def guess_state_country( urb, postcode):
"""parameters are urb.name, urb.postcode;
outputs are urb.id_state, country.code"""
cmd = """
select state.code, state.country
from urb, state
where
lower(urb.name) = lower(%s) and
upper(urb.postcode) = upper(%s) and
urb.id_state = state.id
"""
data = gmPG.run_ro_query ('personalia', cmd, None, urb, postcode)
if data is None:
return "", ""
if len(data) == 0:
return '', ''
return data[0]
#----------------------------------------------------------------
def getPostcodeForUrbId(urb_id):
# FIXME: get from views
cmd = "select postcode from urb where id = %s"
data = gmPG.run_ro_query ('personalia', cmd, None, urb_id)
if data is None:
_log.Log(gmLog.lErr, 'cannot get postcode for urb [%s]' %
urb_id)
return None
if len(data) == 0:
return ''
return data[0][0]
#----------------------------------------------------------------
def getUrbsForPostcode(pcode=None):
cmd = "select name from urb where postcode=%s"
data = gmPG.run_ro_query('personalia', cmd, None, pcode)
if data is None:
_log.Log(gmLog.lErr, 'cannot get urbs from postcode [%s]' %
pcode)
return None
if len(data) == 0:
return []
return [x[0] for x in data]
#----------------------------------------------------------------
class PostcodeMP (gmMatchProvider.cMatchProvider_SQL):
"""Returns a list of valid postcodes,
Accepts two contexts : "urb" and "street" being the **IDs** of urb and
street
"""
def __init__ (self):
# we search two tables here, as in some jurisdictions (UK,
Germany, US)
# postcodes are tied to streets or small groups of streets,
# and in others (Australia) postcodes refer to an entire town
# reviewers' comments:
# - pk this will be the data return to the id_callback()
function passed
# as gmPhrasewheel.__init__ last parameter , so the event
data will be
# the postcode for urb or street , not the id of those tables.
# This is in the cMatchProvider.__findMatches code.
source = [{
'column':'postcode',
'pk':'postcode',
'limit':10,
'table':'urb',
'extra conditions':{'urb':'id = %s',
'default':'postcode is not null'}
, 'service': 'personalia'
},{
'column':'postcode',
'table':'street',
'limit':10,
'pk':'postcode',
'extra conditions':{'urb':'id_urb = %s', 'street': 'id
= %s', 'default':'postcode is not null'}
, 'service': 'personalia'
}]
gmMatchProvider.cMatchProvider_SQL.__init__(self, source)
#----------------------------------------------------------------
class StreetMP (gmMatchProvider.cMatchProvider_SQL):
"""Returns a list of streets
accepts "urb" and "postcode" contexts
e.g.
using cMatchProvider_SQL's self.setContext("urb",...)
"""
def __init__ (self):
source = [{
'service': 'personlia',
'table': 'street',
'column': 'name',
'limit': 10,
'extra conditions': {
'urb': 'id_urb = %s',
'postcode': 'postcode = %s or postcode is null'
}
}]
gmMatchProvider.cMatchProvider_SQL.__init__(self, source)
#------------------------------------------------------------
class MP_urb_by_zip (gmMatchProvider.cMatchProvider_SQL):
"""Returns a list of urbs
accepts "postcode" context
"""
def __init__ (self):
source = [{
'service': 'personalia',
'table': 'urb',
'column': 'name',
'limit': 15,
'extra conditions': {'postcode': '(postcode = %s or
postcode is null)'}
}]
gmMatchProvider.cMatchProvider_SQL.__init__(self, source)
class CountryMP (gmMatchProvider.cMatchProvider_SQL):
"""
Returns a list of countries
"""
def __init__ (self):
source = [{
'service':'personalia',
'table':'country',
'pk':'code',
'column':'name',
'limit':5
}]
gmMatchProvider.cMatchProvider_SQL.__init__ (self, source)
class OccupationMP (gmMatchProvider.cMatchProvider_SQL):
"""
Returns a list of occupations
"""
def __init__ (self):
source = [{
'service':'personalia',
'table':'occupation',
'pk':'id',
'column':'name',
'limit':7
}]
gmMatchProvider.cMatchProvider_SQL.__init__ (self, source)
class NameMP (gmMatchProvider.cMatchProvider_SQL):
"""
List of names
"""
def __init__ (self):
source =[{
'service':'personalia',
'table':'names',
'pk':'id_identity',
'column':'lastnames',
'result':"lastnames || ', ' || firstnames || ' (' ||
dob || ')'",
'limit':5,
'extra conditions':{'occupation':'exists (select 1 from
lnk_job2person where id_occupation = %s and lnk_job2person.id_identity =
names.id_identity)'}
}]
gmMatchProvider.cMatchProvider_SQL.__init__ (self, source)
#------------------------------------------------------------
class OrgCategoryMP (gmMatchProvider.cMatchProvider_SQL):
"""
List of org categories.
"""
def __init__(self):
source = [ {
'service': 'personalia',
'table' : 'org_category',
'pk' : 'id',
'column': 'description',
'result': 'description' ,
'limit' : 5,
}]
gmMatchProvider.cMatchProvider_SQL.__init__(self, source)
#------------------------------------------------------------
#============================================================
# callbacks
#------------------------------------------------------------
def _patient_selected(**kwargs):
print "received patient_selected notification"
print kwargs['kwds']
#============================================================
if __name__ == "__main__":
_log.SetAllLogLevels(gmLog.lData)
gmDispatcher.connect(_patient_selected, gmSignals.patient_selected())
while 1:
pID = raw_input('a patient ID: ')
if pID == '-1':
break
try:
myPatient = cPerson(aPKey = pID)
except:
_log.LogException('Unable to set up patient with ID
[%s]' % pID, sys.exc_info())
print "patient", pID, "can not be set up"
continue
print "ID ", myPatient['id']
print "name ", myPatient['names']
print "title ", myPatient['title']
print "dob ", myPatient['dob']
print "med age ", myPatient['medical_age']
for adr in myPatient['addresses']:
print "address ", adr
print "--------------------------------------"
#============================================================
# $Log: gmDemographicRecord.py,v $
# Revision 1.54 2004/08/18 09:05:07 ncq
# - just some cleanup, double-check _ is defined for epydoc
#
# Revision 1.53 2004/07/26 14:34:49 sjtan
#
# numbering correction from labels in gmDemograpics.
#
# Revision 1.52 2004/06/25 12:37:19 ncq
# - eventually fix the import gmI18N issue
#
# Revision 1.51 2004/06/21 16:02:08 ncq
# - cleanup, trying to make epydoc fix do the right thing
#
# Revision 1.50 2004/06/21 14:48:25 sjtan
#
# restored some methods that gmContacts depends on, after they were booted
# out from gmDemographicRecord with no home to go , works again ;
# removed cCatFinder('occupation') instantiating in main module scope
# which was a source of complaint , as it still will lazy load anyway.
#
# Revision 1.49 2004/06/20 15:38:00 ncq
# - remove import gettext/_ = gettext.gettext
# - import gmI18N handles that if __main__
# - else the importer of gmDemographicRecord has
# to handle setting _
# - this is the Right Way as per the docs !
#
# Revision 1.48 2004/06/20 06:49:21 ihaywood
# changes required due to Epydoc's OCD
#
# Revision 1.47 2004/06/17 11:36:12 ihaywood
# Changes to the forms layer.
# Now forms can have arbitrary Python expressions embedded in @..@ markup.
# A proper forms HOWTO will appear in the wiki soon
#
# Revision 1.46 2004/05/30 03:50:41 sjtan
#
# gmContacts can create/update org, one level of sub-org, org persons, sub-org
persons.
# pre-alpha or alpha ? Needs cache tune-up .
#
# Revision 1.45 2004/05/29 12:03:47 sjtan
#
# OrgCategoryMP for gmContact's category field
#
# Revision 1.44 2004/05/28 15:05:10 sjtan
#
# utility functions only called with exactly 2 args in order to fulfill
function intent, but do some checking for invalid args.
#
# Revision 1.43 2004/05/26 12:58:14 ncq
# - cleanup, error handling
#
# Revision 1.42 2004/05/25 16:18:13 sjtan
#
# move methods for postcode -> urb interaction to gmDemographics so gmContacts
can use it.
#
# Revision 1.41 2004/05/25 16:00:34 sjtan
#
# move common urb/postcode collaboration to business class.
#
# Revision 1.40 2004/05/19 11:16:08 sjtan
#
# allow selecting the postcode for restricting the urb's picklist, and resetting
# the postcode for unrestricting the urb picklist.
#
# Revision 1.39 2004/04/15 09:46:56 ncq
# - cleanup, get_lab_data -> get_lab_results
#
# Revision 1.38 2004/04/11 10:15:56 ncq
# - load title in get_names() and use it superceding getFullName
#
# Revision 1.37 2004/04/10 01:48:31 ihaywood
# can generate referral letters, output to xdvi at present
#
# Revision 1.36 2004/04/07 18:43:47 ncq
# - more gender mappings
# - *comm_channel -> comm_chan
#
# Revision 1.35 2004/03/27 04:37:01 ihaywood
# lnk_person2address now lnk_person_org_address
# sundry bugfixes
#
# Revision 1.34 2004/03/25 11:01:45 ncq
# - getActiveName -> get_names(all=false)
# - export_demographics()
#
# Revision 1.33 2004/03/20 19:43:16 ncq
# - do import gmI18N, we need it
# - gm2long_gender_map -> map_gender_gm2long
# - gmDemo* -> cDemo*
#
# Revision 1.32 2004/03/20 17:53:15 ncq
# - cleanup
#
# Revision 1.31 2004/03/15 15:35:45 ncq
# - optimize getCommChannel() a bit
#
# Revision 1.30 2004/03/10 12:56:01 ihaywood
# fixed sudden loss of main.shadow
# more work on referrals,
#
# Revision 1.29 2004/03/10 00:09:51 ncq
# - cleanup imports
#
# Revision 1.28 2004/03/09 07:34:51 ihaywood
# reactivating plugins
#
# Revision 1.27 2004/03/04 10:41:21 ncq
# - comments, cleanup, adapt to minor schema changes
#
# Revision 1.26 2004/03/03 23:53:22 ihaywood
# GUI now supports external IDs,
# Demographics GUI now ALPHA (feature-complete w.r.t. version 1.0)
# but happy to consider cosmetic changes
#
# Revision 1.25 2004/03/03 05:24:01 ihaywood
# patient photograph support
#
# Revision 1.24 2004/03/02 10:21:09 ihaywood
# gmDemographics now supports comm channels, occupation,
# country of birth and martial status
#
# Revision 1.23 2004/02/26 17:19:59 ncq
# - setCommChannel: arg channel -> channel_type
# - setCommChannel: we need to read currval() within
# the same transaction as the insert to get consistent
# results
#
# Revision 1.22 2004/02/26 02:12:00 ihaywood
# comm channel methods
#
# Revision 1.21 2004/02/25 09:46:20 ncq
# - import from pycommon now, not python-common
#
# Revision 1.20 2004/02/18 15:26:39 ncq
# - fix dob2medical_age()
#
# Revision 1.19 2004/02/18 06:36:04 ihaywood
# bugfixes
#
# Revision 1.18 2004/02/17 10:30:14 ncq
# - enhance GetAddresses() to return all types if addr_type is None
#
# Revision 1.17 2004/02/17 04:04:34 ihaywood
# fixed patient creation refeential integrity error
#
# Revision 1.16 2004/02/14 00:37:10 ihaywood
# Bugfixes
# - weeks = days / 7
# - create_new_patient to maintain xlnk_identity in historica
#
# Revision 1.15 2003/12/01 01:01:41 ncq
# - get_medical_age -> dob2medical_age
#
# Revision 1.14 2003/11/30 01:06:21 ncq
# - add/remove_external_id()
#
# Revision 1.13 2003/11/23 23:32:01 ncq
# - some cleanup
# - setTitle now works on identity instead of names
#
# Revision 1.12 2003/11/23 14:02:40 sjtan
#
# by setting active=false first, then updating values, side effects from
triggers can be avoided; see also
# F_active_name trigger function in server sql script,which fires only if
new.active = true .
#
# Revision 1.11 2003/11/22 14:44:17 ncq
# - setTitle now only operates on active names and also doesn't set the name
# to active which would trigger the trigger
#
# Revision 1.10 2003/11/22 14:40:59 ncq
# - setActiveName -> addName
#
# Revision 1.9 2003/11/22 12:53:48 sjtan
#
# modified the setActiveName and setTitle so it works as intended in the face
of insurmountable triggers ;)
#
# Revision 1.8 2003/11/20 07:45:45 ncq
# - update names/identity, not v_basic_person in setTitle et al
#
# Revision 1.7 2003/11/20 02:10:50 sjtan
#
# remove 'self' parameter from functions moved into global module namespace.
#
# Revision 1.6 2003/11/20 01:52:03 ncq
# - actually, make that **?** for good measure
#
# Revision 1.5 2003/11/20 01:50:52 ncq
# - return '?' for missing names in getActiveName()
#
# Revision 1.4 2003/11/18 16:44:24 ncq
# - getAddress -> getAddresses
# - can't verify mxDateTime bug with missing time tuple
# - remove getBirthYear, do getDOB() -> mxDateTime -> format
# - get_relative_list -> get_relatives
# - create_dummy_relative -> link_new_relative
# - cleanup
#
# Revision 1.3 2003/11/17 10:56:34 sjtan
#
# synced and commiting.
#
# Revision 1.2 2003/11/04 10:35:22 ihaywood
# match providers in gmDemographicRecord
#
# Revision 1.1 2003/11/03 23:59:55 ncq
# - renamed to avoid namespace pollution with plugin widget
#
# Revision 1.6 2003/10/31 23:21:20 ncq
# - remove old history
#
# Revision 1.5 2003/10/27 15:52:41 ncq
# - if aFormat is None in getDOB() return datetime object, else return
formatted string
#
# Revision 1.4 2003/10/26 17:35:04 ncq
# - conceptual cleanup
# - IMHO, patient searching and database stub creation is OUTSIDE
# THE SCOPE OF gmPerson and gmDemographicRecord
#
# Revision 1.3 2003/10/26 16:35:03 ncq
# - clean up as discussed with Ian, merge conflict resolution
#
# Revision 1.2 2003/10/26 11:27:10 ihaywood
# gmPatient is now the "patient stub", all demographics stuff in gmDemographics.
#
# Ergregious breakages are fixed, but needs more work
#
# Revision 1.1 2003/10/25 08:48:06 ihaywood
# Split from gmTmpPatient
#
# old gmTmpPatient log
# Revision 1.41 2003/10/19 10:42:57 ihaywood
# extra functions
#
# Revision 1.40 2003/09/24 08:45:40 ihaywood
# NewAddress now functional
#
# Revision 1.39 2003/09/23 19:38:03 ncq
# - cleanup
# - moved GetAddressesType out of patient class - it's a generic function
#
signature.asc
Description: OpenPGP digital signature
- [Gnumed-devel] new gmDemographicRecord,
Ian Haywood <=