parent
							
								
									c428143ef1
								
							
						
					
					
						commit
						9d53b15e4d
					
				@ -0,0 +1,16 @@
 | 
				
			||||
"""
 | 
				
			||||
(c) 2019 EDI Parser Toolkit, 
 | 
				
			||||
Health Information Privacy Lab, Vanderbilt University Medical Center
 | 
				
			||||
 | 
				
			||||
Steve L. Nyemba <steve.l.nyemba@vanderbilt.edu>
 | 
				
			||||
Khanhly Nguyen <khanhly.t.nguyen@gmail.com>
 | 
				
			||||
 | 
				
			||||
 | 
				
			||||
This code is intended to process and parse healthcare x12 837 (claims) and x12 835 (remittances) into human readable JSON format.
 | 
				
			||||
The claims/outpout can be forwarded to a NoSQL Data store like couchdb and mongodb
 | 
				
			||||
Usage :
 | 
				
			||||
    Commandline :
 | 
				
			||||
    python xreader.py --parse claims|remits --config <path>
 | 
				
			||||
    Embedded    :
 | 
				
			||||
 | 
				
			||||
"""
 | 
				
			||||
@ -0,0 +1,100 @@
 | 
				
			||||
"""
 | 
				
			||||
(c) 2019 Claims Toolkit, 
 | 
				
			||||
Health Information Privacy Lab, Vanderbilt University Medical Center
 | 
				
			||||
 | 
				
			||||
Steve L. Nyemba <steve.l.nyemba@vanderbilt.edu>
 | 
				
			||||
Khanhly Nguyen <khanhly.t.nguyen@gmail.com>
 | 
				
			||||
 | 
				
			||||
 | 
				
			||||
This code is intended to process and parse healthcare x12 837 (claims) and x12 835 (remittances) into human readable JSON format.
 | 
				
			||||
The claims/outpout can be forwarded to a NoSQL Data store like couchdb and mongodb
 | 
				
			||||
Usage :
 | 
				
			||||
    Commandline :
 | 
				
			||||
        python edi --scope --config <path> --folder <path> --store <[mongo|disk|couch]> --<db|path]> <id|path>
 | 
				
			||||
 | 
				
			||||
        with :
 | 
				
			||||
            --scope     <claims|remits>
 | 
				
			||||
            --config    path of the x12 to be parsed i.e it could be 835, or 837
 | 
				
			||||
            --folder    location of the files (they must be decompressed)
 | 
				
			||||
            --store     data store could be disk, mongodb, couchdb
 | 
				
			||||
            --db|path    name of the folder to store the output or the database name
 | 
				
			||||
    
 | 
				
			||||
    Embedded in Code   :
 | 
				
			||||
 | 
				
			||||
        import edi.parser
 | 
				
			||||
        import json
 | 
				
			||||
 | 
				
			||||
        file = '/data/claim_1.x12'
 | 
				
			||||
        conf = json.loads(open('config/837.json').read())
 | 
				
			||||
        edi.parser.get_content(filename,conf)
 | 
				
			||||
"""
 | 
				
			||||
from params import SYS_ARGS
 | 
				
			||||
from transport import factory
 | 
				
			||||
from parser import *
 | 
				
			||||
import os
 | 
				
			||||
import json
 | 
				
			||||
import sys
 | 
				
			||||
