gnumed-devel
[Top][All Lists]
Advanced

[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)
        
        


reply via email to

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