__init__.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579
  1. """
  2. This function contains code to load plugins, the idea of a plugin is that it is designed to load, functions written outside of the scope of the parser.
  3. This enables better parsing, and the creation of a pipeline that can perform pre/post conditions given that {x12} is organized in loops
  4. Contract:
  5. Functions will take in an object (the row of the claim they are intended to parse), and prior rows (parsed)
  6. """
  7. import os
  8. import importlib as IL
  9. # import imp
  10. import sys
  11. from healthcareio import x12
  12. class parser:
  13. """
  14. This is a decorator used against the plugins to determine which function get applied against a given x12 element
  15. The following are mandatory :
  16. {element,map} or {element,columns},
  17. - Providing the map attribute will invoke the built-in parser
  18. - not providing the built-in parser will suggest that the code will handle parsing itself and thus must inform of the attributes expected
  19. """
  20. def __init__(self,**_args):
  21. self.element = _args['element']
  22. self.x12 = _args['x12'] if 'x12' in _args else '*'
  23. self.parent = _args['parent'] if 'parent' in _args else None
  24. self.map = None if 'map' not in _args else _args['map']
  25. self.cols = None if 'columns' not in _args else _args['columns']
  26. self.anchor = None if 'anchor' not in _args else _args['anchor']
  27. if 'field' in _args :
  28. self.field = _args['field']
  29. elif 'container' in _args :
  30. self.container = _args['container']
  31. def __call__(self, pointer):
  32. def wrapper(**args): #(*args, **kwargs):
  33. #
  34. # @TODO: Log this in case we have an error
  35. return pointer(**args) #(*args, **kwargs)
  36. #
  37. #-- These attributes will be read by the factory class to make sure the functions are assigned the data they are designed for
  38. setattr(wrapper,'element',self.element)
  39. setattr(wrapper,'x12',self.x12)
  40. setattr(wrapper,'parent',self.parent)
  41. _meta = {'element':self.element,'x12':self.x12,'parent':self.parent}
  42. if self.cols :
  43. setattr(wrapper,'columns',self.cols)
  44. _meta['columns'] = self.cols
  45. elif self.map :
  46. setattr(wrapper,'map',self.map)
  47. _meta['map'] = self.map
  48. if hasattr(self,'container') :
  49. setattr(wrapper,'container',self.container)
  50. _meta['container'] = self.container
  51. if hasattr(self,'field') :
  52. setattr(wrapper,'field',self.field)
  53. _meta['field'] = self.field
  54. if hasattr(self,'anchor') and self.anchor:
  55. _meta['anchor'] = self.anchor
  56. setattr(wrapper,'anchor',self.anchor)
  57. setattr(wrapper,'meta',_meta)
  58. #
  59. # clean up here ....
  60. # 1. if anchor no field|containers
  61. # 2. if parent no field or containers
  62. # 3. field and containers can't be
  63. return wrapper
  64. DEFAULT_PLUGINS='healthcareio.x12.plugins.default'
  65. def isplugin(_module,_name) :
  66. """
  67. This function for a given module returns a True if a function is a plugin if
  68. :_module module
  69. :_name a given name of a resource in a module
  70. """
  71. p = type(getattr(_module,_name)).__name__ =='function' # is a function
  72. q = hasattr(getattr(_module,_name),'element') # has element {x12}
  73. r = hasattr(getattr(_module,_name),'x12') # has an {x12} type i.e *,835,837
  74. return p and q and r
  75. def build_map (**_args):
  76. """
  77. This function builds the map of {x12,element,pointer} to enable rapid access to parsing fucntions
  78. :module module object as returned framework (importlib or imp)
  79. """
  80. _plugins = [getattr(_args['module'],_name) for _name in dir(_args['module']) if isplugin(_args['module'],_name)]
  81. _map = {}
  82. _parents = {}
  83. for _item in _plugins :
  84. _id = _item.x12
  85. if _id not in _map :
  86. _map[_id] = {}
  87. _element = _item.element
  88. if _item.parent :
  89. if _item.parent not in _parents :
  90. _parents[_item.parent] = []
  91. _parents[_item.parent].append (_item.element)
  92. if type(_element) == list :
  93. for _e in _element :
  94. _map[_id][_e.strip()] = _item
  95. else:
  96. _map[_id][_element]= _item
  97. return _map,_parents
  98. def instance(**_args):
  99. """
  100. This function returns an dictionary/map of functions available for execution
  101. The functions are organized as follows: {835:{BHT:_pointer,NM1:_pointer}}
  102. :_args
  103. - path provides the location (folder|file) to be extracted
  104. """
  105. #
  106. # Loading one of the default functions built-in ...
  107. #
  108. _module = IL.import_module(DEFAULT_PLUGINS)
  109. _map,_parents = build_map(module=_module)
  110. #
  111. # loaded the common functions, now load domain specific ones one
  112. #
  113. _packages = ['remits','claims']
  114. _handler = x12.util.document.Builder(parents={},plugins={})
  115. for _x12 in _packages :
  116. if hasattr(_module,_x12) :
  117. _ixmap,_ixparents = build_map(module=getattr(_module,_x12))
  118. _map = _handler.merge(_map,_ixmap)
  119. _parents = _handler.merge(_parents,_ixparents)
  120. #
  121. # consolidate, the common elements across the ...
  122. # We override any common processing element with the version specific element
  123. _map['835'] = _handler.merge(_map['*'],_map['835'])
  124. _map['837'] = _handler.merge(_map['*'],_map['837'])
  125. if 'path' in _args:
  126. #
  127. # We can/will override the default modules given the user has provided a location
  128. # _module = imp.load_source('udf',_args['path'])
  129. _module = IL.machinery.SourcefileLoader('udf',_args['path']).load_module()
  130. _udf_map,_udfp = build_map(module=_module)
  131. _map = dict(_map,**_udf_map)
  132. for key in _udfp:
  133. if key not in _parents :
  134. _parents[key] = _udfp[key]
  135. else:
  136. _parents[key] = _parents[key] + _udfp[key]
  137. if 'filter' in _args :
  138. return filter(elements = _args['filter'],plugins=_map)
  139. # return _smap
  140. return _map,_parents
  141. def merge (_x,_y):
  142. """
  143. This function will merge two objects _x, _y
  144. """
  145. _zcols = list(set(_x.keys()) & set(_y.keys())) #--common columns
  146. if _zcols :
  147. _out = dict(_x,**{})
  148. for _key in list(_y.keys()) :
  149. if _key not in _zcols and _key:
  150. _out[_key] = _y[_key]
  151. else:
  152. if type(_out[_key]) == list :
  153. for value in _y[_key] :
  154. if value not in _out[_key] :
  155. _out[_key].append(value)
  156. # _out[_key] += _y[_key]
  157. elif type(_out[_key]) == dict:
  158. _out[_key] = dict(_out[_key],**_y[_key])
  159. else:
  160. _out[_key] = _y[_key]
  161. return _out
  162. else:
  163. return dict(_x,**_y)
  164. # def template(**_args) :
  165. # """
  166. # This function generates an object template to be used in object assignment and export functionalities
  167. # We chose to proceed in this manner so as to enforce consistency of the parser
  168. # :plugins {*,837,835} with element and pointers associated
  169. # """
  170. # _plugins = _args['plugins']
  171. # _object = {'837':{},'835':{}}
  172. # for _x12 in _plugins :
  173. # _pointers = _plugins[_x12]
  174. # for _element in _pointers :
  175. # _meta = _pointers[_element].meta
  176. # _values = _meta['map'].values() if 'map' in _meta else _meta['columns']
  177. # #
  178. # # where do the attributes go ..
  179. # #
  180. # _attr = []
  181. # for _item in list(_values) :
  182. # if type(_item) == list :
  183. # _attr = _attr + _item
  184. # else:
  185. # _attr.append(_item)
  186. # _field = []
  187. # if 'field' in _meta or 'container' in _meta :
  188. # _field = _meta['field'] if 'field' in _meta else _meta['container']
  189. # if 'anchor' in _meta : #-- No parents are expected
  190. # _field = _meta['anchor'].values()
  191. # elif _meta['parent'] :
  192. # #
  193. # # It means the attributes will be
  194. # _parentPlug = filter(elements=[_meta['parent']],plugins=_plugins)
  195. # _pid = list(_parentPlug.keys())[0]
  196. # _parentMeta = _parentPlug[_pid][_meta['parent']].meta
  197. # _attr = _attr + list(_parentMeta['map'].values()) if 'map' in _parentMeta else _parentMeta['columns']
  198. # if 'anchor' in _parentMeta :
  199. # _field = list(_parentMeta['anchor'].values())
  200. # _field = [_field] if type(_field) == str else _field
  201. # _attr = dict.fromkeys(_attr,'')
  202. # if not _field :
  203. # _info = (_attr)
  204. # else:
  205. # _info = (dict.fromkeys(_field,_attr))
  206. # if _x12 == '*' :
  207. # _object['837']= merge(_object['837'], _info)
  208. # _object['835']= merge (_object['835'], _info)
  209. # else:
  210. # _object[_x12] = merge(_object[_x12],_info)
  211. # return _object
  212. def filter (**_args) :
  213. _elements = _args['elements']
  214. _plugins = _args['plugins']
  215. _map = {}
  216. for x12 in _plugins :
  217. _item = _plugins[x12]
  218. _found = list(set(_elements) & set(_item.keys()))
  219. if _found :
  220. _map[x12] = {key:_item[key] for key in _found }
  221. return _map
  222. def getTableName(**_args) :
  223. _plugins = _args['plugins']
  224. _meta = _args['meta']
  225. _x12 = _meta['x12']
  226. _foreignkeys = _args['tableKeys']
  227. _attributes = list(_meta['map'].values()) if 'map' in _meta else _meta['columns']
  228. if 'field' in _meta or 'container' in _meta:
  229. _tableName = _meta['field'] if 'field' in _meta else _meta['container']
  230. # _table = {_id:_attributes}
  231. elif 'anchor' in _meta :
  232. _tableName = _meta['anchor'].values()
  233. # for _name in _meta['anchor'].values() :
  234. # _table[_name] = _attributes
  235. elif 'parent' in _meta and _meta['parent']:
  236. #
  237. # We can have a parent with no field/container/anchor
  238. # We expect either a map or columns ...
  239. #
  240. _parentElement = _meta['parent']
  241. _parentMeta = _plugins[_x12][_parentElement].meta
  242. _parentTable = getTableName(plugins=_plugins,meta = _parentMeta,tableKeys=_foreignkeys)
  243. _tableName = list(_parentTable.keys())[0]
  244. # _table[_id] = _parentTable[_id] + _attributes
  245. _attributes = _parentTable[_tableName] + _attributes
  246. # print (_meta)
  247. else:
  248. #
  249. # baseline tables have no parent, we need to determine the name
  250. #
  251. _tableName = 'claims' if _x12 == '837' else 'remits'
  252. # print (_id,_attributes)
  253. pass
  254. #
  255. # Are there any anchors
  256. if _x12 == '837':
  257. _keys = [_foreignkeys['claims']]
  258. elif _x12 == '835' :
  259. _keys = [_foreignkeys['remits']]
  260. else:
  261. _keys = list(set(_foreignkeys.values()))
  262. _attr = []
  263. for _item in _attributes :
  264. if type(_item) == list :
  265. _attr += _item
  266. else:
  267. _attr.append(_item)
  268. _keys = list(set(_keys) - set(_attr))
  269. _attr = _keys + _attr
  270. # if 'container' in _meta and _meta['container'] == 'procedures' :
  271. # print (_attributes)
  272. _tableName = [_tableName] if type(_tableName) == str else _tableName
  273. return dict.fromkeys(_tableName,_attr)
  274. def _getTableName (**_args):
  275. """
  276. This function provides a list of attributes associated with an entity
  277. The function infers a relational structure from the JSON representation of a claim and plugin specifications
  278. """
  279. _meta = _args['meta']
  280. _xattr = list(_meta['map'].values()) if 'map' in _meta else _meta['columns']
  281. _plugins = _args['plugins']
  282. _foreignkeys = _args['tableKeys']
  283. #
  284. # Fix attributes, in case we have an index associated with multiple fields
  285. #
  286. _attr = []
  287. if 'anchor' not in _meta and not _meta['parent']:
  288. for _item in _xattr :
  289. _attr += _item if type(_item) == list else [_item]
  290. _name = None
  291. _info = {}
  292. _infoparent = {}
  293. if 'field' in _meta :
  294. _name = _meta['field']
  295. elif 'container' in _meta :
  296. _name = _meta['container']
  297. elif 'anchor' in _meta :
  298. _name = list(_meta['anchor'].values())
  299. # if _name :
  300. # _name = _name if type(_name) == list else [_name]
  301. # _info = dict.fromkeys(_name,_attr)
  302. if _meta['parent'] :
  303. _parentElement = filter(elements=[_meta['parent']],plugins=_plugins)
  304. _x12 = list(_parentElement.keys())[0]
  305. _id = list(_parentElement[_x12].keys())[0]
  306. _infoparent = getTableName(meta = _parentElement[_x12][_id].meta,plugins=_plugins,tableKeys=_foreignkeys)
  307. if _meta['x12'] == '*' :
  308. _name = ['claims','remits'] if not _name else _name
  309. _attr = list(set(_foreignkeys.values())) + _attr
  310. else:
  311. _name = 'claims' if _meta['x12'] == '837' and not _name else ('remits' if not _name and _meta['x12'] == '835' else _name)
  312. _id = 'claims' if _meta['x12'] == '837' else 'remits'
  313. if _id in _foreignkeys:
  314. _attr = [_foreignkeys[_id]] + _attr
  315. # if not _name :
  316. # if _meta['x12'] == '*' :
  317. # _name = ['claims','remits']
  318. # else:
  319. # _name = 'claims' if _meta['x12'] == '837' else 'remits'
  320. #
  321. # Let us make sure we can get the keys associated here ...
  322. #
  323. # filter (elements = [])
  324. _name = _name if type(_name) == list else [_name]
  325. _info = dict.fromkeys(_name,_attr)
  326. if _infoparent:
  327. _info = dict(_info,**_infoparent)
  328. return _info
  329. def getTableKeys(**_args):
  330. _plugins=_args['plugins']
  331. _pointer = filter(elements=['CLM'],plugins=_plugins)
  332. _keys = {}
  333. for _element in ['CLM','CLP'] :
  334. _pointer = filter(elements=[_element],plugins=_plugins)
  335. if not _pointer :
  336. continue
  337. _pointer = list(_pointer.values())[0]
  338. _meta = _pointer[_element].meta
  339. _name = _meta['map'][1] if 'map' in _meta else _meta['columns'][0]
  340. _id = 'claims' if _element == 'CLM' else 'remits'
  341. _keys[_id] = _name
  342. return _keys
  343. # print (list(_pointer.values())[0]['CLM'].meta)
  344. # print (_pointer.values()[0].meta)
  345. def sql (**_args):
  346. _plugins = _args['plugins']
  347. # _info = {'foreign':{},'keys':{'claims':None,'remits':None}}
  348. _documentHandler = x12.util.document.Builder(plugins=_plugins,parents=_args['parents'])
  349. _tableKeys = getTableKeys(plugins=_plugins)
  350. _schemas = {}
  351. for key in _plugins :
  352. _mpointers = _plugins[key]
  353. for _element in _mpointers :
  354. _pointer = _mpointers[_element]
  355. _meta = _pointer.meta
  356. _info = getTableName(meta=_meta,plugins=_plugins,tableKeys=_tableKeys)
  357. # _schemas = dict(_schemas,**_info)
  358. if _info :
  359. _schemas = _documentHandler.merge(_schemas,_info)
  360. # print (_info)
  361. return _schemas
  362. # if not _info :
  363. # print (_meta)
  364. # continue
  365. # if _meta['x12'] in ['837','837'] :
  366. # _schema_id = 'claims' if _meta['x12'] == '837' else 'remits'
  367. # _schema_id = [_schema_id]
  368. # else:
  369. # _schema_id = ['claims','remits']
  370. # if _info :
  371. # #
  372. # # foreign tables need to be placed here
  373. # for _id in _schema_id :
  374. # if type(_info) == list :
  375. # _schemas[_id]['attributes'] += _info
  376. # else:
  377. # _schemas[_id]['foreign'] = dict(_schemas[_id]['foreign'],**_info)
  378. # else:
  379. # #
  380. # # This one goes to the main tables
  381. # for _id in _schema_id :
  382. # print (_info)
  383. # _schemas[_id]['attributes'] += list(_info.values())
  384. # DEFAULT_PLUGINS='healthcareio.x12.plugins.default'
  385. # class MODE :
  386. # TRUST,CHECK,TEST,TEST_AND_CHECK= [0,1,2,3]
  387. # def instance(**_args) :
  388. # pass
  389. # def has(**_args) :
  390. # """
  391. # This function will inspect if a function is valid as a plugin or not
  392. # name : function name for a given file
  393. # path : python file to examine
  394. # """
  395. # _pyfile = _args['path'] if 'path' in _args else ''
  396. # _name = _args['name']
  397. # # p = os.path.exists(_pyfile)
  398. # _module = {}
  399. # if os.path.exists(_pyfile):
  400. # _info = IL.utils.spec_from_file_location(_name,_pyfile)
  401. # if _info :
  402. # _module = IL.utils.module_from_spec(_info)
  403. # _info.load.exec(_module)
  404. # else:
  405. # _module = sys.modules[DEFAULT_PLUGINS]
  406. # return hasattr(_module,_name)
  407. # def get(**_args) :
  408. # """
  409. # This function will inspect if a function is valid as a plugin or not
  410. # name : function name for a given file
  411. # path : python file to examine
  412. # """
  413. # _pyfile = _args['path'] if 'path' in _args else ''
  414. # _name = _args['name']
  415. # # p = os.path.exists(_pyfile)
  416. # _module = {}
  417. # if os.path.exists(_pyfile):
  418. # _info = IL.utils.spec_from_file_location(_name,_pyfile)
  419. # if _info :
  420. # _module = IL.utils.module_from_spec(_info)
  421. # _info.load.exec(_module)
  422. # else:
  423. # _module = sys.modules[DEFAULT_PLUGINS]
  424. # return getattr(_module,_name) if hasattr(_module,_name) else None
  425. # def test (**_args):
  426. # """
  427. # This function will test a plugin to insure the plugin conforms to the norm we are setting here
  428. # :pointer function to call
  429. # """
  430. # _params = {}
  431. # try:
  432. # if 'pointer' in _args :
  433. # _caller = _args['pointer']
  434. # else:
  435. # _name = _args['name']
  436. # _path = _args['path'] if 'path' in _args else None
  437. # _caller = get(name=_name,path=_path)
  438. # _params = _caller()
  439. # #
  440. # # the expected result is a list of field names [field_o,field_i]
  441. # #
  442. # return [_item for _item in _params if _item not in ['',None] and type(_item) == str]
  443. # except Exception as e :
  444. # return []
  445. # pass
  446. # def inspect(**_args):
  447. # _mode = _args['mode']
  448. # _name= _args['name']
  449. # _path= _args['path']
  450. # if _mode == MODE.CHECK :
  451. # _doapply = [has]
  452. # elif _mode == MODE.TEST :
  453. # _doapply = [test]
  454. # elif _mode == MODE.TEST_AND_CHECK :
  455. # _doapply = [has,test]
  456. # _status = True
  457. # _plugin = {"name":_name}
  458. # for _pointer in _doapply :
  459. # _plugin[_pointer.__name__] = _pointer(name=_name,path=_path)
  460. # if not _plugin[_pointer.__name__] :
  461. # _status = False
  462. # break
  463. # _plugin['loaded'] = _status
  464. # return _plugin
  465. # def load(**_args):
  466. # """
  467. # This function will load all the plugins given an set of arguments :
  468. # path file
  469. # name list of functions to export
  470. # mode 1- CHECK ONLY, 2 - TEST ONLY, 3- TEST_AND_CHECK
  471. # """
  472. # _path = _args ['path']
  473. # _names= _args['names']
  474. # _mode= _args ['mode'] if 'mode' in _args else MODE.TEST_AND_CHECK
  475. # _doapply = []
  476. # if _mode == MODE.CHECK :
  477. # _doapply = [has]
  478. # elif _mode == MODE.TEST :
  479. # _doapply = [test]
  480. # elif _mode == MODE.TEST_AND_CHECK :
  481. # _doapply = [has,test]
  482. # # _plugins = []
  483. # _plugins = {}
  484. # for _name in _names :
  485. # _plugin = {"name":_name}
  486. # if 'inspect' in _args and _args['inspect'] :
  487. # _plugin = inspect(name=_name,mode=_mode,path=_path)
  488. # else:
  489. # _plugin["method"] = ""
  490. # _status = True
  491. # _plugin['loaded'] = _status
  492. # if _plugin['loaded'] :
  493. # _plugin['pointer'] = get(name=_name,path=_path)
  494. # else:
  495. # _plugin['pointer'] = None
  496. # # _plugins.append(_plugin)
  497. # _plugins[_name] = _plugin
  498. # return _plugins
  499. # def parse(**_args):
  500. # """
  501. # This function will apply a function against a given function, and data
  502. # :row claim/remits pre-processed
  503. # :plugins list of plugins
  504. # :conifg configuration associated with
  505. # """
  506. # _row = _args['row']
  507. # _document = _args['document']
  508. # _config = _args['config']
  509. # """
  510. # "apply":"@path:name"
  511. # """
  512. # _info = _args['config']['apply']
  513. # _plug_conf = _args['config']['plugin'] if 'plugin' in _args['config'] else {}
  514. # if _info.startswith('@') :
  515. # _path = '' #-- get this from general configuration
  516. # elif _info.startswith('!'):
  517. # _path = _info.split('!')[0][1:]
  518. # _name = _info.split(':')[-1]
  519. # _name = _args['config']['apply'].split(_path)