if __name__ == '__main__' :
 | 
				
			||||
    """
 | 
				
			||||
    The program was called from the command line thus we are expecting 
 | 
				
			||||
        parse   in [claims,remits]
 | 
				
			||||
        config  os.sep.path.exists(path)
 | 
				
			||||
        folder    os.sep.path.exists(path)
 | 
				
			||||
        store   store ()
 | 
				
			||||
    """
 | 
				
			||||
    p = len( set(['store','config','folder']) & set(SYS_ARGS.keys())) == 3 and ('db' in SYS_ARGS or 'path' in SYS_ARGS)
 | 
				
			||||
    TYPE = {
 | 
				
			||||
        'mongo':'mongo.MongoWriter',
 | 
				
			||||
        'couch':'couch.CouchWriter',
 | 
				
			||||
        'disk':'disk.DiskWriter'
 | 
				
			||||
    }
 | 
				
			||||
    INFO = {
 | 
				
			||||
        '837':{'scope':'claims','section':'HL'},
 | 
				
			||||
        '835':{'scope':'remits','section':'CLP'}
 | 
				
			||||
    }
 | 
				
			||||
    if p :
 | 
				
			||||
        args = {}
 | 
				
			||||
        scope = SYS_ARGS['config'][:-5].split(os.sep)[-1]
 | 
				
			||||
        CONTEXT = INFO[scope]['scope']
 | 
				
			||||
        #
 | 
				
			||||
        # @NOTE:
 | 
				
			||||
        # improve how database and data stores are handled.
 | 
				
			||||
        if SYS_ARGS['store'] == 'couch' :
 | 
				
			||||
            args = {'url': SYS_ARGS['url'] if 'url' in SYS_ARGS else 'http://localhost:5984'}
 | 
				
			||||
            args['dbname'] = SYS_ARGS['db']
 | 
				
			||||
            
 | 
				
			||||
        elif SYS_ARGS ['store'] == 'mongo':
 | 
				
			||||
            args = {'host':SYS_ARGS['host']if 'host' in SYS_ARGS else 'localhost:27217'}
 | 
				
			||||
        if SYS_ARGS['store'] in ['mongo','couch']:
 | 
				
			||||
            args['dbname'] = SYS_ARGS['db'] if 'db' in SYS_ARGS else 'claims_outcomes'
 | 
				
			||||
            args['doc'] = CONTEXT
 | 
				
			||||
 | 
				
			||||
        TYPE = TYPE[SYS_ARGS['store']] 
 | 
				
			||||
        writer = factory.instance(type=TYPE,args=args)
 | 
				
			||||
        logger = factory.instance(type=TYPE,args= dict(args,**{"doc":"logs"}))
 | 
				
			||||
        files = os.listdir(SYS_ARGS['folder'])
 | 
				
			||||
        CONFIG = json.loads(open(SYS_ARGS['config']).read())
 | 
				
			||||
        SECTION= INFO[scope]['section']
 | 
				
			||||
        for file in files :
 | 
				
			||||
            if 'limit' in SYS_ARGS and files.index(file) == int(SYS_ARGS['limit']) :
 | 
				
			||||
                break
 | 
				
			||||
            else:
 | 
				
			||||
                filename = os.sep.join([SYS_ARGS['folder'],file])
 | 
				
			||||
                
 | 
				
			||||
                try:
 | 
				
			||||
                    content,logs = get_content(filename,CONFIG,SECTION)
 | 
				
			||||
                except Exception as e:
 | 
				
			||||
                    if sys.version_info[0] > 2 :
 | 
				
			||||
                        logs = [{"filename":filename,"msg":e.args[0]}]
 | 
				
			||||
                    else:
 | 
				
			||||
                        logs = [{"filename":filename,"msg":e.message}]
 | 
				
			||||
                    content = None
 | 
				
			||||
                if content :
 | 
				
			||||
                    writer.write(row= content)
 | 
				
			||||
                if logs:
 | 
				
			||||
                    
 | 
				
			||||
                    logger.write(row=logs)
 | 
				
			||||
                
 | 
				
			||||
        pass
 | 
				
			||||
    else:
 | 
				
			||||
        print (__doc__)
 | 
				
			||||
@ -0,0 +1,18 @@
 | 
				
			||||
import sys
 | 
				
			||||
 | 
				
			||||
SYS_ARGS  = {'context':''}
 | 
				
			||||
