[Top][All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: [Gnumed-devel] Time for a major re-think in 2005 - opinions please.
From: |
catmat |
Subject: |
Re: [Gnumed-devel] Time for a major re-think in 2005 - opinions please. |
Date: |
Thu, 06 Jan 2005 10:57:50 +1100 |
User-agent: |
Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.7.5) Gecko/20041231 |
Richard Terry wrote:
One of my new-year resolutions was to try and re-start the debate again on
gnuMed and the way it is heading (or not heading depending on which way one
looks at it).
I'd ask anyone reading this to give it time and considered, not knee jerk
opinion. Though I've mentioned particular names of some of those involved in
the project, I hope that those mentioned are able to not take any comments
I've made in a personal way. Also, firing back comments to this post such as
'its in the Wiki' 'It's in the road-map' is not going to be helpful.
Perhaps asking those who susbscribe to the list but don't take an active part
in development to pass an honest opinion on the processes of this group and
what they see the problems as and how things could be improved.
Slashdotters amongst you may possibly already have read one of today's links
entitled "Developers, is your project a sinking ship?". If not please read
the link before continuing:
http://www.acmqueue.com/modules.php?name=Content&pa=showpage&pid=239
and specifically try and apply the criteria to gnuMed, if necessary by this
quick link:
http://www.acmqueue.com/figures/issue019/tiwana2.gif
this is something one read's in say, Software Engineering by Pressman,
which makes one think that there's a place in the world for authors of
self -help books for programmers.
Anyone checking out the gnuMed web site is confronted by the following hopeful
description;
==============================================
As its major project, it is busy building a medical software package that will
be
* open source
* free
* secure
* respectful of patient privacy
* based on open standards
* flexible
* fully featured
* networked (client-server architecture)
* easy to use
* multi platform
* multi lingual
Gnumed is being actively developed, but is not yet ready for
clinical use.
==============================================
The essence of the problem to me is whether or not gnuMed can ever offer an
alternative to current medical software, if it continues in its current
unmanaged direction. Having such an ambitious project which in the end only
works for a few individuals in the world because that's the way they designed
it, is not of much use.
Some sort of project management I beleive is essential for gnuMed. If one
looks at all the sucessful open-source software projects, ranging from KDE to
GIMP, Abiword etc, all have some sort of project management teams.
these people are full time, trained for SD, company employees or paid
computing academics,
and are backed probably by an equally large marketing department.
gnumed fits the pattern of most of the other thousand or so open
sourceforge projects,
fine , but small programs motivated by a few or even just one person,
who does most
of the programming.
There is
an overall goal, and each of the developers have an allocated role, with
someone having a final say in certain area's.
there isn't enough people for this.
gnuMed seems to have no such structure. We addressed this in detail during our
gnuMed conference in Sydney a long time ago. We attempted to put in motion a
process which would involve a proper plan for the project, to no avail. The
frustration for me is that a number of active developers on the list seem to
actually beleive that there is structure and process, almost like they have a
'blind spot'.
I've watched a large number of talented people come enthusiastically to the
project, only to melt away into cyberspace. I Myself, have gone through long
periods of not even bothering to read the listmail, 'disappearing' for months
on end.
IN Summary I beleive:
1) We need a project manager WHO IS NOT INVOLVED WITH CODING.
project managers , like say , some non-medically trained practice managers,
are motivated by business - being paid for bossing people or having
"contacts" . They are not interested in quality, only customer satisfaction
and profit.
ie someone with project management experience and talents who at the end of
the day has the final say, who can overide Karsten, Horst, Ian, myself,
carlos, etc, because they will have the ability to have a larger over-view
than anyone in the project.
2) Individuals who are accepted as part of the project development team need
to have their abilities respected, and given the final say in their aspect of
the project design over other team members, subject to the final decision of
the project manager.
Yes, some of us just don't have acceptable behaviour, and by default,
then, they don't produce acceptable code.
3) We need a heavy dose of reality checking in respect to what will make the
project actually become usuable e.g sticking to the niave belief that drugref
will be able to provide usable drug data to the office desk of working GP's
in australia needs to be replaced with the pragmatic decision to make a deal
with say MIMS and use their drug data. As much as you try an argue against
this stuff it is reality. Perhaps in the future we can switch to DrugRef, but
not now.
How about BNF ? It requires free registration, but otherwise is navigable
by following links. Perhaps a web agent for gnumed to bnf requests.
Must be other like web drug databases around too.
MIMS would cost money.
Though I almost cringe at the thought of another frustratiing day, perhaps its
time for another gnuMed AU(+DE) conference to thrash this out for once and
for all.
Thoughts??
Regards
Richard
As an experiment, I recently did a console mode emr application using
python2.4.
In windows, python2.4 comes with bsd-db (berkeley-db), but in linux,
one has to install bsd-db package first, then run configure/make/make
install
on the python2.4 src. medical records are simply recursive maps,
and stored under a application generated primary key. Merging 2 medical
records
can be done by doing a recursive merge. The code was written with the
client
being a python script with a bunch of functions which calls an xml-rpc
server, which
is the server, which performs "store patient" "get patient" "merge
patient",
"reindex database", "export database". Given about 2 weeks of spare time,
a fairly basic working knowledge of python and it's modules, and a
*clear* idea
of what one wants out of an emr, this application shows that *anyone*
can write
a simple emr application, they just need time, inclination and a bit of
practice.
(so there's my usual plug).
The test-app can generate fake patients loaded with data (quantity mainly),
so one can generate 10,000 moderately loaded med recs, and create some
hand entered ones, and search for them,
and this shows bsd-db works quite well, as object database backend.
Because it's xmlrpc, you can run several main.py clients and generate
records,
and it shows that there is no interference between client when storing
records.
Not sure about scalability though , but I think it would work ok in an
environment
of say 10 users, who are infrequently loading and saving records ( not
more than
once every 5 minutes , for each user).
I found that there was a problem copying the bsd-db
data file from one machine to the other, and expecting it to work
on the other machine (perhaps incompatible between data formats of different
bsd-db versions), so export was done between test-apps on different machines
by having one test-app call another's xml-rpc interface, which works.
import traceback, sys, time
import xmlrpclib
from testpatientserver import PatientServer
#server = PatientServer()
server = xmlrpclib.ServerProxy("http://syan9589laptop:8000", allow_none=1)
main_menu_input = raw_input
def main_menu(m):
key = ""
while 1:
for k,v in m.iteritems():
print k, ": ", v
answ = main_menu_input("command ? ")
try:
key = m.get(int(answ), answ)
except:
key= answ
lastindex = 20
for v in m.itervalues():
if v.find(answ) <> -1:
if v.find(answ) < lastindex:
lastindex = v.find(answ)
key = v
print key
yield key
print "the last command was ", key
def get_attributes( names, old_values = None, mandatory=[] ):
m={}
for n in names:
prompt = n + " ? "
if old_values and old_values.has_key(n):
prompt += "\n( old value is '"+str(old_values[n])+"'[enter to KEEP;
leading '+' to APPEND] : "
answ = raw_input(prompt).strip()
if len(answ) == 0 and old_values:
answ = old_values.get(n, '')
elif len(answ) and answ[0]=='+':
answ = old_values.get(n, '') + answ[1:]
if len(answ) == 0 and n in mandatory:
return None
m[n]=answ
if old_values:
#attach the difference of old_values
l =[ x for x in old_values.keys() if x not in names]
for k in l:
m[k] = old_values[k]
return m
def null_or_blank( d, attributes):
for x in attributes:
if d.has_key(x) and \
( d[x] is None or not isinstance(d[x], str) or d[x].strip()==""):
print x, " must be a non-blank string"
return 1
return 0
def get_demographic_attributes():
return ["first name", "last name", "birthdate", "address",
"suburb", "postcode", "state", "medicare_no", "medicare_exp_date"]
def get_mandatory_demographic_attributes():
return ["first name", "last name" ]
def console_patient_getter(old_patient = None):
patient = get_attributes( get_demographic_attributes() , old_patient )
if null_or_blank(patient, get_mandatory_demographic_attributes() ):
return None
return patient
def get_patient_detail_block(patient):
d = get_demographic_attributes()
return [ [ (k,patient.get(k,"")) for k in d[:4] ],
[ (k,patient.get(k,"")) for k in d[4:8] ],
[(k,patient.get(k,"")) for k in d[8:] ]
]
def show_patient_detail(patient):
for l in get_patient_detail_block(patient):
print l
def create_patient(patient_getter = console_patient_getter, sync =1 ):
patient = patient_getter()
if patient:
server.store_patient( patient, 0, sync)
return patient
def edit_patient_detail( patient , patient_getter = console_patient_getter):
new_patient = patient_getter(patient)
if new_patient:
server.store_patient( new_patient, server.make_primary_key( patient) )
return new_patient
def find_patient():
query = get_attributes(["last name"])
try:
query["last name"], query["first name"] = [ x.strip() for x in
query["last name"].split(",")]
except:
query = get_attributes(["first name"], query)
patients = server.find_patient( query["last name"],query["first name"] ,
get_demographic_attributes() )
if not patients:
"No Patient Found"
return None
if not isinstance(patients, list):
return patients
if patients is []:
return None
m = {}
for (i, p) in enumerate(patients):
l = get_patient_detail_block(p)
print i +1, " : ",[x[1] for x in l[0]],[x[1] for x in l[1]]
while 1:
try:
j = int( raw_input("Enter number of selected patient (0 to exit): "))
except:
continue
if j == 0:
return None
if len(patients) >= j:
return server.find_patient( query["last name"],query["first name"] ,
get_demographic_attributes() , j -1)
def show_allergy_status(patient):
# just a message if currently nil known allergies
if patient.has_key('nka') and patient['nka'] is True:
print "patient has nil known allergies"
#show current allergies with numbers
if patient.has_key('allergies') :
for i, a in enumerate(patient['allergies']):
print i+1, a
def manage_allergy(patient):
#allergy management functions
f=main_menu( { 1:"add or change allergy", 2: "delete allergy",
3:"nil known allergy",
4:"exit manage allergy" } )
show_allergy_status(patient)
q = f.next()
while q is not "exit manage allergy":
process_allergy_command(q, patient)
show_allergy_status(patient)
q = f.next()
server.store_patient(patient)
def process_allergy_command( q, patient):
print "process command ", q
if q == "add or change allergy":
allergy = get_attributes(["allergen", "reaction", "when", "context"])
print "got allergy"
if null_or_blank(allergy, ["allergen", "reaction"]):
print "allergen and reaction both needed."
elif not patient.has_key('allergies'):
#create new list of first allergy
patient['allergies'] = [ allergy ]
if patient.has_key('nka'):
del patient['nka']
else:
#filter out old allergy entry
patient['allergies'] = \
[ a for a in patient['allergies'] if a['allergen'] <>
allergy['allergen'] ]
patient['allergies'].append(allergy)
elif q == "delete allergy":
if not patient.has_key('allergies'):
print "no allergies to delete"
else:
allergen =raw_input("Allegen to delete : ")
allergies = [ a for a in patient['allergies'] if a['allergen'].find(
allergen) >=0 ]
for allergy in allergies:
print "found allergy: ", allergy
yn = raw_input("Delete this allergy (y/n) ? ")
if yn.lower().strip() == "y":
patient['allergies'].remove(allergy)
if len(patient['allergies']) == 0:
del patient['allergies']
elif q == "nil known allergy":
if patient.has_key('allergies') and len(patient['allergies']) > 0:
print "patient has existing allergy."
else:
patient['nka']=True
def display_social_history(patient):
features = ["smoking", "alcohol", "other drugs",
"occupation", "relationships"]
for f in features:
print f, ":\n\t", patient.get(f, "")
def social_history(patient):
f = main_menu( {1:"smoking" , 2:"alcohol", 3:"other drugs",
4: "occupation",
5: "relationships",
6:"exit"
} )
display_social_history(patient)
q = f.next()
while q <> "exit":
if q == "smoking":
smoking = get_attributes(["smokes", "never smoked",
"cigarettes/per/day", "last quit", "ready to quit"])
if not null_or_blank(smoking, ["cigarettes/per/day"]):
patient['smoking'] = smoking
elif q == "alcohol":
alcohol = get_attributes(["binges",
"standards drinks per day",
"days free per week"])
patient['alcohol'] = alcohol
elif q == "other drugs":
other_drugs = get_attributes(["marijuhuana, gms per week",
"heroin , hits per year",
"speed, tabs per year",
"ecstasy, tabs per year"
])
patient['other drugs'] = other_drugs
elif q == "occupation":
occupation = get_attributes(["current occupation", "duration",
"previous occupations",
"exposure concerns"])
patient["occupation"] = occupation
elif q == "relationships":
relationships = get_attributes(["concerns"])
patient["relationships"] = relationships
display_social_history(patient)
q = f.next()
server.store_patient(patient)
def show_history(patient):
print
if not patient.has_key("history"):
print " ** No Past History entered."
else:
for i, v in enumerate(sorted_history_list(patient)):
print i+1, ": ", [ n + ': "' + str(v.get(n,""))+'"' for n in
['onset', 'condition', 'active' ]]
print "\t comments : ", v['comments']
print
def sorted_history_list(patient):
l = patient['history'].values()
l.sort(lambda x,y: cmp(x['onset'], y['onset']) )
return l
def get_history_attributes() :
return [ "condition", "onset", "active", "comments"]
def input_history( patient, old_item = None):
history = get_attributes(get_history_attributes() , old_item)
if history['active'] in ['f', '0', 'false', 'n', 'no']:
history['active'] = False
else:
history['active'] = True
return history
def add_or_replace_history( patient, history, check_replace =0):
if not check_replace or not patient.has_key("history") \
or \
not patient["history"].has_key(history["condition"]) \
or \
raw_input("Replace existing history item :\n\t"+
patient["history"][history["condition"]]
+"\nwith\n\t"+history+"\t ?")[0] == 'y':
if not patient.has_key("history"):
patient["history"] = {}
patient["history"][history["condition"]]=history
def get_history_item_by_n( patient):
n = int(raw_input("Enter number of history item : "))
if n < 1 or n > len(patient["history"]):
raise ValueError
return sorted_history_list(patient)[n-1]
def past_history(patient, check_replace = 1):
f = main_menu ({1:"add history item", 2:"toggle active/inactive history
item",
3:"edit history item", 4:"delete history item" , 5:"exit history"})
show_history(patient)
q= f.next()
while q <> "exit history":
if q == "add history item":
add_or_replace_history( patient, input_history(patient),
check_replace)
elif q == "toggle active/inactive history item":
try:
print "Toggle history item"
item = get_history_item_by_n( patient)
item['active'] = not item['active']
except:
print "error in toggle items"
elif q == "edit history item":
print "Edit history item"
item = get_history_item_by_n(patient)
history = input_history(patient, item)
del patient['history'][item['condition']]
add_or_replace_history( patient, history)
show_history(patient)
q= f.next()
server.store_patient(patient)
def view_summary(patient , include_encounters = False, brief_encounters = True):
print "~"*80
print "PATIENT DETAIL"
show_patient_detail(patient)
print
print "PAST HISTORY"
show_history(patient)
print "MEDICATIONS"
print
view_medications(patient)
print "ALLERGIES"
show_allergy_status(patient)
print
print "SOCIAL HISTORY"
display_social_history(patient)
print
if include_encounters:
print "PROGRESS NOTES"
view_encounters( patient, brief = brief_encounters)
print "~"*80
def input_drug(old_drug = None):
print "Enter drug details."
d = get_attributes(["name", "presentations", "drug class",
"description",
"indications", "contraindications",
"practice points",
"warnings",
"side effects",
"interactions",
"pregnancy",
"breastfeeding" ], old_drug, mandatory=["name"] )
return d
def select_drug( pick_presentation = 0):
drug = None
drugname = raw_input("enter drug name : ")
result=server.get_drug(drugname)
if not isinstance(result, list):
drug = result
else:
for i, d in enumerate(result):
print_drug(d , format= str(i+1)+": %s" )
n = 0
try:
n = int(raw_input("select drug by number:"))
if n:
drug = result[n-1]
except:
print "no drug selected"
p = ""
if drug and pick_presentation and drug.has_key("presentations"):
for i, p in enumerate( drug['presentations'].split(',') ):
p = p.strip()
print i+1, p
if i > 1:
n =0
while n < 1 and n >= i:
n = int(raw_input("select presentation by number: ") )
p = drug['presentations'].split(",")[n-1].strip()
return drug, p
def manage_drugs():
f = main_menu ( { 1:"add drug", 2:"find/edit drug", 3:"delete drug",
4: "list all drugs",
5:"exit drugs" })
q = f.next()
while q <> "exit drugs":
try:
if q =="add drug":
d = input_drug()
if d and server.set_drug(d):
print "drug ", d['name'], "added"
elif q == "find/edit drug":
drug, p = select_drug()
if drug:
print drug["name"], drug["presentations"]
if drug and raw_input("edit this drug ?").strip().lower()[:1]
=='y':
d=input_drug(drug)
print "setting with ", d
if d :
server.set_drug(d, drug['name'])
elif q == "delete drug":
result = server.delete_drug(raw_input("enter drug name to
delete"))
if isinstance(result, str):
print "ERROR:", result
elif q == "list all drugs":
for d in server.list_drugs():
print_drug(d)
except:
print sys.exc_info()
raise
q= f.next()
def print_drug(drug, format="*%s*"):
print format % drug["name"]
for k in ["presentations", "indications","class",
"description",
"warnings", "contraindications",
"indications", "side effects",
"practice points", "pregnancy",
"breastfeeding"]:
print " ",k,":",drug.get(k,"")
print
def view_medications(patient):
if not patient.has_key("medications"):
print "NO MEDICATIONS"
else:
for i,m in enumerate(patient["medications"].values()):
print i+1, [ (k, m.get(k,"") ) for k in ["drug", "presentation",
"instruction", "quantity", "repeats", "dated", "duration"] ]
def view_old_prescriptions(patient):
if not patient.has_key("old scripts"):
print "NO OLD PRESCRIPTIONS."
else:
for i,m in enumerate( patient["old scripts"] ):
print i+1, m
def prescribe_drug(patient):
d, presentation = select_drug(pick_presentation = True)
if d:
p = raw_input("instruction?")
q = raw_input("quantity?")
r = raw_input("repeats?")
date = raw_input("prescription date?")
if date.strip() == "":
date = time.strftime("%x", time.localtime() )
dur = 1
try:
dur = int(q)
try: dur *= int(r.split(' ')[0])
except: pass
except: pass
new_dur = raw_input("intended duration(default is "+ str(dur)+"
day(s) , i for indefinite )?")
if new_dur == "i":
dur= "indefinite"
else:
try:
dur = int(new_dur)
except:
pass
prescription = { "drug":d['name'], "presentation": presentation,
"instruction": p, "quantity": q, "repeats": r,
"dated":date , "duration":dur}
meds = patient.setdefault("medications", {})
old_scripts = patient.setdefault("old scripts", [])
if meds.has_key(prescription["drug"]):
old_scripts.append( meds[prescription["drug"] ] )
meds[prescription["drug"] ] = prescription
server.store_patient(patient)
def cease_drug(patient):
view_medications(patient)
try:
n = int(raw_input("enter number of drug to cease : "))
if (n == 0):
raise ValueError
if n > len( patient["medications"]):
raise ValueError
patient["old scripts"].append( patient["medications"].values()[n-1]
)
del
patient["medications"][patient["medications"].values()[n-1]["drug"]]
server.store_patient(patient)
except:
print "error in cease drugs"
def medications(patient):
f = main_menu( {
1: "prescribe drug",
2:"change prescription",
3:"cease prescription",
4:"view prescription history",
5:"view current medications",
6:"exit medications"
} )
q = f.next()
while q <> "exit medications":
if q == "prescribe drug":
prescribe_drug(patient)
if q == "cease prescription":
cease_drug(patient)
if q == "view current medications":
view_medications(patient)
if q == "view prescription history":
view_old_prescriptions(patient)
q = f.next()
def add_encounter(patient):
encounter={ 'time': [n for n in time.localtime() ] }
print "time of encounter: ", time.strftime("%c", encounter['time'])
encounter['notes'] = raw_input("encounter notes :")
if encounter['notes'].strip() == "":
return
e = patient.setdefault('encounters',{})
day = e.setdefault( time.strftime('%x', encounter['time']) , {})
day[time.strftime('%X', encounter['time']) ] = encounter
server.store_patient(patient)
def date_sort(x, y):
return int(time.mktime(time.strptime(x, "%x")) -
time.mktime(time.strptime(y, "%x")))
def time_sort(x,y):
return int ( 100*(time.mktime(x['time']) - time.mktime(y['time']) ) )
def view_encounters( patient, all=1, between_times = [], brief = 0):
if not patient.has_key('encounters'):
print "no encounters recorded for patient"
else:
l = patient['encounters'].keys()
l.sort(date_sort)
e = [ (k, patient['encounters'][k]) for k in l]
for k, d in e:
print "Date: ", k
l = d.values()
l.sort( time_sort)
for entry in l:
if brief:
def endof(s,start, x, default):
if x < start or x > (start + default) :
return start + default
return x
n =entry['notes']
si = n.find('s/')
oi = n.find('o/')
ai = n.find('a/')
pi = n.find('p/')
s, o, a, p = n[: endof(n, 0, oi, 30)], n[oi: endof(n, oi,
ai, 40)], n[ai: endof(n, ai, pi, 20)] , n[pi:pi+30]
print " Time:", time.strftime("%X", entry['time']), s, o,
a, p
else:
print " Time:", time.strftime("%X", entry['time'])
print " ",entry["notes"]
def manage_encounter(patient):
f = main_menu( {
1: "add encounter",
2: "view encounters",
3:"exit encounters"
})
q = f.next()
while q <> "exit encounters":
if q == "add encounter":
add_encounter(patient)
elif q =="view encounters":
view_encounters(patient)
pass
q = f.next()
def merge_patient(patient):
print "Select second patient to merge:"
p2= find_patient()
if not p2:
return patient
for i, p in enumerate([patient, p2]):
print
print i+1, ":\t"
for x in get_patient_detail_block(p):
print "\t",[y[1] for y in x]
print
yn = raw_input("merge these two patients(y/n)")
if yn[:1] == "y":
n = 0
while n not in[1,2]:
try:
n = int(raw_input("keep which patient's details( 1 or 2)?"))
except: pass
try:
patient = server.merge_patient(patient, p2 , n)
except:
print sys.exc_info()[0], sys.exc_info()[1]
return patient
def edit_patient(patient):
print "** editing patient "
show_patient_detail(patient)
print
old_patient = {}
old_patient.update(patient)
f = main_menu( {
1:"view summary",
2: "encounter" ,
3: "medications",
4: "past history",
5:"edit patient detail",
6: "manage allergies",
7:"social history",
8:"family history",
9:"merge patient",
10:"exit" } )
q = f.next()
while q is not "exit":
print "doing ", q
if q == "edit patient detail":
pat = edit_patient_detail(patient)
if pat: patient = pat
if q is "manage allergies":
manage_allergy(patient)
if q is "social history":
social_history(patient)
if q is "past history":
past_history(patient)
if q is "medications":
medications(patient)
if q is "view summary":
yn = raw_input("(b)rief progress notes , (l)ong progress notes,
or (n)o progress notes(default) (b/l/n)")
view_summary(patient , include_encounters = 'bl'.find(yn[:1])
>=0 , brief_encounters = yn[:1] =='b' )
if q is "encounter":
manage_encounter(patient)
if q is "merge patient":
patient = merge_patient( patient)
q = f.next()
def test_junk():
import testjunk
n = 0
try:
n = int ( raw_input("number of junk patients ? ") )
except:
pass
if not n:
return
for i in xrange(0, n):
p = create_patient(testjunk.get_random_demographic, sync=0)
p = testjunk.add_random_history(
testjunk.add_random_meds(
testjunk.add_random_allergy(
testjunk.add_random_notes(p))))
server.store_patient(p, 0, 0)
if i % 100 == 0:
server.sync()
view_summary(p)
server.sync()
if __name__=="__main__":
f = main_menu({ 1:"login", 2:"find patient", 3:"create patient",
4:"test junk",
5: "transfer all patients",
6:"reindex lastnames",
7: "manage drugs",
8:"exit" })
print f
while 1:
q = f.next()
if q is "exit":
break
if q is "create patient":
create_patient()
if q is "find patient":
p = find_patient( )
if p :
edit_patient(p)
if q is "test junk":
test_junk()
if q is "reindex lastnames":
server.reindex_lastnames()
if q is "manage drugs":
manage_drugs()
if q is "transfer all patients":
url = raw_input("enter url of receiving server: ")
print server.load_to_server(url)
#import bsddb
import shelve
import bsddb
import gc
import sys
class PatientServer:
def __init__( self, dbname="patient.shelve",
index_name="patient_by_last_name.index",
flag_patdb="c", flag_indexdb="c", open_meddb=True,
med_dbname="drugs.shelve"):
self.index_name = index_name
self.pat_dbname = dbname
self.open( flag_patdb, flag_indexdb)
if open_meddb:
self.med_dbname= med_dbname
self.med_btdb = bsddb.btopen(med_dbname,"c")
self.med_db = shelve.BsdDbShelf(self.med_btdb)
if self.med_db is not None:
print self.med_db, "created"
else:
print self.med_db, " seems to be null"
else:
self.med_db=None
def open( self, flag_patdb = "c", flag_indexdb ="c" ):
self.patient_hashdb = bsddb.hashopen(self.pat_dbname, flag_patdb)
self.lastname_hashdb= bsddb.hashopen(self.index_name, flag_indexdb)
self.patient_db = shelve.BsdDbShelf(self.patient_hashdb)
self.patient_by_lastname = shelve.BsdDbShelf(self.lastname_hashdb)
def close( self):
self.patient_db.close()
self.patient_by_lastname.close()
self.patient_db = None
self.patient_by_lastname= None
gc.collect()
def reindex_lastnames(self):
new_patient_server = PatientServer( self.pat_dbname+".new",
self.index_name +".new",
"n", "n" , open_meddb=False)
pk, p = self.patient_db.first()
i = 0
while 1:
i += 1
new_patient_server.store_patient(p, sync=0)
print "indexed ", pk
try:
pk, p = self.patient_db.next()
if i % 1000 == 0:
new_patient_server.sync()
except:
print "reached last"
break
new_patient_server.close()
self.rename()
return True
def rename(self):
import os, time
self.close()
t = "-".join( [ str(x) for x in time.localtime()[:6] ] )
timestamped_index_name = self.index_name + "_" + t
timestamped_pat_dbname = self.pat_dbname + "_" + t
os.rename( self.index_name , timestamped_index_name )
os.rename( self.index_name+".new" , self.index_name )
os.rename( self.pat_dbname , timestamped_pat_dbname )
os.rename( self.pat_dbname+".new" , self.pat_dbname )
self.open()
def make_primary_key(self, patient):
l = [patient.get(n,"").replace("%", "[percent]").lower()
for n in ["last name", "first name", "birthdate", "medicare_no"] ]
primary_key = "%".join(l)
return primary_key
def _remove_primary_key(self, primary_key, sync = 1):
try:
lastname = primary_key.split("%")[0].lower()
except:
print "unable to extract last name from ", primary_key
if not self.patient_by_lastname.has_key( lastname[:2] ):
print "did not find ", lastname, " first 2 characters in index"
return 0
d = self.patient_by_lastname[lastname[:2]]
if d.has_key(primary_key):
del d[primary_key]
self.patient_by_lastname[lastname[:2]] = d
if sync:
self.lastname_hashdb.sync()
return 1
def _index_primary_key(self, primary_key, patient, sync = 1):
lastname = patient.get("last name","").lower()
if not self.patient_by_lastname.has_key(lastname[:2] ):
dict_primary_keys = {}
else:
dict_primary_keys = self.patient_by_lastname[lastname[:2] ]
dict_primary_keys[primary_key] = "1"
self.patient_by_lastname[ lastname[:2] ] = dict_primary_keys
if sync:
self.lastname_hashdb.sync()
def _update_references(self, old_primary_key, primary_key):
if not primary_key:
print "No new primary key"
return 0
pass
def load_to_server( self, server_url):
import xmlrpclib
i = 0
try:
server = xmlrpclib.ServerProxy(server_url, allow_none=1)
pk, p = self.patient_db.first()
while 1:
server.store_patient(p)
i+=1
pk, p = self.patient_db.next()
except:
print sys.exc_info()[0], sys.exc_info()[1]
return "finished "+str(i) + " transfers"
def store_patient(self, patient, old_primary_key = 0, sync = 1):
print "storing with sync = ", sync
primary_key = self.make_primary_key(patient)
self._index_primary_key(primary_key, patient, sync)
self.patient_db[ primary_key ] = patient
self._delete_patient( old_primary_key, primary_key, sync = 0)
if sync:
self.sync()
return 1
def _delete_patient(self, old_primary_key, new_primary_key, sync = 1):
if old_primary_key and new_primary_key and new_primary_key <>
old_primary_key:
self._remove_primary_key( old_primary_key, sync = 0)
self._update_references( old_primary_key, new_primary_key )
try:
del self.patient_db[old_primary_key]
except:
print "failed to remove ", old_primary_key
if sync:
self.sync()
return 1
def sync(self):
self.patient_hashdb.sync()
self.lastname_hashdb.sync()
return 1
def find_patient(self, lastname, firstname, attributes, index = -1):
if lastname is None :
return []
l = []
lastname , firstname = lastname.lower(), firstname.lower()
dict_primary_keys = self.patient_by_lastname.get( lastname[:2] , {} )
l.extend(dict_primary_keys.keys())
l.sort()
possibilities = []
for n in l:
if n.lower().find( lastname ) == 0 and
n.lower().split('%')[1].find(firstname) ==0:
possibilities .append(n)
l = [ self.patient_db[v] for v in possibilities ]
l = [ dict( [ ( k, p.get(k, "") ) for k in attributes] ) for p in l ]
if len(l) == 1 :
return self.patient_db[ self.make_primary_key(l[0]) ]
if index > -1 and index < len(l):
return self.patient_db[ self.make_primary_key(l[index] ) ]
return l
def _do_merge_map( self, m1, m2, n):
kl1, kl2 = m1.keys(), m2.keys()
for k in kl2:
if k not in kl1: kl1.append(k)
print "kl1", kl1, " and kl2 ",kl2
new_map = {}
for k in kl1:
v1,v2 = m1.get(k, None), m2.get(k, None)
if v1 and isinstance(v1, str) : v1 = v1.strip()
if v2 and isinstance(v2, str) : v2 = v2.strip()
if v1 == {} or v1 == [] : v1 = None
if v2 == {} or v2 == [] : v2 = None
if not v1 and v2:
v = v2
elif not v2 and v1:
v = v1
elif isinstance(v1, dict ) and isinstance(v2,dict ):
v = self._do_merge_map(v1,v2, n)
elif n == 2:
v = v2
else:
v = v1
if v:
new_map[k] = v
print "="*30
print "new map is ", new_map
return new_map
def merge_patient(self, p1, p2, n):
old_pk1 = self.make_primary_key(p1)
old_pk2 = self.make_primary_key(p2)
if old_pk1 == old_pk2 :
print "Identical patient not merged"
raise ValueError, "Identical patient not merged"
new_patient = self._do_merge_map(p1, p2, n)
new_primary_key = self.make_primary_key(new_patient)
self.store_patient( new_patient, old_pk1)
if old_pk2 <> new_primary_key:
self._delete_patient( old_pk2, new_primary_key)
return new_patient
def set_drug(self, drug, allow_replace = 0):
assert( drug["name"].strip() <>"", "drug name must be nonblank")
assert( not self.med_db.has_key(drug['name']) or allow_replace <> 0,
"drugname already exists, and client not allowing
replacement")
if allow_replace <> 0:
del self.med_db[allow_replace]
self.med_db[drug["name"]] = drug
self.med_db.sync()
return True
def delete_drug(self, drugname):
if not self.med_db is None:
try:
d = self.med_db[drugname]
del self.med_db[drugname]
self.med_db.sync()
return d
except:
return "unable to delete drug", drugname
def get_drug(self, drugname):
if not self.med_db is None:
if self.med_db.has_key(drugname):
return self.med_db[drugname]
k, d = self.med_db.first()
l = []
while True:
if k.find(drugname)>=0:
l.append(d)
try:
k, d = self.med_db.next()
except:
return l
return 0
def list_drugs(self):
l = []
if not self.med_db is None:
k, d = self.med_db.first()
try:
while 1:
l.append(d)
k, d = self.med_db.next()
except:
print "at end."
return l
def fix_encounter_times(self):
#fixes time_struct objects being unserializable, and not retrievable
by xmlrpc.
pk, p = self.patient_db.first()
i = 0
while 1:
i += 1
if p.has_key("encounters"):
days = p['encounters']
for d, e in days.items():
for t, ec in e.items():
ec['time'] = [ n for n in ec['time'] ]
self.patient_db[pk] = p
try:
pk, p = self.patient_db.next()
except:
print sys.exc_info()[0], sys.exc_info()[1]
break
self.patient_db.sync()
def run_server():
from SimpleXMLRPCServer import SimpleXMLRPCServer
from popen2 import popen2
i,o = popen2("uname -a")
host = i.next().split(" ")[1]
print "server started at ", host
server = SimpleXMLRPCServer((host, 8000))
server.register_instance( PatientServer())
server.serve_forever()
def test():
s = PatientServer()
#s.fix_encounter_times()
#s.rename()
if __name__=="__main__":
#test()
run_server()
from random import SystemRandom
from string import ascii_lowercase
import datetime, time
def get_random_constanant(s):
c = ascii_lowercase[int(len(ascii_lowercase) * s.random())]
if c in ['aeiou']:
return get_random_constanant(s)
return c
vowels='aeiou'
def get_random_vowel(s):
c = vowels[int(len(vowels) * s.random())]
return c
def getword(s, n = 5):
w = ''
for i in xrange(0, n/2 + int(n * s.random()) ):
w += get_random_constanant(s)
if s.random() < .1:
w += get_random_constanant(s)
w += get_random_vowel(s)
if s.random() < .1:
w += get_random_vowel(s)
w += get_random_constanant(s)
return w
def get_date(s):
try:
return datetime.date( int(s.random()*70) + 1930,
int (s.random() * 12) + 1,
int (s.random() * 30) + 1)
except:
return get_date(s)
def get_random_demographic(s = SystemRandom() ):
d = { 'first name': getword(s), 'last name': getword(s),
'address' : str( int(s.random() * 100)) + ' ' + getword(s) + ' '+
['st', 'rd', 'ave'][int(s.random() * 3)],
'suburb': getword(s),
'postcode': str( int(s.random()*4000)+ 2000),
'state': getword(s),
'medicare_no' : str ( int(s.random() * 1000000000) + 100000000),
'birthdate' : str (get_date(s)),
'test':1
}
return d
allergens = [ 'penicillin', 'aspirin', 'sulfur drugs', 'erythromycin',
'augmentin duo', 'codeine' ]
reaction = ['rash', 'collapse', 'dyspnoea', 'nausea', 'vomiting', 'swelling']
conditions = ['hypertension', 'angina', 'niddm', 'asthma', 'coad', 'heart
failure',
'parkinsons', 'epilepsy' ]
notes = ['has headache', 'chest crackles', 'chest clear', 'ankle swelling',
'pallour',
'bp 130/70', 'irregular pulse', 'lax, nontender no masses',
'review',
'sutures', 'script' ]
meds = [ {'drug' : 'atenolol', 'instructions' : '1 daily',
'presentation': '50mg', 'qty': '30' , 'repeats':
'5' ,'duration': 'indefinite' },
{'drug' :
'salbutamol puffer', 'instructions' : '2 puffs qid prn',
'presentation': '200 doses', 'qty': '2' ,
'repeats': '5' ,'duration': 'indefinite' },
{'drug': 'simvastatin', 'instructions': '1 nocte',
'presentation' : '40mg', 'qty' : '30' ,
'repeats':'5', 'duration': 'indefinite' },
{'drug': 'diltiazem', 'instructions': '1 nocte',
'presentation' : '360mg', 'qty' : '30' ,
'repeats':'5', 'duration': 'indefinite' },
{'drug':'metformin' , 'instructions': '1 tds',
'presentation' : '500mg', 'qty' : '100' ,
'repeats':'5', 'duration': 'indefinite' }
]
def add_random_allergy(patient, s = SystemRandom()):
allergies= patient .setdefault('allergies', {} )
for i in xrange(0, int(s.random() * 3) ):
a = {}
a['allergen'] = allergens[int(s.random() * len(allergens)) ]
a['reaction'] = reaction[ int( s.random() * len ( reaction) )]
a['when'] = str( int(s.random() * 30) + 1970 )
allergies[a['allergen'] ] = a
return patient
def add_random_notes(patient, s = SystemRandom() ):
e = patient.setdefault('encounters', {})
for x in xrange(0, 20 + int(s.random() * 20)):
t= time.time() - s.random() * 100000
encounter = {'time' : [ n for n in time.localtime(t) ] }
note = []
for j in xrange(20 +int( s.random() * 20) ):
note.append( notes[int (s.random() * len(notes) )] )
encounter['notes'] = " ".join(note)
day = e.setdefault( time.strftime('%x', encounter['time']) , {})
day[time.strftime('%X', encounter['time']) ] = encounter
return patient
def add_random_meds( patient, s = SystemRandom() ):
pmeds = patient.setdefault( 'medications', {})
for i in xrange(0, 2 + int(s.random() * 7) ):
med = meds[int(s.random() * len(meds) ) ]
pmeds[med['drug']] = med
return patient
def add_random_history(patient, s = SystemRandom() ):
phistory = patient.setdefault( 'history', {})
for i in xrange(0, 2 + int(s.random() * 8) ):
n = int ( s.random() * len(conditions) )
phistory[conditions[n]] = { 'condition': conditions[n], 'onset' :
str(1970 + int(s.random() *35) ),
'active' : ['t',
'f'][int(s.random() *2)] , 'comments': 'none' }
return patient
if __name__=="__main__":
s = SystemRandom()
for i in range(0, 20):
print get_random_demographic(s)
- [Gnumed-devel] Time for a major re-think in 2005 - opinions please., Richard Terry, 2005/01/04
- Re: [Gnumed-devel] Time for a major re-think in 2005 - opinions please.,
catmat <=
- Re: [Gnumed-devel] Time for a major re-think in 2005 - opinions please., Horst Herb, 2005/01/05
- Re: [Gnumed-devel] Time for a major re-think in 2005 - opinions please., Tim Churches, 2005/01/05
- Re: [Gnumed-devel] Time for a major re-think in 2005 - opinions please., Karsten Hilbert, 2005/01/05
- Re: [Gnumed-devel] Time for a major re-think in 2005 - opinions please., Ian Haywood, 2005/01/06
- Re: [Gnumed-devel] Time for a major re-think in 2005 - opinions please., Karsten Hilbert, 2005/01/06
- Re: [Gnumed-devel] Time for a major re-think in 2005 - opinions please., Horst Herb, 2005/01/06
- Re: [Gnumed-devel] Time for a major re-think in 2005 - opinions please., Hilmar Berger, 2005/01/06
- Re: [Gnumed-devel] Time for a major re-think in 2005 - opinions please., Karsten Hilbert, 2005/01/06
- Re: [Gnumed-devel] Time for a major re-think in 2005 - opinions please., Hilmar Berger, 2005/01/07
- Re: [Gnumed-devel] Time for a major re-think in 2005 - opinions please., Karsten Hilbert, 2005/01/07