healthcareio.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. #!/usr/bin/env python3
  2. """
  3. (c) 2019 Claims Toolkit,
  4. Health Information Privacy Lab, Vanderbilt University Medical Center
  5. Steve L. Nyemba <steve.l.nyemba@vanderbilt.edu>
  6. Khanhly Nguyen <khanhly.t.nguyen@gmail.com>
  7. This code is intended to process and parse healthcare x12 837 (claims) and x12 835 (remittances) into human readable JSON format.
  8. The claims/outpout can be forwarded to a NoSQL Data store like couchdb and mongodb
  9. Usage :
  10. Commandline :
  11. python edi-parser --scope --config <path> --folder <path> --store <[mongo|disk|couch]> --<db|path]> <id|path>
  12. with :
  13. --scope <claims|remits>
  14. --config path of the x12 to be parsed i.e it could be 835, or 837
  15. --folder location of the files (they must be decompressed)
  16. --store data store could be disk, mongodb, couchdb
  17. --db|path name of the folder to store the output or the database name
  18. Embedded in Code :
  19. import edi.parser
  20. import json
  21. file = '/data/claim_1.x12'
  22. conf = json.loads(open('config/837.json').read())
  23. edi.parser.get_content(filename,conf)
  24. """
  25. from params import SYS_ARGS
  26. from transport import factory
  27. import requests
  28. from parser import get_content
  29. import os
  30. import json
  31. import sys
  32. PATH = os.sep.join([os.environ['HOME'],'.healthcareio'])
  33. OUTPUT_FOLDER = os.sep.join([os.environ['HOME'],'healthcare-io'])
  34. INFO = None
  35. URL = "https://healthcareio.the-phi.com"
  36. if not os.path.exists(PATH) :
  37. os.mkdir(PATH)
  38. import platform
  39. import sqlite3 as lite
  40. # PATH = os.sep.join([os.environ['HOME'],'.edi-parser'])
  41. def register (**args) :
  42. """
  43. :email user's email address
  44. :url url of the provider to register
  45. """
  46. email = args['email']
  47. url = args['url'] if 'url' in args else URL
  48. folders = [PATH,OUTPUT_FOLDER]
  49. for path in folders :
  50. if not os.path.exists(path) :
  51. os.mkdir(path)
  52. #
  53. #
  54. headers = {"email":email,"client":platform.node()}
  55. http = requests.session()
  56. r = http.post(url,headers=headers)
  57. #
  58. # store = {"type":"disk.DiskWriter","args":{"path":OUTPUT_FOLDER}}
  59. # if 'store' in args :
  60. # store = args['store']
  61. filename = (os.sep.join([PATH,'config.json']))
  62. info = r.json() #{"parser":r.json(),"store":store}
  63. info = dict({"owner":email},**info)
  64. info['store']['args']['path'] =os.sep.join([OUTPUT_FOLDER,'healthcare-io.db3']) #-- sql
  65. info['out-folder'] = OUTPUT_FOLDER
  66. file = open( filename,'w')
  67. file.write( json.dumps(info))
  68. file.close()
  69. #
  70. # Create the sqlite3 database to
  71. def analytics(**args):
  72. """
  73. This fucntion will only compute basic distributions of a given feature for a given claim
  74. @args
  75. @param x: vector of features to process
  76. @param apply: operation to be applied {dist}
  77. """
  78. if args['apply'] in ['dist','distribution'] :
  79. """
  80. This section of the code will return the distribution of a given space.
  81. It is intended to be applied on several claims/remits
  82. """
  83. x = pd.DataFrame(args['x'],columns=['x'])
  84. return x.groupby(['x']).size().to_frame().T.to_dict(orient='record')
  85. def log(**args):
  86. """
  87. This function will perform a log of anything provided to it
  88. """
  89. pass
  90. def init():
  91. """
  92. read all the configuration from the
  93. """
  94. filename = os.sep.join([PATH,'config.json'])
  95. info = None
  96. if os.path.exists(filename):
  97. file = open(filename)
  98. info = json.loads(file.read())
  99. if not os.path.exists(info['out-folder']) :
  100. os.mkdir(info['out-folder'])
  101. if not os.path.exists(info['store']['args']['path']) :
  102. conn = lite.connect(info['store']['args']['path'],isolation_level=None)
  103. for key in info['schema'] :
  104. _sql = info['schema'][key]['create']
  105. # r = conn.execute("select * from sqlite_master where name in ('claims','remits')")
  106. conn.execute(_sql)
  107. conn.commit()
  108. conn.close()
  109. return info
  110. #
  111. # Global variables that load the configuration files
  112. def parse(**args):
  113. """
  114. This function will parse the content of a claim or remittance (x12 format) give the following parameters
  115. :filename absolute path of the file to be parsed
  116. :type claims|remits in x12 format
  117. """
  118. global INFO
  119. if not INFO :
  120. INFO = init()
  121. if args['type'] == 'claims' :
  122. CONFIG = INFO['parser']['837']
  123. elif args['type'] == 'remits' :
  124. CONFIG = INFO['parser']['835']
  125. else:
  126. CONFIG = None
  127. if CONFIG :
  128. # CONFIG = CONFIG[-1] if 'version' not in args and (args['version'] < len(CONFIG)) else CONFIG[0]
  129. CONFIG = CONFIG[int(args['version'])-1] if 'version' in SYS_ARGS and int(SYS_ARGS['version']) < len(CONFIG) else CONFIG[-1]
  130. SECTION = CONFIG['SECTION']
  131. os.environ['HEALTHCAREIO_SALT'] = INFO['owner']
  132. return get_content(args['filename'],CONFIG,SECTION)
  133. def upgrade(**args):
  134. """
  135. :email provide us with who you are
  136. :key upgrade key provided by the server for a given email
  137. """
  138. url = args['url'] if 'url' in args else URL+"/upgrade"
  139. headers = {"key":args['key'],"email":args["email"],"url":url}
  140. if __name__ == '__main__' :
  141. info = init()
  142. if 'out-folder' in SYS_ARGS :
  143. OUTPUT_FOLDER = SYS_ARGS['out-folder']
  144. if set(list(SYS_ARGS.keys())) & set(['signup','init']):
  145. #
  146. # This command will essentially get a new copy of the configurations
  147. # @TODO: Tie the request to a version ?
  148. #
  149. email = SYS_ARGS['signup'].strip() if 'signup' in SYS_ARGS else SYS_ARGS['init']
  150. url = SYS_ARGS['url'] if 'url' in SYS_ARGS else 'https://healthcareio.the-phi.com'
  151. register(email=email,url=url)
  152. # else:
  153. # m = """
  154. # usage:
  155. # healthcareio --signup --email myemail@provider.com [--url <host>]
  156. # """
  157. # print (m)
  158. elif 'upgrade' in SYS_ARGS :
  159. #
  160. # perform an upgrade i.e some code or new parsers information will be provided
  161. #
  162. pass
  163. elif 'parse' in SYS_ARGS and info:
  164. """
  165. In this section of the code we are expecting the user to provide :
  166. :folder location of the files to process or file to process
  167. :
  168. """
  169. files = []
  170. if 'file' in SYS_ARGS :
  171. files = [SYS_ARGS['file']] if not os.path.isdir(SYS_ARGS['file']) else []
  172. if 'folder' in SYS_ARGS and os.path.exists(SYS_ARGS['folder']):
  173. names = os.listdir(SYS_ARGS['folder'])
  174. files += [os.sep.join([SYS_ARGS['folder'],name]) for name in names if not os.path.isdir(os.sep.join([SYS_ARGS['folder'],name]))]
  175. else:
  176. #
  177. # raise an erro
  178. pass
  179. #
  180. # @TODO: Log this here so we know what is being processed or not
  181. SCOPE = None
  182. if files and ('claims' in SYS_ARGS['parse'] or 'remits' in SYS_ARGS['parse']):
  183. # _map = {'claims':'837','remits':'835'}
  184. # key = _map[SYS_ARGS['parse']]
  185. # CONFIG = info['parser'][key]
  186. # if 'version' in SYS_ARGS and int(SYS_ARGS['version']) < len(CONFIG) :
  187. # CONFIG = CONFIG[ int(SYS_ARGS['version'])]
  188. # else:
  189. # CONFIG = CONFIG[-1]
  190. if info['store']['type'] == 'disk.DiskWriter' :
  191. info['store']['args']['path'] += (os.sep + 'healthcare-io.json')
  192. elif info['store']['type'] == 'disk.SQLiteWriter' :
  193. # info['store']['args']['path'] += (os.sep + 'healthcare-io.db3')
  194. pass
  195. info['store']['args']['table'] = SYS_ARGS['parse'].strip().lower()
  196. writer = factory.instance(**info['store'])
  197. logger = factory.instance(type='disk.DiskWriter',args={'path':os.sep.join([info['out-folder'],SYS_ARGS['parse']+'.log'])})
  198. #logger = factory.instance(type='mongo.MongoWriter',args={'db':'healthcareio','doc':SYS_ARGS['parse']+'_logs'})
  199. # schema = info['schema']
  200. # for key in schema :
  201. # sql = schema[key]['create']
  202. # writer.write(sql)
  203. for filename in files :
  204. if filename.strip() == '':
  205. continue
  206. # content,logs = get_content(filename,CONFIG,CONFIG['SECTION'])
  207. #
  208. try:
  209. content,logs = parse(filename = filename,type=SYS_ARGS['parse'])
  210. if content :
  211. writer.write(content)
  212. if logs :
  213. [logger.write(_row) for _row in logs]
  214. else:
  215. logger.write({"name":filename,"completed":True,"rows":len(content)})
  216. except Exception as e:
  217. logger.write({"filename":filename,"completed":False,"rows":-1})
  218. # print ([filename,len(content)])
  219. #
  220. # @TODO: forward this data to the writer and log engine
  221. #
  222. pass
  223. elif 'export' in SYS_ARGS:
  224. #
  225. # this function is designed to export the data to csv
  226. #
  227. format = SYS_ARGS['format'] if 'format' in SYS_ARGS else 'csv'
  228. format = format.lower()
  229. if set([format]) not in ['xls','csv'] :
  230. format = 'csv'
  231. pass
  232. # """
  233. # The program was called from the command line thus we are expecting
  234. # parse in [claims,remits]
  235. # config os.sep.path.exists(path)
  236. # folder os.sep.path.exists(path)
  237. # store store ()
  238. # """
  239. # p = len( set(['store','config','folder']) & set(SYS_ARGS.keys())) == 3 and ('db' in SYS_ARGS or 'path' in SYS_ARGS)
  240. # TYPE = {
  241. # 'mongo':'mongo.MongoWriter',
  242. # 'couch':'couch.CouchWriter',
  243. # 'disk':'disk.DiskWriter'
  244. # }
  245. # INFO = {
  246. # '837':{'scope':'claims','section':'HL'},
  247. # '835':{'scope':'remits','section':'CLP'}
  248. # }
  249. # if p :
  250. # args = {}
  251. # scope = SYS_ARGS['config'][:-5].split(os.sep)[-1]
  252. # CONTEXT = INFO[scope]['scope']
  253. # #
  254. # # @NOTE:
  255. # # improve how database and data stores are handled.
  256. # if SYS_ARGS['store'] == 'couch' :
  257. # args = {'url': SYS_ARGS['url'] if 'url' in SYS_ARGS else 'http://localhost:5984'}
  258. # args['dbname'] = SYS_ARGS['db']
  259. # elif SYS_ARGS ['store'] == 'mongo':
  260. # args = {'host':SYS_ARGS['host']if 'host' in SYS_ARGS else 'localhost:27017'}
  261. # if SYS_ARGS['store'] in ['mongo','couch']:
  262. # args['dbname'] = SYS_ARGS['db'] if 'db' in SYS_ARGS else 'claims_outcomes'
  263. # args['doc'] = CONTEXT
  264. # TYPE = TYPE[SYS_ARGS['store']]
  265. # writer = factory.instance(type=TYPE,args=args)
  266. # if SYS_ARGS['store'] == 'disk':
  267. # writer.init(path = 'output-claims.json')
  268. # logger = factory.instance(type=TYPE,args= dict(args,**{"doc":"logs"}))
  269. # files = os.listdir(SYS_ARGS['folder'])
  270. # CONFIG = json.loads(open(SYS_ARGS['config']).read())
  271. # SECTION = INFO[scope]['section']
  272. # for file in files :
  273. # if 'limit' in SYS_ARGS and files.index(file) == int(SYS_ARGS['limit']) :
  274. # break
  275. # else:
  276. # filename = os.sep.join([SYS_ARGS['folder'],file])
  277. # try:
  278. # content,logs = get_content(filename,CONFIG,SECTION)
  279. # except Exception as e:
  280. # if sys.version_info[0] > 2 :
  281. # logs = [{"filename":filename,"msg":e.args[0]}]
  282. # else:
  283. # logs = [{"filename":filename,"msg":e.message}]
  284. # content = None
  285. # if content :
  286. # writer.write(content)
  287. # if logs:
  288. # logger.write(logs)
  289. # pass
  290. # else:
  291. # print (__doc__)