if len(sys.argv) > 1:
 | 
				
			||||
	
 | 
				
			||||
	N = len(sys.argv)
 | 
				
			||||
	for i in range(1,N):
 | 
				
			||||
		value = None
 | 
				
			||||
		if sys.argv[i].startswith('--'):
 | 
				
			||||
			key = sys.argv[i][2:] #.replace('-','')
 | 
				
			||||
			SYS_ARGS[key] = 1
 | 
				
			||||
			if i + 1 < N:
 | 
				
			||||
				value = sys.argv[i + 1] = sys.argv[i+1].strip()
 | 
				
			||||
			if key and value:
 | 
				
			||||
				SYS_ARGS[key] = value
 | 
				
			||||
				
 | 
				
			||||
		
 | 
				
			||||
		i += 2
 | 
				
			||||
@ -0,0 +1,199 @@
 | 
				
			||||
"""
 | 
				
			||||
    (c) 2019 EDI-Parser 1.0
 | 
				
			||||
    Vanderbilt University Medical Center, Health Information Privacy Laboratory
 | 
				
			||||
    https://hiplab.mc.vanderbilt.edu/tools
 | 
				
			||||
 | 
				
			||||
 | 
				
			||||
    Authors:
 | 
				
			||||
        Khanhly Nguyen, 
 | 
				
			||||
        Steve L. Nyemba<steve.l.nyemba@vanderbilt.edu>
 | 
				
			||||
 | 
				
			||||
    License:
 | 
				
			||||
        MIT, terms are available at https://opensource.org/licenses/MIT
 | 
				
			||||
 | 
				
			||||
    This parser was originally written by Khanhly Nguyen for her internship and is intended to parse x12 835,837 and others provided the appropriate configuration
 | 
				
			||||
    USAGE :
 | 
				
			||||
        - COMMAND LINE
 | 
				
			||||
        
 | 
				
			||||
        - EMBEDDED
 | 
				
			||||
"""
 | 
				
			||||
import os
 | 
				
			||||
import sys
 | 
				
			||||
def split(row,sep='*',prefix='HI'):
 | 
				
			||||
    """
 | 
				
			||||
    This function is designed to split an x12 row and 
 | 
				
			||||
    """
 | 
				
			||||
    if row.startswith(prefix) is False:
 | 
				
			||||
        value = []
 | 
				
			||||
        for row_value in row.replace('~','').split(sep) :
 | 
				
			||||
            
 | 
				
			||||
            if '>' in row_value :
 | 
				
			||||
                if row_value.startswith('HC') or row_value.startswith('AD'):
 | 
				
			||||
                
 | 
				
			||||
                    value += row_value.split('>')[:2]
 | 
				
			||||
                else:
 | 
				
			||||
                    value += row_value.split('>')
 | 
				
			||||
            else :
 | 
				
			||||
                value.append(row_value)
 | 
				
			||||
        return [xchar.replace('\r','') for xchar in value] #row.replace('~','').split(sep)
 | 
				
			||||
    else:
 | 
				
			||||
        
 | 
				
			||||
        return [ [prefix]+ split(item,'>') for item in row.replace('~','').split(sep)[1:] ]
 | 
				
			||||
def get_config(config,row):
 | 
				
			||||
    """
 | 
				
			||||
    This function will return the meaningfull parts of the configuration for a given item
 | 
				
			||||
    """
 | 
				
			||||
    _row = list(row) if type(row[0]) == str else list(row[0])
 | 
				
			||||
    _info = config[_row[0]] if _row[0] in config else {}
 | 
				
			||||
    key = None
 | 
				
			||||
    if '@ref' in _info:
 | 
				
			||||
        key = list(set(_row) & set(_info['@ref'].keys()))
 | 
				
			||||
        if key :
 | 
				
			||||
            key  = key[0]
 | 
				
			||||
            return _info['@ref'][key]
 | 
				
			||||
        else:
 | 
				
			||||
            return {}
 | 
				
			||||
        
 | 
				
			||||
    if not _info and 'SIMILAR' in config:
 | 
				
			||||
        #
 | 
				
			||||
        # Let's look for the nearest key using the edit distance
 | 
				
			||||
        if _row[0] in config['SIMILAR']    :
 | 
				
			||||
            key = config['SIMILAR'][_row[0]]
 | 
				
			||||
            _info = config[key]
 | 
				
			||||
    return _info
 | 
				
			||||
