#
# 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()

