# # FileMan report generators to illustrate the use of an FMQL Endpoint. # # v0.6 [RLD] # # An FMQL endpoint is "restful" - queries are made in HTTP GETs. Responses are # JSON (application/json). # # In v0.6, the endpoint supports 6 query types, 4 for FileMan data and 3 for its schema # Data: # 1. Describe(url): describes any node in FileMan. For example, Describe("http://.../2-2") # describes the second node in PATIENT (2). # 2. SelectAllOfType(typeId): lists all nodes of a particular type. For example, SelectAllOfType("2") # lists all the patients in a VistA. # 3. SelectAllReferrers(url): lists all references to a node. For example, SelectAllReferrers("2-2") # returns all nodes that refer to the second entry in PATIENT # 4. SelectReferrersOfType(toURL, ofTypeId, byPredicate): list all referrers of # a type that reference a particular node through a specified predicate. For # example, SelectReferrersOfType("2-2","120_5",".02") returns all vital # measurements of patient 2. # # Schema: # 1. DescribeType(typeId): describes any FileMan (file) type. For example, DescribeType("2") # asks about PATIENT (2). # 2. SelectAllTypes: lists all (file) types. # 3. SelectAllReferrersToType(typeId): lists all types that refer to a type. For example, # SelectAllReferrersToType("2") returns all types that refer to PATIENT. # # Form of JSON in responses: # - the non-schema queries (Describe, SelectAllOfType, SelectAllReferrers) return a superset of # SPARQL JSON (see: http://www.w3.org/TR/rdf-sparql-json-res/) # - the key addition is "fmqlLabel". FMQL adds this display label to all values of type "uri" # - the schema queries (DescribeType, SelectAllTypes, SelectAllReferrersToType) return custom # formats. # # Note: to see how to call the endpoint from a browser/in Javascript, see the source of # fmqlRambler.html in /usr/local/fmql. # # LICENSE: # This program is free software; you can redistribute it and/or modify it under the terms of # the GNU Affero General Public License version 3 (AGPL) as published by the Free Software # Foundation. # (c) 2010 caregraf.org # import re, urllib2 # These settings should match your FMQL configuration FMQLEP = "http://www.examplehospital.com/fmqlEP" # endpoint address # FMQLEP = "http://vista.caregraf.org/fmqlEP" BASEURL = "http://www.examplehospital.com/fmql/" # ############# the patients in your VistA ################ # # What patients are known to a VistA system? What's in the "PATIENT" (2) file? # # EXAMPLE OF: # - how to get information about a type (of file) with "DescribeType", # - get a list of its instances with "SelectAllOfType" # - get details on each of those instances with "Describe" # - how errors are signalled: the reply has an "error" entry # # Note the nature of important FileMan files. Lot's of links. This is "linked data". # Only "codes"/"dictionaries" don't link out. # def reportPatients(): print "\r=== A report on the patient's in a VistA according to PATIENT (2) ===\r" queryURL = FMQLEP + "?" + "op=DescribeType&typeId=2" patientDescription = eval(urllib2.urlopen(queryURL).read()) if patientDescription.has_key("error"): print "Got error %s trying to get definition of PATIENT - exiting" % patientDescription["error"] return print "Type %s (%s): %s\r" % (patientDescription["NAME"], patientDescription["NUMBER"], patientDescription["DESC"]) queryURL = FMQLEP + "?" + "op=SelectAllOfType&typeId=2" reply = eval(urllib2.urlopen(queryURL).read()) if reply.has_key("error"): print "Got error %s trying to get contents of PATIENT - exiting" % reply["error"] return print "There are %d patients\r" % len(reply["results"]) for result in reply["results"]: print "\r----- Details on patient %s -----\r" % result["uri"]["fmqlLabel"] queryURL = FMQLEP + "?" + "op=Describe&url=" + result["uri"]["value"] reply = eval(urllib2.urlopen(queryURL).read()) if reply.has_key("error"): print "Got error %s for %s - skipping" % (reply["error"], uri) continue for field in reply["vars"]: typedValue = reply["results"][0][field] if typedValue["type"] == "fmqlbnodelist": # stay simple. Skip embedded lists. continue print "\t%s: %s\r" % (field, typedValue["value"]) # ############################ the Institutions in your VistA ##################### # # The Institutions defined in your VistA. In most, the VA's default institutions # are still there. Shouldn't these be cleaned up? # # EXAMPLE OF: # the same as "reportPatients". So why not make a template function that can report # on any file and then call that. We could have reported on patients (2) with this # template. # def reportInstitutions(): reportOnType("4") def reportOnType(typeId): queryURL = FMQLEP + "?" + "op=DescribeType&typeId=%s" % typeId typeDescription = eval(urllib2.urlopen(queryURL).read()) if typeDescription.has_key("error"): print "Got error %s trying to get definition of type %s - exiting" % (typeDescription["error"], typeId) return print "\r\r===== A report on %s (%s) =====\r" % (typeDescription["NAME"], typeDescription["NUMBER"]) print "%s\r" % typeDescription["DESC"] queryURL = FMQLEP + "?" + "op=SelectAllOfType&typeId=%s" % typeId reply = eval(urllib2.urlopen(queryURL).read()) if reply.has_key("error"): print "Got error %s trying to get contents of %s - exiting" % (typeDescription["NAME"], reply["error"]) return print "There are %d items\r" % len(reply["results"]) for result in reply["results"]: print "\r----- Details on %s -----\r" % result["uri"]["fmqlLabel"] queryURL = FMQLEP + "?" + "op=Describe&url=" + result["uri"]["value"] reply = eval(urllib2.urlopen(queryURL).read()) if reply.has_key("error"): print "Got error %s for %s - skipping" % (reply["error"], uri) continue for field in reply["vars"]: typedValue = reply["results"][0][field] if typedValue["type"] == "fmqlbnodelist": # stay simple. Skip embedded lists. continue print "\t%s: %s\r" % (field, typedValue["value"]) # ############################ Patient Referrers ########################### # # VistA has 2 "anchors" for Patient data: PATIENT (2) and IHR PATIENT (9000001). Most # of the information about a particular patient points INTO her record in these files. # (the inward reference is the most common information arrangement in FileMan and typical # of graphs in general). # # For example, Vital sign records for a patient are in GMRV VITAL MEASUREMENT (120.5). # The records of a particular patient will refer into that patient's record in # IHR PATIENT. # # So to get a picture of a full patient in VistA, get these inward references. # Then the next step (not done here, but in the VPR code below) is to DESCRIBE each # of these urls. # # EXAMPLE OF: # - Using SelectAllReferrers, the nodes that refer to a node. # - Relative links: urls don't need to be fully qualified ex/ "http://.../2-2". A # relative link ala "2-2" is enough. # def reportPatientReferrers(): patientId = "2" # second patient but this could be any. Be sure to pick a much tested fellow to see something interesting. anchorTypes = ["2", "9000001"] for anchorType in anchorTypes: print "Looking for ALL referrers to %s/%s ... can take time ..." % (anchorType, patientId) queryURL = "%s?op=SelectAllReferrers&url=%s-%s" % (FMQLEP, anchorType, patientId) reply = eval(urllib2.urlopen(queryURL).read()) if reply.has_key("error"): print "Got error %s trying to get patient %s's %s entry - exiting" % (reply["error"], patientId, anchorType) return if not len(reply["results"]): print "Patient has no referrers in %s" % anchorType continue print "There are %d\r" % len(reply["results"]) for referrer in reply["results"]: print "\tfrom %s - %s" % (referrer["refby"]["fmqlFileName"], referrer["refby"]["value"]) # ############################ Selection/'Filter' ########################### # # While filters are not supported (yet) in FMQL, you can still filter most of # what you want using "referrers". Here we ask for all the married patients by # selecting all referrers to "marital status/married". # # EXAMPLE OF: # using SelectAllReferrers to codes for selective extraction. # def reportMarriedPatients(): print "Married Patients?: all referrers to %s-%s" % ("Marital Status (11)", "Married (2)") queryURL = "%s?op=SelectAllReferrers&url=%s-%s" % (FMQLEP, "11", "2") reply = eval(urllib2.urlopen(queryURL).read()) if reply.has_key("error"): print "Got error %s trying to get married patients - exiting" % (reply["error"]) return if not len(reply["results"]): print "No married patients" return print "There are %d\r" % len(reply["results"]) for referrer in reply["results"]: print "\t%s" % (referrer["refby"]["fmqlLabel"]) # ############################ the types in your VistA ########################### # # What (file) types are in there? # # EXAMPLE OF: # using "selectAllTypes". This query type lists all the (file) types in a VistA. The # report is the same as "/schema" in the Rambler. # def reportTypes(): queryURL = FMQLEP + "?" + "op=SelectAllTypes" reply = eval(urllib2.urlopen(queryURL).read()) if reply.has_key("error"): print "Got error %s trying to get all types - exiting" % reply["error"] return print "There are %d\r" % len(reply["results"]) for i in range(len(reply["results"])): print "Entry %d\r" % i for field in reply["fields"]: if reply["results"][i].has_key(field): print "\t%s: %s\r" % (field, reply["results"][i][field]) # ############################ the types that refer to a type ########################### # # What types refer to a type? What types refer to PATIENT? It's popular - over 300 # possible references. # # EXAMPLE OF: # using "selectAllReferrersToType". This query type lists all the (file) types that # refer to a type. # def reportPatientTypeReferrers(): queryURL = FMQLEP + "?" + "op=SelectAllReferrersToType&typeId=2" reply = eval(urllib2.urlopen(queryURL).read()) if reply.has_key("error"): print "Got error %s trying to get referrers to type PATIENT - exiting" % reply["error"] return print "There are %d\r" % len(reply["results"]) for i in range(len(reply["results"])): typs = [] for possTyp in ["LOG", "EXTRACT", "AUDIT", "TRANSACTION", "REQUEST"]: if re.search(possTyp, reply["results"][i]["F"]): typs.append(possTyp) print "Type %s (%s) refers with predicate %s %s" % (reply["results"][i]["F"], reply["results"][i]["F#"], reply["results"][i]["C"], typs) # ############################ fmqlLabel and a big file ########################### # # What packages do you have? BUILD (9.6) tells you. But it's big, has lot's of entries. # We could get them all and then Describe each in turn but that will take time. # Luckily the label of each record tells us what we want - we can just count and list. # A lot of files, particular code files are like this - big but the label (fmqlLabel) # says it all. # # EXAMPLE OF: # using fmqlLabel to avoid Describing the entries in a large file # def reportBuilds(): queryURL = FMQLEP + "?" + "op=DescribeType&typeId=9_6" typeDescription = eval(urllib2.urlopen(queryURL).read()) if typeDescription.has_key("error"): print "Got error %s trying to get definition of BUILD - exiting" % typeDescription["error"] return print "\r\r===== A report on %s (%s) =====\r" % (typeDescription["NAME"], typeDescription["NUMBER"]) print "%s\r" % typeDescription["DESC"] print "... may take a moment, the file is big ..." queryURL = FMQLEP + "?" + "op=SelectAllOfType&typeId=9_6" reply = eval(urllib2.urlopen(queryURL).read()) if reply.has_key("error"): print "Got error %s trying to get contents of %s - exiting" % (typeDescription["NAME"], reply["error"]) return print "There are %d\r" % len(reply["results"]) for result in reply["results"]: print "%s\r" % result["uri"]["fmqlLabel"] # NOTE: fmqlLabel says it all # ############################# FileMan Corruption Report ######################## # # As it accesses FileMan data, an FMQL endpoint notes FileMan Corruption. This # information is accessible through the "SelectAllCorruptionReports" call. # # Note: uses a custom FMQL call. # def reportFileManCorruption(): queryURL = FMQLEP + "?" + "op=SelectAllCorruptionReports" reply = eval(urllib2.urlopen(queryURL).read()) if reply.has_key("error"): print "Got error %s trying to get Corruption info - exiting" % reply["error"] return print "\r\r===== Report on FileMan corruptions (%d) =====\r" % len(reply.keys()) for key, exp in reply.iteritems(): print "\t%s: %s\r" % (key, exp) # ############################# Main Driver #################################### import sys import getopt import time def main(): reportPatients() reportInstitutions() reportPatientReferrers() reportMarriedPatients() reportPatientTypeReferrers() reportTypes() reportBuilds() reportFileManCorruption() if __name__ == "__main__": main()