def format_date(value) :
 | 
				
			||||
    year = value[:4]
 | 
				
			||||
    month = value[4:6]
 | 
				
			||||
    day = value[6:]
 | 
				
			||||
    return "-".join([year,month,day])[:10] #{"year":year,"month":month,"day":day}
 | 
				
			||||
def format_time(value):
 | 
				
			||||
    return ":".join([value[:2],value[2:] ])[:5]
 | 
				
			||||
def format_proc(value):
 | 
				
			||||
    if ':' in value :
 | 
				
			||||
        return {"procedure_type":value.split(':')[0].strip(),"procedure_code":value.split(':')[1].strip()}
 | 
				
			||||
    else:
 | 
				
			||||
        return value
 | 
				
			||||
 | 
				
			||||
def map(row,config,version):
 | 
				
			||||
    
 | 
				
			||||
    label = config['label'] if 'label' in config else None    
 | 
				
			||||
    
 | 
				
			||||
    omap = config['map'] if version not in config else config[version]
 | 
				
			||||
    anchors = config['anchors'] if 'anchors' in config else []
 | 
				
			||||
    if type(row[0]) == str:
 | 
				
			||||
        object_value = {}
 | 
				
			||||
        for key in omap :
 | 
				
			||||
            index = omap[key]
 | 
				
			||||
            if anchors and set(anchors) & set(row):
 | 
				
			||||
                _key = list(set(anchors) & set(row))[0]
 | 
				
			||||
                
 | 
				
			||||
                aindex = row.index(_key)
 | 
				
			||||
                index = aindex +  index
 | 
				
			||||
 | 
				
			||||
            if index < len(row) :
 | 
				
			||||
                value = row[index] 
 | 
				
			||||
                if 'cast' in config and key in config['cast'] and value.strip() != '' :
 | 
				
			||||
                    value = eval(config['cast'][key])(value)
 | 
				
			||||
                    
 | 
				
			||||
                    pass
 | 
				
			||||
                if 'syn' in config and value in config['syn'] :
 | 
				
			||||
                    value = config['syn'][value]
 | 
				
			||||
                if type(value) == dict :
 | 
				
			||||
                    object_value = dict(object_value, **value)
 | 
				
			||||
                else:
 | 
				
			||||
                    object_value[key] = value
 | 
				
			||||
    else:
 | 
				
			||||
        #
 | 
				
			||||
        # we are dealing with a complex object
 | 
				
			||||
        object_value = []
 | 
				
			||||
        for row_item in row :
 | 
				
			||||
            object_value.append( list(map(row_item,config,version)))
 | 
				
			||||
        # object_value = {label:object_value}
 | 
				
			||||
    return object_value
 | 
				
			||||
 | 
				
			||||
def get_locations(x12_file,section='HL') :
 | 
				
			||||
    locations = []
 | 
				
			||||
    for line in x12_file :
 | 
				
			||||
        
 | 
				
			||||
        if line.strip().startswith(section) :
 | 
				
			||||
            i = x12_file.index(line)
 | 
				
			||||
            locations.append(i)
 | 
				
			||||
    return locations
 | 
				
			||||
 | 
				
			||||
#def get_claims(filename,config,section) :
 | 
				
			||||
def get_content(filename,config,section=None) :
 | 
				
			||||
    """
 | 
				
			||||
    This function returns the of the EDI file parsed given the configuration specified
 | 
				
			||||
    :section    loop prefix (HL, CLP)
 | 
				
			||||
    :config     configuration with formatting rules, labels ...
 | 
				
			||||
    :filename   location of the file
 | 
				
			||||
    """
 | 
				
			||||
    section = section if section else config['SECTION']
 | 
				
			||||
    x12_file = open(filename).read().split('\n')
 | 
				
			||||
    if len(x12_file) == 1 :
 | 
				
			||||
        
 | 
				
			||||
        x12_file = x12_file[0].split('~')
 | 
				
			||||
        
 | 
				
			||||
 | 
				
			||||
    locations = get_locations(x12_file,section)
 | 
				
			||||
    claims = []
 | 
				
			||||
     
 | 
				
			||||
    logs = []
 | 
				
			||||
    
 | 
				
			||||
    # VERSION = x12_file[2].split('*')[3].replace('~','')    
 | 
				
			||||
    VERSION = x12_file[1].split('*')[-1].replace('~','')    
 | 
				
			||||
    
 | 
				
			||||
    row = split(x12_file[3])
 | 
				
			||||
    _info = get_config(config,row)
 | 
				
			||||
    _default_value = list(map(row,_info,VERSION)) if _info else None
 | 
				
			||||
    N = len(locations)
 | 
				
			||||
 | 
				
			||||
    for index in range(0,N-1):
 | 
				
			||||
        beg = locations[index]
 | 
				
			||||
        end = locations[index+1]
 | 
				
			||||
        claim = {}
 | 
				
			||||
        for row in x12_file[beg:end] :
 | 
				
			||||
            row = split(row)
 | 
				
			||||
            _info = get_config(config,row)
 | 
				
			||||
            if _info :
 | 
				
			||||
                try:                    
 | 
				
			||||
                    # tmp = map(row,_info,VERSION)
 | 
				
			||||
                    tmp = list(map(row,_info,VERSION))
 | 
				
			||||
                except Exception as e:                    
 | 
				
			||||
                    if sys.verion_info[0] > 2 :
 | 
				
			||||
                        logs.append ({"version":VERSION,"filename":filename,"msg":e.args[0],"X12":x12_file[beg:end]})
 | 
				
			||||
                    else:
 | 
				
			||||
                        logs.append ({"version":VERSION,"filename":filename,"msg":e.message,"X12":x12_file[beg:end]})
 | 
				
			||||
                    claim = {}
 | 
				
			||||
                    break
 | 
				
			||||
                
 | 
				
			||||
                if 'label' not in _info :
 | 
				
			||||
                    tmp['version'] = VERSION                    
 | 
				
			||||
                    claim = dict(claim, **tmp)
 | 
				
			||||
                    
 | 
				
			||||
                    
 | 
				
			||||
                else:
 | 
				
			||||
                    label = _info['label']
 | 
				
			||||
                    if type(tmp) == list :
 | 
				
			||||
                        
 | 
				
			||||
                        claim[label] = tmp if label not in claim else claim[label] + tmp
 | 
				
			||||
                    else:
 | 
				
			||||
                        if label not in claim:                    
 | 
				
			||||
                            claim[label] = [tmp]
 | 
				
			||||
                        elif len(list(tmp.keys())) == 1 :
 | 
				
			||||
                            # print "\t",len(claim[label]),tmp
 | 
				
			||||
                            index = len(claim[label]) -1 
 | 
				
			||||
                            claim[label][index] = dict(claim[label][index],**tmp)
 | 
				
			||||
                        else:
 | 
				
			||||
                            claim[label].append(tmp)
 | 
				
			||||
        
 | 
				
			||||
        if claim and 'claim_id' in claim:
 | 
				
			||||
            
 | 
				
			||||
            claim = dict(claim,**_default_value)
 | 
				
			||||
            claim['name'] = filename[:-5].split(os.sep)[-1] #.replace(ROOT,'')
 | 
				
			||||
            claim['index'] = index
 | 
				
			||||
            claims.append(claim)
 | 
				
			||||
            
 | 
				
			||||
            
 | 
				
			||||
    
 | 
				
			||||
    return claims,logs
 | 
				
			||||
					Loading…
					
					
				
		Reference in new issue