浏览代码

healthcare io parser bug fixes

Steve Nyemba 4 年之前
父节点
当前提交
e53720906b
共有 100 个文件被更改,包括 123742 次插入122 次删除
  1. 0 105
      edi/__main__.py
  2. 二进制
      healthcareio/.parser.py.swp
  3. 14 0
      healthcareio/Dockerfile
  4. 1 0
      edi/__init__.py
  5. 17 0
      healthcareio/__main__.py
  6. 543 0
      healthcareio/analytics.py
  7. 335 0
      healthcareio/healthcareio.py
  8. 1 1
      edi/params.py
  9. 49 16
      edi/parser.py
  10. 0 0
      healthcareio/report/__init__.py
  11. 82 0
      healthcareio/report/index.py
  12. 469 0
      healthcareio/report/out.html
  13. 37 0
      healthcareio/report/static/css/borders.css
  14. 8 0
      healthcareio/report/static/css/default.css
  15. 34 0
      healthcareio/report/static/css/fa/LICENSE.txt
  16. 4556 0
      healthcareio/report/static/css/fa/css/all.css
  17. 5 0
      healthcareio/report/static/css/fa/css/all.min.css
  18. 15 0
      healthcareio/report/static/css/fa/css/brands.css
  19. 5 0
      healthcareio/report/static/css/fa/css/brands.min.css
  20. 4522 0
      healthcareio/report/static/css/fa/css/fontawesome.css
  21. 5 0
      healthcareio/report/static/css/fa/css/fontawesome.min.css
  22. 15 0
      healthcareio/report/static/css/fa/css/regular.css
  23. 5 0
      healthcareio/report/static/css/fa/css/regular.min.css
  24. 16 0
      healthcareio/report/static/css/fa/css/solid.css
  25. 5 0
      healthcareio/report/static/css/fa/css/solid.min.css
  26. 371 0
      healthcareio/report/static/css/fa/css/svg-with-js.css
  27. 5 0
      healthcareio/report/static/css/fa/css/svg-with-js.min.css
  28. 2172 0
      healthcareio/report/static/css/fa/css/v4-shims.css
  29. 5 0
      healthcareio/report/static/css/fa/css/v4-shims.min.css
  30. 4441 0
      healthcareio/report/static/css/fa/js/all.js
  31. 5 0
      healthcareio/report/static/css/fa/js/all.min.js
  32. 571 0
      healthcareio/report/static/css/fa/js/brands.js
  33. 5 0
      healthcareio/report/static/css/fa/js/brands.min.js
  34. 998 0
      healthcareio/report/static/css/fa/js/conflict-detection.js
  35. 5 0
      healthcareio/report/static/css/fa/js/conflict-detection.min.js
  36. 2478 0
      healthcareio/report/static/css/fa/js/fontawesome.js
  37. 5 0
      healthcareio/report/static/css/fa/js/fontawesome.min.js
  38. 280 0
      healthcareio/report/static/css/fa/js/regular.js
  39. 5 0
      healthcareio/report/static/css/fa/js/regular.min.js
  40. 1124 0
      healthcareio/report/static/css/fa/js/solid.js
  41. 5 0
      healthcareio/report/static/css/fa/js/solid.min.js
  42. 68 0
      healthcareio/report/static/css/fa/js/v4-shims.js
  43. 5 0
      healthcareio/report/static/css/fa/js/v4-shims.min.js
  44. 19 0
      healthcareio/report/static/css/fa/less/_animated.less
  45. 16 0
      healthcareio/report/static/css/fa/less/_bordered-pulled.less
  46. 12 0
      healthcareio/report/static/css/fa/less/_core.less
  47. 6 0
      healthcareio/report/static/css/fa/less/_fixed-width.less
  48. 1441 0
      healthcareio/report/static/css/fa/less/_icons.less
  49. 27 0
      healthcareio/report/static/css/fa/less/_larger.less
  50. 18 0
      healthcareio/report/static/css/fa/less/_list.less
  51. 56 0
      healthcareio/report/static/css/fa/less/_mixins.less
  52. 24 0
      healthcareio/report/static/css/fa/less/_rotated-flipped.less
  53. 5 0
      healthcareio/report/static/css/fa/less/_screen-reader.less
  54. 2066 0
      healthcareio/report/static/css/fa/less/_shims.less
  55. 22 0
      healthcareio/report/static/css/fa/less/_stacked.less
  56. 1453 0
      healthcareio/report/static/css/fa/less/_variables.less
  57. 23 0
      healthcareio/report/static/css/fa/less/brands.less
  58. 16 0
      healthcareio/report/static/css/fa/less/fontawesome.less
  59. 23 0
      healthcareio/report/static/css/fa/less/regular.less
  60. 24 0
      healthcareio/report/static/css/fa/less/solid.less
  61. 6 0
      healthcareio/report/static/css/fa/less/v4-shims.less
  62. 2562 0
      healthcareio/report/static/css/fa/metadata/categories.yml
  63. 57762 0
      healthcareio/report/static/css/fa/metadata/icons.json
  64. 21485 0
      healthcareio/report/static/css/fa/metadata/icons.yml
  65. 2317 0
      healthcareio/report/static/css/fa/metadata/shims.json
  66. 298 0
      healthcareio/report/static/css/fa/metadata/shims.yml
  67. 688 0
      healthcareio/report/static/css/fa/metadata/sponsors.yml
  68. 20 0
      healthcareio/report/static/css/fa/scss/_animated.scss
  69. 20 0
      healthcareio/report/static/css/fa/scss/_bordered-pulled.scss
  70. 21 0
      healthcareio/report/static/css/fa/scss/_core.scss
  71. 6 0
      healthcareio/report/static/css/fa/scss/_fixed-width.scss
  72. 1441 0
      healthcareio/report/static/css/fa/scss/_icons.scss
  73. 23 0
      healthcareio/report/static/css/fa/scss/_larger.scss
  74. 18 0
      healthcareio/report/static/css/fa/scss/_list.scss
  75. 56 0
      healthcareio/report/static/css/fa/scss/_mixins.scss
  76. 24 0
      healthcareio/report/static/css/fa/scss/_rotated-flipped.scss
  77. 5 0
      healthcareio/report/static/css/fa/scss/_screen-reader.scss
  78. 2066 0
      healthcareio/report/static/css/fa/scss/_shims.scss
  79. 31 0
      healthcareio/report/static/css/fa/scss/_stacked.scss
  80. 1458 0
      healthcareio/report/static/css/fa/scss/_variables.scss
  81. 23 0
      healthcareio/report/static/css/fa/scss/brands.scss
  82. 16 0
      healthcareio/report/static/css/fa/scss/fontawesome.scss
  83. 23 0
      healthcareio/report/static/css/fa/scss/regular.scss
  84. 24 0
      healthcareio/report/static/css/fa/scss/solid.scss
  85. 6 0
      healthcareio/report/static/css/fa/scss/v4-shims.scss
  86. 1336 0
      healthcareio/report/static/css/fa/sprites/brands.svg
  87. 463 0
      healthcareio/report/static/css/fa/sprites/regular.svg
  88. 2995 0
      healthcareio/report/static/css/fa/sprites/solid.svg
  89. 1 0
      healthcareio/report/static/css/fa/svgs/brands/500px.svg
  90. 1 0
      healthcareio/report/static/css/fa/svgs/brands/accessible-icon.svg
  91. 1 0
      healthcareio/report/static/css/fa/svgs/brands/accusoft.svg
  92. 1 0
      healthcareio/report/static/css/fa/svgs/brands/acquisitions-incorporated.svg
  93. 1 0
      healthcareio/report/static/css/fa/svgs/brands/adn.svg
  94. 1 0
      healthcareio/report/static/css/fa/svgs/brands/adobe.svg
  95. 1 0
      healthcareio/report/static/css/fa/svgs/brands/adversal.svg
  96. 1 0
      healthcareio/report/static/css/fa/svgs/brands/affiliatetheme.svg
  97. 1 0
      healthcareio/report/static/css/fa/svgs/brands/airbnb.svg
  98. 1 0
      healthcareio/report/static/css/fa/svgs/brands/algolia.svg
  99. 1 0
      healthcareio/report/static/css/fa/svgs/brands/alipay.svg
  100. 0 0
      healthcareio/report/static/css/fa/svgs/brands/amazon-pay.svg

+ 0 - 105
edi/__main__.py

@@ -1,105 +0,0 @@
-"""
-(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 get_content
-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:27017'}
-        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)
-        if SYS_ARGS['store'] == 'disk':
-            writer.init(path = 'output-claims.json')
-        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(content)
-                if logs:
-                    
-                    logger.write(logs)
-            
-                
-        pass
-    else:
-        print (__doc__)

二进制
healthcareio/.parser.py.swp


+ 14 - 0
healthcareio/Dockerfile

@@ -0,0 +1,14 @@
+FROM ubuntu:bionic-20200403
+RUN ["apt","update","--fix-missing"]
+RUN ["apt-get","upgrade","-y"]
+
+RUN ["apt-get","-y","install","apt-utils"]
+
+RUN ["apt","update","--fix-missing"]
+RUN ["apt-get","upgrade","-y"]
+RUN ["apt-get","install","-y","sqlite3","sqlite3-pcre","libsqlite3-dev","python3-dev","python3","python3-pip","git","python3-virtualenv"]
+#
+#
+USER health-user
+# VOLUME ["/home/health-user/healthcare-io/","/home-healthuser/.healthcareio"]
+# RUN ["pip3","install","git+https://healthcareio.the-phi.com/git"]

+ 1 - 0
edi/__init__.py

@@ -14,3 +14,4 @@ Usage :
     Embedded    :
 
 """
+import healthcareio

+ 17 - 0
healthcareio/__main__.py

@@ -0,0 +1,17 @@
+"""
+(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    :
+
+"""
+import healthcareio

+ 543 - 0
healthcareio/analytics.py

@@ -0,0 +1,543 @@
+import pandas as pd
+import numpy as np
+import os
+import io
+import json
+from multiprocessing import Process
+import transport
+import sqlite3 as lite
+import numpy as np
+import transport
+import matplotlib.pyplot as plt
+import re, base64
+from weasyprint import HTML, CSS
+COLORS = ["#f79256","#7dcfb6","#fbd1a2","#00b2ca","#1d4e89","#4682B4","#c5c3c6","#4c5c68","#1985a1","#f72585","#7209b7","#3a0ca3","#4361ee","#4cc9f0","#ff595e","#ffca3a","#8ac926","#1982c4","#6a4c93"]
+class stdev :
+    def __init__(self) :
+        self.values = []
+    def step(self,value):
+        if value : #and type in [np.int64, np.int32,np.float64,np.float32, int]:
+            self.values.append(value)            
+    def finalize(self):
+        return np.std(self.values) if self.values else None
+
+
+# conn = lite.connect("/home/steve/healthcare-io/healthcare-io.db3")
+# conn.create_aggregate("stdev",1,stdev)
+# df = pd.read_sql("select count(distinct (json_extract(data,'$.patient_id'))) as patient_count, avg(json_array_length(data,'$.procedures')) mean, stdev(json_array_length(data,'$.procedures')) stdev from claims",conn)
+ROOT_FOLDER = 'stats'
+# plt.gcf().subplots_adjust(bottom=0.15)
+# from matplotlib import rcParams
+# rcParams.update({'figure.autolayout': True})
+class Chart :
+    @staticmethod
+    def remove_borders(axes,wedges,labels,item) :
+        # plt.axes()
+        axes.spines["top"].set_visible(False)
+        # plt.axes().
+        axes.spines["right"].set_visible(False)
+        axes.legend(wedges, labels #,title=item['label']
+        ,loc="upper right",fontsize=12,bbox_to_anchor=(1, 0, 0.5, 1),fancybox=True,framealpha=0.2)                
+        # plt.axes().
+        # axes.spines["left"].set_visible(False)
+        if 'axis' in item['chart'] :
+            
+            axes.set_ylabel(item['chart']['axis']['y'])
+            axes.set_xlabel(item['chart']['axis']['x'])
+
+    @staticmethod
+    def donut(item,**args) :
+        df = item['data']
+        x = item['chart']['x'] #args['x']
+        labels = item['chart']['y']
+        labels = df[labels]
+
+        # figure = plt.figure()
+        figure, axes = plt. subplots()
+        # wedges, texts = plt.pie(df[x],labels=labels)
+        colors = COLORS[:len(labels)] #np.random.choice(COLORS,len(labels),replace=False)
+        wedges = axes.pie(df[x],labels=labels,wedgeprops=dict(width=0.3),colors=colors,autopct=lambda pct: "{:.2f}%\n({:.0f})".format(pct,int((pct/100)*df[x].sum() )))  #,autopct=lambda pct: func(pct, df[x].values))
+        # my_circle=plt.Circle( (0,0), 0.7, color='#ffffff',fill=True)
+        # p=plt.gcf()
+        # p.gca().add_artist(my_circle)   
+        # plt.legend(wedges, labels,title=item['label'],loc="upper right",bbox_to_anchor=(1, 0, 0.5, 1))        
+        # axes.legend(wedges[0], labels,title=item['label'],loc="upper right",bbox_to_anchor=(1, 0, 0.5, 1),framealpha=0,edgecolor='#CAD5E0',
+
+        # )        
+#         x = plt.show()
+        Chart.remove_borders(axes,wedges[0],labels,item) 
+        plt.close()
+    
+        return figure
+    @staticmethod
+    def barh(item,**args):
+        """
+        This function will return/render a bar chart (horizontal) which is conducive to showing distributions of things like diagnosis codes        
+        """
+        # figure = plt.figure()
+        figure, axes = plt. subplots()
+        y_labels = item['chart']['y'][0]   
+        x_labels = item['chart']['x'] #[args['x']] if type(args['x']) == str else args['x']
+        df = item['data'].iloc[:9].copy()
+#         odf = item['data'].iloc[9:].copy().mean().to_frame().T
+#         odf[y_labels] = 'Other'
+#         df  = df.append(odf)
+        wedges = []
+        # COLORS = ['#003f5c','#7a5195','#374c80','#bc5090','#ef5675','#ff764a','#ffa600']
+        for x_ in x_labels:
+            index = x_labels.index(x_)
+            color = COLORS[index]
+            w = axes.barh(df[y_labels],df[x_],align='edge',label='counts' ,color=color)    
+            
+            wedges += [w]
+#         labels = [name.replace('_',' ') for name in x_labels]
+        # axes.legend(wedges,[name.replace('_',' ') for name in x_labels],
+        #             title=item['label'],
+        #             framealpha=0,
+        #             edgecolor='#CAD5E0',
+
+        #           loc="upper right",bbox_to_anchor=(1, 0, 0.5, 1)
+        #           )   
+        Chart.remove_borders(axes,wedges,[name.replace('_',' ') for name in x_labels],item)
+        plt.close()
+    
+        return figure
+    @staticmethod
+    def spline(item,**args):
+        """
+        """
+        df = item['data']     
+        # figure = plt.figure()
+        figure, axes = plt. subplots()
+        wedges = []
+        item['chart']['x'] = [item['chart']['x']]if type(item['chart']['x']) == str else item['chart']['x']
+        # COLORS = ['#003f5c','#7a5195','#374c80','#bc5090','#ef5675','#ff764a','#ffa600']
+        for xl in item['chart']['x'] :
+            x = df[xl]
+            index = 0
+            for yl in item['chart']['y'] :
+                y  = df[yl]    
+                color = COLORS[index]
+                if 'scatter' in args :
+                    w = plt.plot(x,y,'o',color=color)    
+                else:
+                    w = plt.plot(x,y,color=color,marker='o')
+                
+                wedges += w
+                index += 1
+#         print (item['chart']['x'])
+        # if 'axis' in item :
+        #     axes.set_ylabel(item['axis']['y'])
+        #     axes.set_xlabel(item['axis']['x'])
+#         plt.title(item['label'])
+        # axes.legend(wedges,[name.replace('_',' ') for name in item['chart']['y']],
+        #           title=item['label'],
+        #           framealpha=0,
+        #           edgecolor='#CAD5E0',
+        #           loc="upper right",bbox_to_anchor=(1, 0, 0.5, 1)
+        #           )  
+        axes.grid(b=False,which='major',axis='x')
+        Chart.remove_borders(axes,wedges,[name.replace('_',' ') for name in item['chart']['y']],item)
+        plt.close()
+    
+        return figure
+    @staticmethod
+    def scatter(item,**args):
+        return Chart.spline(item,scatter=True)
+class Apex :
+    """
+    This class will format a data-frame to work with Apex charting engine
+    """
+    @staticmethod
+    def apply(item):
+        pointer = item['chart']['type']
+        if hasattr(Apex,pointer) :
+            pointer = getattr(Apex,pointer)
+            options = pointer(item)
+            options['responsive']= [
+                {
+                'breakpoint': 1,
+                'options': {
+                    'plotOptions':item['plotOptions'] if 'plotOptions' in item else None,
+                    
+                }
+                }
+            ]
+            return options
+        else:
+            print ("Oops")
+        pass
+    @staticmethod
+    def scatter(item):
+        options = Apex.spline(item)
+        options['apex']['chart']['type'] = 'scatter'
+        return options
+    @staticmethod
+    def scalar(item):
+        _df = item['data']
+        name = str(_df.columns[0])
+        value = _df[name].values.round(2)[0]
+        html = '<div class="scalar"><div class="value">:value</div><div class="label">:label</div></div>'
+        if value > 999 and value < 1000000 :
+            value = " ".join([str(np.divide(value,1000).round(2)),"K"])
+        elif value > 999999  :
+            #@ Think of considering the case of a billion ...
+            value = " ".join([str(np.divide(value,1000000).round(2)),"M"])
+        else:
+            value = str(value)
+        unit = name.replace('_',' ') if 'unit' not in item else item['unit']
+        return {'html':html.replace(':value',value).replace(":label",unit)}
+    @staticmethod
+    def column(item):
+        df = item['data']
+        N = df.shape[0] if df.shape[0] < 10 else 10
+        axis = item['chart']['axis']
+        x = axis['x']
+        if type(x) == list :
+            x = x[0]
+        axis['y'] = [axis['y']] if type(axis['y']) != list else axis['y']
+        series = []
+        for y in axis['y'] :
+            series += [{"data": df[y].values.tolist()[:N],"name":y.upper().replace('_',' ')}]
+        xtitle,ytitle = Apex.get_labels(item)
+        options = {"chart":{"type":"bar"},"plotOptions":{"bar":{"horizontal":False,"width:":2,"color":["transparent"]}},"dataLabels":{"enabled":False},"legend":{"position":"right"}}
+        options['xaxis'] = {"categories":df[x].values.tolist()[:N],"title":xtitle['title']}
+        options['yaxis'] = ytitle
+        options['series'] = series
+        options['colors']  = COLORS[:df[x].size]
+        return {"apex":options}        
+        # options = Apex.barh(item)
+        # options['chart']['type'] = 'column'
+        # options['plotOptions']['bar'] = {'horizontal':False,'columnWidth':'55%'}
+        # options['stroke']={'show':True,'width':2,'colors':['transparent']}
+        # return {"apex":options}
+    @staticmethod
+    def get_labels(item):
+        xtitle = ytitle = ""
+        if "labels" not in item['chart'] :
+            xtitle = item['chart']['axis']['x']
+            ytitle = item['chart']['axis']['y']
+        else:
+            xtitle = item['chart']['labels']['x']
+            ytitle = item['chart']['labels']['y']
+        xtitle = xtitle if type(xtitle) != list else xtitle[0]
+        ytitle = ytitle if type(ytitle) != list else ytitle[0]
+        return {"title":{"text":xtitle.lower().replace('_',' '),"style":{"fontWeight":"lighter"}}},{"title":{"text":ytitle.lower().replace('_',' '),"style":{"fontWeight":"lighter"}}}
+        
+    @staticmethod
+    def bar(item):
+        return Apex.barh(item)
+    @staticmethod
+    def barh(item):
+        """
+        rendering a horizontal bar chart assuming for now that only one series is involved
+        @TODO: alias this with bar (!= column)
+        """
+        df = item['data']
+        N = df.shape[0] if df.shape[0] < 10 else 10
+        axis = item['chart']['axis']
+        y = axis['y']
+        if type(y) == list :
+            y = y[0]
+        axis['x'] = [axis['x']] if type(axis['x']) != list else axis['x']
+        series = []
+        _min=_max = 0
+        for x in axis['x'] :
+            series += [{"data": df[x].values.tolist()[:N],"name":x.upper().replace('_',' ')}]
+            _min = df[x].min() if df[x].min() < _min else _min
+            _max = df[x].max() if df[x].max() > _max else _max
+        
+        xtitle , ytitle = Apex.get_labels(item)
+        options = {"chart":{"type":"bar"},"plotOptions":{"bar":{"horizontal":True}},"dataLabels":{"enabled":False},"legend":{"position":"right"}}
+        options['xaxis'] = {"categories":df[y].values.tolist()[:N],"title":xtitle['title']}
+        
+        options['yaxis'] = ytitle
+        options['series'] = series
+        options['colors']  = COLORS[:df[x].size]
+        return {"apex":options}
+        
+    @staticmethod
+    def spline(item):
+        series = []
+
+        df = item['data']
+        N = df.shape[0] if df.shape[0] < 10 else 10
+        axis = item['chart']['axis']
+        x = axis['x']
+        _min=_max = 0
+        for y in axis['y'] :
+            series += [{"data":df[y].values[:N].tolist(),"name":y.upper().replace('_',' ')}]
+            _min = df[y].min() if df[y].min() < _min else _min
+            _max = df[y].max() if df[y].max() > _max else _max
+
+        colors = COLORS[:len(axis['y'])]    
+        options = {"chart":{"type":"line"},"series":series,"stroke":{"curve":"smooth"},"colors":colors,"legend":{"position":"right"}}
+        xtitle , ytitle = Apex.get_labels(item)
+        
+        options['xaxis'] = {"categories":df[x].values[:N].tolist(),"title":xtitle['title']}
+        options['yaxis'] = ytitle
+
+        return {"apex":options}
+    @staticmethod
+    def donut(item):
+        """
+        :pre    data must have more than one item otherwise just make it a scalar
+        here we will use the key as labels and the values as the values (obviously)
+        labels are y-axis
+        values are x-axis
+        """
+        df = item['data']
+        
+        if df.shape [0]> 1 :
+            y_cols,x_cols = item['chart']['axis']['y'],item['chart']['axis']['x']
+            labels = df[y_cols].values.tolist()
+            
+            values = df[x_cols].values.round(2).tolist()
+        else:
+            labels = [name.upper().replace('_',' ') for name in df.columns.tolist()]
+            values = df.values.round(2).tolist()[0] if df.shape[1] > 1 else df.values.round(2).tolist()
+            
+        colors  = COLORS[:len(values)]
+        options = {"series":values,"colors":colors,"labels":labels,"chart":{"type":"donut"},"plotOptions":{"pie":{"customScale":.8}},"legend":{"position":"right"}}
+        return {"apex":options}
+
+        pass
+    
+class engine :
+    """
+    This engine is designed to load the configuration and run the queries given they are remittance or claims
+    @TODO:
+        - make sure the readers of the queries are configurable i.e use data-transport
+    """
+    def __init__(self,path) :
+        """
+        Loading configuration file from a designated location ...
+        """
+        f = open(path) ;
+        _config = json.loads(f.read())
+        self.store_config = _config['store']          
+        self.info   = _config['analytics']
+
+    def apply (self,**args) :
+        """
+            type: claims or remits
+            filter  optional identifier claims, procedures, taxonomy, ...
+        """
+        _m = {'claim':'837','claims':'837','remits':'835','remit':'835'}
+        # key = '837' if args['type'] == 'claims' else '835'
+        table = _m[ args['type']]
+        analytics = self.info[table]
+        if 'index' in args :
+            index = int(args['index'])
+            analytics = [analytics[index]]
+            
+        _info = list(analytics) if 'filter' not in args else [item for item in analytics if args['filter'] == item['id']]
+        conn = lite.connect(self.store_config['args']['path'],isolation_level=None)
+        conn.create_aggregate("stdev",1,stdev)
+
+        r = []
+        for row in _info :
+            
+            for item in row['pipeline'] :
+                item['data'] = pd.read_sql(item['sql'],conn)
+                if 'serialize' in args :
+                    item['data'] = json.dumps(item['data'].to_dict(orient='record'))
+                # if 'info' in item:
+                #     item['info'] = item['info'].replace(":rows",str(item["data"].shape[0]))
+        conn.close()
+        
+        return _info
+
+    def _html(self,item) :
+
+        figure = None
+        df = item['data']
+        label = ['<div class="label">',item['label'],'</div>']
+        text = ['<div class="grid">',df.describe().iloc[:].round(2).to_html().replace('_',' '),'</div>']
+        info = ['<div class="info">',item['info'],'</div>'] if 'info' in item else []
+        if item['chart']['type'] in ['pie','donut','doughnut'] :
+            figure = Chart.donut(item)
+            text = ['<div class="grid">',df.to_html(index=False).replace('_',' '),'</div>']
+        elif item['chart']['type'] == 'scatter' :
+            figure = Chart.scatter(item)
+        elif item['chart']['type'] == 'spline' :
+            figure = Chart.spline(item)
+        elif item['chart']['type'] in ['barh','hbar'] :
+            figure = Chart.barh(item)
+        elif item['chart']['type'] == 'scalar' :
+            
+            figure  = (item['data'].apply(lambda col: '<div class="scalar"><div class="value bold">'+str(col.values[0].round(2))+'</div><div class="value-text">'+col.name.replace('_', ' ')+'</div></div>' ).tolist())
+            label   = text = []
+
+            pass
+        if figure and item['chart']['type'] != 'scalar':
+            stream = io.BytesIO()
+            figure.savefig(stream,format='png',dpi=300,quality=95, bbox_inches = "tight",transparent=True)
+            stream.seek(0)
+            stream = base64.b64encode(stream.getvalue()).decode("utf-8")
+            stream = "data:image/png;base64,"+stream
+            figure = ['<div class="figure"><img src="'+stream+'">',"</div>"]
+            
+            # figure.canvas.draw()
+            # figure = "".join( map(chr,figure.canvas.tostring_argb())) #--bytes
+        # else:
+            # figure = [ ]
+        if item['chart']['type'] != 'scalar':
+            return ['<div class="frame"><div class="chart '+ item['chart']['type']+'">'] + [ " ".join(row) for row in [label,figure,text,info] if row] + ["</div></div>"]
+        else:
+            return [ " ".join(row) for row in [label,figure,text,info] if row] 
+        pass
+    def _csv(self,item):
+
+        pass
+    def export(self,item,format):
+        """
+        We have a pipeline here and we should attempt to build a figure using seaborn within an html template using jinja2
+        This is considered a page (or an item) of an analysis where we will have both data and rendering information with accompanying text
+        """
+        html = []
+        for row in item['pipeline'] :
+            p = [ "<h2>",row['label'].replace('_',' '),"</h2>"]
+            y_label = [name for name in row['data'].columns if 'count' in name]
+            x_label = list(set(row['data'].columns) - set(y_label))
+            N = row.shape[0]
+            if 'info' in row :
+                p += ["<div class='info'>",row['info'],'</div>']
+            
+        pass
+
+class LogAnalytics :
+    def __init__(self,path):
+        logs = open(path).read().split('\n')        
+        logs = [json.loads(row) for row in logs if row.strip() != '']
+        self.remits = {
+            "completed": np.sum([1 for row in logs if row['completed'] == True]),
+            "files":len(logs)
+        }
+        
+# m = LogAnalytics('/home/steve/healthcare-io/remits.log')
+
+
+css = """
+    <meta charset="utf-8">            
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+
+    <title>HealthcareIO - :title </title>
+    <style>
+        body{
+            padding:8px;
+            padding-left:4%;
+            padding-right:4%;
+            
+
+        }
+        .pane{
+            padding:4px;
+            display:grid;
+            gap:16px;
+            grid-template-columns:repeat(2,1fr) ;
+            
+            
+            }
+
+        .numbers {
+            display:grid;
+            grid-template-columns:repeat(2,1fr);
+            gap:16px;
+            /*padding:2px;*/
+            /*border:1px solid #CAD5E0;*/
+            
+        }
+        .numbers .scalar {
+            padding:8px;
+            background-image: linear-gradient(to bottom, #f3f3f3,#d3d3d3, #ffffff);
+            border:1px solid #CAD5E0;
+            font-family:sans-serif;
+            text-transform:capitalize;
+            text-align:right;
+            font-size:12px;
+            display:grid;
+            grid-template-rows:auto 28px; gap:2px;
+            
+        }
+        .numbers .scalar .value-text {
+            border-top:1px solid #CAD5E0;
+            padding:8px;
+            font-weight:bold;
+            align-items:center;
+            font-size:14px;
+            display:grid;
+            
+            
+        }
+
+        .numbers .scalar .value {
+            display:grid;
+            color:#004b79;
+            align-content:center;
+            font-size:48px; text-align:right; font-weight:bold;}
+        .frame {
+            background-image: linear-gradient(to bottom, #f3f3f3,#d3d3d3, #ffffff);
+            padding:2px;
+            border:1px solid #CAD5E0;
+            
+        }
+        .figure {grid-area:figure; width:500px; height:350px; display:grid; align-items:center}
+        .info {height:28px;  width:100%; grid-area:info;
+            display:grid;
+            align-items:center;
+            text-align:center; text-transform:capitalize; padding:4px; font-size:12px; font-family:sans-serif; border-top:1px solid #CAD5E0;}
+        .grid {grid-area:grid; }
+        .label {grid-area:label; font-weight:bold; font-size: 22px; text-align:center; text-transform:capitalize}
+        .chart {
+            padding:4px;
+            padding:8px;
+            display:grid; grid-template-areas:
+                "label  label   label"
+                "figure grid    grid"
+                "info   info    info" ;
+                
+            gap:2px;
+            
+        }
+        img {height:auto; max-width:100% ;}
+        table {width:100%; border-collapse: collapse;}
+        table , TH, TD{ font-size:14px; padding:8px; font-family:sans-serif; border:1px; border:1px solid #CAD5E0;}
+        table thead, tbody th { padding:4px; text-transform:capitalize; background-color:#4682B4; color:#ffffff; text-align:center}
+        table thead tr th {text-align:center}
+        table tbody td {text-align:right; font-weight: lighter}
+        table tbody tr:nth-child(odd) {background: #95bce0}
+        table tbody tr:nth-child(even) {background: #c8e5ff}
+        
+        
+    </style>
+"""    
+# folder = '/home/steve/.healthcareio/config.json'
+# e = engine(path=folder)
+# p = e.apply(type='claims')
+# values = []
+# html = [css]
+# for row in p :
+#     frame = []
+#     for item in row['pipeline'] :
+#         if row['pipeline'].index(item) == 0 :
+#             if item['chart']['type'] != 'scalar' :
+#                 # frame = ['<div class="frame">']
+#                 pass
+#             else:
+#                 frame = ['<div><div class="numbers">']
+        
+#         frame += e._html(item) #p[3]['pipeline'][0])
+#     frame   += ['</div></div>'] if item['chart']['type'] == 'scalar' else []
+#     html    += frame
+
+# html = '<div class="pane">' + "\n".join(html) + "</div></div>"
+# f = open('out.html','w')
+# f.write(html.replace(":title","Claims"))
+# 
+# HTML(string=html).write_pdf('out.pdf',stylesheets=[CSS(string=css)])
+# x.write_pdf('./out.pdf')
+# print (p[2]['pipeline'][0]['data'])
+# e.export (p[0])
+# features = ['diagnosis.code']
+# split(folder = folder, features=features)

+ 335 - 0
healthcareio/healthcareio.py

@@ -0,0 +1,335 @@
+#!/usr/bin/env python3
+"""
+(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-parser --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
+import requests
+from parser import get_content
+import os
+import json
+import sys
+PATH = os.sep.join([os.environ['HOME'],'.healthcareio'])
+OUTPUT_FOLDER = os.sep.join([os.environ['HOME'],'healthcare-io'])
+INFO = None
+URL = "https://healthcareio.the-phi.com"
+if not os.path.exists(PATH) :
+    os.mkdir(PATH)
+import platform
+import sqlite3 as lite
+# PATH = os.sep.join([os.environ['HOME'],'.edi-parser'])
+def register (**args) :
+    """
+    :email  user's email address
+    :url    url of the provider to register
+    """
+    
+    email = args['email']
+    url = args['url'] if 'url' in args else URL
+    folders = [PATH,OUTPUT_FOLDER]
+    for path in folders :
+        if not os.path.exists(path) :
+            os.mkdir(path)
+    
+    #
+    # 
+    headers = {"email":email,"client":platform.node()}
+    http = requests.session()    
+    r = http.post(url,headers=headers)
+    
+    #
+    # store = {"type":"disk.DiskWriter","args":{"path":OUTPUT_FOLDER}}
+    # if 'store' in args :
+    #     store = args['store']
+    filename =  (os.sep.join([PATH,'config.json']))
+    info = r.json() #{"parser":r.json(),"store":store}
+    info = dict({"owner":email},**info)
+    info['store']['args']['path'] =os.sep.join([OUTPUT_FOLDER,'healthcare-io.db3']) #-- sql
+    info['out-folder'] = OUTPUT_FOLDER
+
+    file = open( filename,'w')
+    file.write( json.dumps(info))
+    file.close()
+
+    #
+    # Create the sqlite3 database to 
+
+
+def analytics(**args):
+    """
+    This fucntion will only compute basic distributions of a given feature for a given claim   
+    @args
+        @param x:  vector of features to process
+        @param apply:  operation to be applied {dist} 
+    """
+    if args['apply'] in ['dist','distribution'] :
+        """
+        This section of the code will return the distribution of a given space. 
+        It is intended to be applied on several claims/remits
+        """
+        x = pd.DataFrame(args['x'],columns=['x'])
+        return x.groupby(['x']).size().to_frame().T.to_dict(orient='record')
+            
+
+def log(**args):
+    """
+    This function will perform a log of anything provided to it
+    """
+    pass
+def init():
+    """
+    read all the configuration from the 
+    """
+    filename    = os.sep.join([PATH,'config.json'])
+    info        = None
+    if os.path.exists(filename):
+        file = open(filename)
+        info = json.loads(file.read())
+        if not os.path.exists(info['out-folder']) :
+            os.mkdir(info['out-folder'])
+        if not os.path.exists(info['store']['args']['path']) :
+            conn = lite.connect(info['store']['args']['path'],isolation_level=None)
+            for key in info['schema'] :
+                _sql = info['schema'][key]['create']
+                # r = conn.execute("select * from sqlite_master where name in ('claims','remits')")
+                conn.execute(_sql)
+            conn.commit()
+            conn.close()
+
+    return info
+#
+# Global variables that load the configuration files
+
+def parse(**args):
+    """
+    This function will parse the content of a claim or remittance (x12 format) give the following parameters
+    :filename   absolute path of the file to be parsed
+    :type       claims|remits in x12 format
+    """
+    global INFO
+    if not INFO :
+        INFO = init()
+    if args['type'] == 'claims' :
+        CONFIG = INFO['parser']['837']
+    elif args['type'] == 'remits' :
+        CONFIG = INFO['parser']['835']
+    else:
+        CONFIG = None
+    if CONFIG :
+        # CONFIG = CONFIG[-1] if 'version' not in args and (args['version'] < len(CONFIG)) else CONFIG[0]
+        CONFIG = CONFIG[int(args['version'])-1] if 'version' in SYS_ARGS and int(SYS_ARGS['version']) < len(CONFIG) else CONFIG[-1]
+        SECTION = CONFIG['SECTION']
+        os.environ['HEALTHCAREIO_SALT'] = INFO['owner']
+
+        
+        return get_content(args['filename'],CONFIG,SECTION)
+
+
+def upgrade(**args):
+    """
+    :email  provide us with who you are
+    :key    upgrade key provided by the server for a given email
+    """    
+    url = args['url'] if 'url' in args else URL+"/upgrade"
+    headers = {"key":args['key'],"email":args["email"],"url":url}
+    
+if __name__ == '__main__' :
+    info = init()
+    
+    if 'out-folder' in SYS_ARGS :
+        OUTPUT_FOLDER = SYS_ARGS['out-folder']
+        
+    if set(list(SYS_ARGS.keys())) & set(['signup','init']):
+        #
+        # This command will essentially get a new copy of the configurations
+        # @TODO: Tie the request to a version ? 
+        #
+        
+        email = SYS_ARGS['signup'].strip() if 'signup' in SYS_ARGS else SYS_ARGS['init']
+        url = SYS_ARGS['url'] if 'url' in SYS_ARGS else 'https://healthcareio.the-phi.com'
+        
+        register(email=email,url=url)
+    # else:
+    #     m = """
+    #     usage:
+    #         healthcareio --signup --email myemail@provider.com [--url <host>]
+                    
+    #     """
+    #     print (m)
+    elif 'upgrade' in SYS_ARGS :
+        #
+        # perform an upgrade i.e some code or new parsers information will be provided
+        #
+
+        pass
+    elif 'parse' in SYS_ARGS and info:
+        """
+        In this section of the code we are expecting the user to provide :
+        :folder location of the files to process or file to process
+        :
+        """     
+        files = []
+        if 'file' in SYS_ARGS :
+            files = [SYS_ARGS['file']]  if not os.path.isdir(SYS_ARGS['file']) else []
+        if 'folder' in SYS_ARGS and os.path.exists(SYS_ARGS['folder']):
+            names = os.listdir(SYS_ARGS['folder'])
+            files   += [os.sep.join([SYS_ARGS['folder'],name]) for name in names if not os.path.isdir(os.sep.join([SYS_ARGS['folder'],name]))]
+        else:
+            #
+            # raise an erro
+            pass
+        #
+        # @TODO: Log this here so we know what is being processed or not
+        SCOPE = None
+        
+        if files and ('claims' in SYS_ARGS['parse'] or 'remits' in SYS_ARGS['parse']):
+            # _map = {'claims':'837','remits':'835'}
+            # key = _map[SYS_ARGS['parse']]
+            # CONFIG = info['parser'][key]
+            # if 'version' in SYS_ARGS and int(SYS_ARGS['version']) < len(CONFIG) :
+            #     CONFIG = CONFIG[ int(SYS_ARGS['version'])]
+            # else:
+            #     CONFIG = CONFIG[-1]
+            if info['store']['type'] == 'disk.DiskWriter' :
+                info['store']['args']['path'] += (os.sep + 'healthcare-io.json')
+            elif info['store']['type'] == 'disk.SQLiteWriter' :
+                # info['store']['args']['path'] += (os.sep + 'healthcare-io.db3')
+                pass
+            
+            info['store']['args']['table'] = SYS_ARGS['parse'].strip().lower()
+            writer = factory.instance(**info['store'])
+            logger = factory.instance(type='disk.DiskWriter',args={'path':os.sep.join([info['out-folder'],SYS_ARGS['parse']+'.log'])})
+            #logger = factory.instance(type='mongo.MongoWriter',args={'db':'healthcareio','doc':SYS_ARGS['parse']+'_logs'})
+            # schema = info['schema']
+            
+            # for key in schema :                
+            #     sql = schema[key]['create']
+            #     writer.write(sql)
+            for filename in files :
+                
+                if filename.strip() == '':
+                    continue
+                # content,logs = get_content(filename,CONFIG,CONFIG['SECTION'])  
+                # 
+                try:              
+                    content,logs = parse(filename = filename,type=SYS_ARGS['parse'])
+                    if content :
+                        writer.write(content)
+                    if logs :
+                        [logger.write(_row) for _row in logs]
+                    else:
+                        logger.write({"name":filename,"completed":True,"rows":len(content)})
+                except Exception as e:
+                    logger.write({"filename":filename,"completed":False,"rows":-1})
+                # print ([filename,len(content)])
+                #
+                # @TODO: forward this data to the writer and log engine 
+                #
+
+        pass
+    elif 'export' in SYS_ARGS:
+        #
+        # this function is designed to export the data to csv
+        #
+        format = SYS_ARGS['format'] if 'format' in SYS_ARGS else 'csv'
+        format = format.lower()
+        if set([format]) not in ['xls','csv'] :
+            format = 'csv'
+        
+
+        pass
+    # """
+    # 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:27017'}
+    #     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)
+    #     if SYS_ARGS['store'] == 'disk':
+    #         writer.init(path = 'output-claims.json')
+    #     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(content)
+    #             if logs:
+                    
+    #                 logger.write(logs)
+            
+                
+    #     pass
+    # else:
+    #     print (__doc__)

+ 1 - 1
edi/params.py

@@ -8,7 +8,7 @@ if len(sys.argv) > 1:
 		value = None
 		if sys.argv[i].startswith('--'):
 			key = sys.argv[i][2:] #.replace('-','')
-			SYS_ARGS[key] = 1
+			SYS_ARGS[key] = 1			
 			if i + 1 < N:
 				value = sys.argv[i + 1] = sys.argv[i+1].strip()
 			if key and value:

+ 49 - 16
edi/parser.py

@@ -19,6 +19,7 @@
 """
 import os
 import sys
+import hashlib
 import json
 def split(row,sep='*',prefix='HI'):
     """
@@ -67,6 +68,16 @@ def get_config(config,row):
             _info = config[key]
     
     return _info
+def hash(value):
+    salt = os.environ['HEALTHCAREIO_SALT'] if 'HEALTHCAREIO_SALT' in os.environ else ''
+    _value = str(value)+ salt
+    if sys.info.version[0] > 2 :
+        return hashlib.md5(_value.encode('utf-8')).hexdigest()
+    else:
+        return hashlib.md5(_value).hexdigest()
+def suppress(value):
+    return 'N/A'
+    
 def format_date(value) :
     if len(value) == 8 :
         year = value[:4]
@@ -82,12 +93,12 @@ def format_time(value):
     return ":".join([value[:2],value[2:] ])[:5]
 def format_proc(value):
     for xchar in [':','<'] :
-        if xchar in value and len(value.split(xchar)) == 2 :
-            _value = {"type":value.split(':')[0].strip(),"code":value.split(':')[1].strip()}
+        if xchar in value and len(value.split(xchar)) > 1 :
+            #_value = {"type":value.split(':')[0].strip(),"code":value.split(':')[1].strip()}
+            _value = {"type":value.split(xchar)[0].strip(),"code":value.split(xchar)[1].strip()}
             break
         else:
             _value = str(value)
-    
     return _value
 def format_diag(value):
 
@@ -121,13 +132,13 @@ def get_map(row,config,version):
                 if 'cast' in config and key in config['cast'] and value.strip() != '' :
                     
                     value = eval(config['cast'][key])(value)
+
                     
                 if type(value) == dict :
-                    
                     for objkey in value :
                         if 'syn' in config and value[objkey] in config['syn'] :
                             value[objkey] = config['syn'][ value[objkey]]
-                    value = {key:value}
+                    value = {key:value} if key not  in value else value
                 else:
                     if 'syn' in config and value in config['syn'] :
                         value = config['syn'][value]
@@ -166,18 +177,38 @@ def get_content(filename,config,section=None) :
     :filename   location of the file
     """
     section = section if section else config['SECTION']
-    
-    x12_file = open(filename).read().split('\n')
+    logs = []
+    try:
+
+        x12_file = open(filename.strip(),errors='ignore').read().split('\n')
+    except Exception as e:
+        #
+        # We have an error here that should be logged 
+        if sys.version_info[0] > 2 :
+            # logs.append ({"version":VERSION,"filename":filename,"msg":e.args[0],"X12":x12_file[beg:end]})
+            logs.append ({"version":"unknown","filename":filename,"msg":e.args[0]})
+        else:
+            # logs.append ({"version":VERSION,"filename":filename,"msg":e.message,"X12":x12_file[beg:end]})
+            logs.append ({"version":"unknown","filename":filename,"msg":e.message})
+        return [],logs
+
+        pass
     
     if len(x12_file) == 1 :
         
         x12_file = x12_file[0].split('~')
         
-    partitions = '\n'.join(x12_file).split(section+'*')
+    #partitions = '\n'.join(x12_file).split(section+'*')
     locations = get_locations(x12_file,section)
     claims = []
-     
-    logs = []
+    #
+    # given locations it is possible to build up the partitions (made of segments)
+    
+    beg = locations [0]
+    partitions = []
+    for end in locations[1:] :
+        partitions.append ("\n".join(x12_file[beg:end]))
+        beg = end
     
     # VERSION = x12_file[2].split('*')[3].replace('~','')    
     TOP_ROW = x12_file[1].split('*')
@@ -202,7 +233,7 @@ def get_content(filename,config,section=None) :
         # for row in x12_file[beg:end] :
         segment = segment.replace('\n','').split('~')
         for row in segment :
-            row = split(row)            
+            row = split(row)
             
             _info = get_config(config,row)
             if _info :
@@ -216,10 +247,10 @@ def get_content(filename,config,section=None) :
                 except Exception as e:                    
                     if sys.version_info[0] > 2 :
                         # logs.append ({"version":VERSION,"filename":filename,"msg":e.args[0],"X12":x12_file[beg:end]})
-                        logs.append ({"version":VERSION,"filename":filename,"msg":e.args[0],"X12":row})
+                        logs.append ({"version":VERSION,"filename":filename,"msg":e.args[0],"X12":row,"completed":False,"rows":len(row)})
                     else:
                         # logs.append ({"version":VERSION,"filename":filename,"msg":e.message,"X12":x12_file[beg:end]})
-                        logs.append ({"version":VERSION,"filename":filename,"msg":e.message,"X12":row})
+                        logs.append ({"version":VERSION,"filename":filename,"msg":e.message,"X12":row,"rows":len(row),"completed":False})
                     claim = {}
                     break
                 
@@ -258,15 +289,17 @@ def get_content(filename,config,section=None) :
                                 labels.append(item)
                         claim[label] = labels
                         # claim[label] = list( set(claim[label])) #-- removing redundancies
-                        
         if claim and 'claim_id' in claim:
             
             claim = dict(claim,**_default_value)
             claim['name'] = filename.split(os.sep)[-1] #.replace(ROOT,'')
             claim['index'] = len(claims) if len(claims) > 0 else 0
             claims.append(claim)
+        else:
+            #
+            # Could not find claim identifier associated with data 
+            #
+            pass
             
             
-            
-    
     return claims,logs

+ 0 - 0
healthcareio/report/__init__.py


+ 82 - 0
healthcareio/report/index.py

@@ -0,0 +1,82 @@
+from flask import Flask, request,render_template, send_from_directory
+from healthcareio.params import SYS_ARGS
+import healthcareio.analytics
+import os
+import json
+app = Flask(__name__)
+@app.route("/favicon.ico")
+def _icon():
+    return send_from_directory(os.path.join([app.root_path, 'static','img','logo.svg']),
+                               'favicon.ico', mimetype='image/vnd.microsoft.icon')
+@app.route("/")
+def init():
+    e = SYS_ARGS['engine']
+    sections = {"remits":e.info['835'],"claims":e.info['837']}
+    _args = {"sections":sections}
+    return render_template("index.html",**_args)
+@app.route("/format/<id>/<index>",methods=['POST'])
+def _format(id,index):
+    
+    e = SYS_ARGS['engine']
+    key = '837' if id == 'claims' else '835'
+    index = int(index)
+    # p = e.info[key][index]
+    p = e.apply(type=id,index=index)
+    
+    #
+    r = []
+    for item in p[0]['pipeline'] :
+        _item= dict(item)
+        del _item['sql']
+        del _item ['data']
+        
+        _item = dict(_item,**healthcareio.analytics.Apex.apply(item))
+        if 'apex' in _item or 'html' in _item:
+            r.append(_item)
+        
+    r = {"id":p[0]['id'],"pipeline":r}
+    return json.dumps(r),200
+@app.route("/get/<id>/<index>",methods=['GET'])
+def get(id,index):
+    e = SYS_ARGS['engine']
+    key = '837' if id == 'claims' else '835'
+    index = int(index)
+    # p = e.info[key][index]
+    p = e.apply(type=id,index=index)
+    r = {}
+    for item in p[0]['pipeline'] :
+        
+        _item= [dict(item)]
+        
+        r[item['label']] = item['data'].to_dict(orient='record')
+        # del _item['sql']
+        # del _item ['data']
+    #     print (item['label'])
+    #     _item['apex'] = healthcareio.analytics.Apex.apply(item)
+    #     if _item['apex']:
+    #         r.append(_item)
+        
+    # r = {"id":p[0]['id'],"pipeline":r}
+    return json.dumps(r),200
+@app.route("/reload",methods=['POST'])
+def reload():
+    # e = SYS_ARGS['engine']
+    # e.apply()
+    PATH= SYS_ARGS['config'] if 'config' in SYS_ARGS else os.sep.join([os.environ['HOME'],'.healthcareio','config.json'])    
+    e = healthcareio.analytics.engine(PATH)
+    # e.apply()
+    SYS_ARGS['engine'] = e
+
+    return "1",200
+if __name__ == '__main__' :
+    PORT = int(SYS_ARGS['port']) if 'port' in SYS_ARGS else 5500
+    DEBUG= int(SYS_ARGS['debug']) if 'debug' in SYS_ARGS else 0
+    SYS_ARGS['context'] = SYS_ARGS['context'] if 'context' in SYS_ARGS else ''
+    #
+    # 
+    PATH= SYS_ARGS['config'] if 'config' in SYS_ARGS else os.sep.join([os.environ['HOME'],'.healthcareio','config.json'])
+    
+    e = healthcareio.analytics.engine(PATH)
+    # e.apply(type='claims',serialize=True)
+    SYS_ARGS['engine'] = e
+    app.run(host='0.0.0.0',port=PORT,debug=DEBUG,threaded=True)

文件差异内容过多而无法显示
+ 469 - 0
healthcareio/report/out.html


+ 37 - 0
healthcareio/report/static/css/borders.css

@@ -0,0 +1,37 @@
+.border {
+    border:1px solid #CAD5E0 ;    
+}
+.border-round {
+    padding:6px;
+    border-radius:8px;
+}
+
+.border-round-top-left{
+    border-top-left-radius: 8px;    
+    padding:6px;
+}
+.border-round-top-right{
+    border-top-right-radius: 8px;
+    padding:6px;
+}
+.border-round-bottom-right{
+    border-bottom-right-radius: 8px;
+    padding:6px;
+}
+.border-round-bottom-left{
+    border-bottom-left-radius: 8px;
+    padding:6px;
+}
+
+
+.border-right{
+    border-right:1px solid #CAD5E0;
+}
+
+.border-left{
+    border-left:1px solid #CAD5E0;
+}
+
+
+.border-bottom { border-bottom:1px solid #CAD5E0}
+.border-top { border-top:1px solid #CAD5E0}

+ 8 - 0
healthcareio/report/static/css/default.css

@@ -0,0 +1,8 @@
+.active {
+    padding:4px;
+    cursor:pointer;
+    border-bottom:2px solid transparent ;
+}
+.active:hover{
+    border-bottom:2px solid #ff6500; 
+}

+ 34 - 0
healthcareio/report/static/css/fa/LICENSE.txt

@@ -0,0 +1,34 @@
+Font Awesome Free License
+-------------------------
+
+Font Awesome Free is free, open source, and GPL friendly. You can use it for
+commercial projects, open source projects, or really almost whatever you want.
+Full Font Awesome Free license: https://fontawesome.com/license/free.
+
+# Icons: CC BY 4.0 License (https://creativecommons.org/licenses/by/4.0/)
+In the Font Awesome Free download, the CC BY 4.0 license applies to all icons
+packaged as SVG and JS file types.
+
+# Fonts: SIL OFL 1.1 License (https://scripts.sil.org/OFL)
+In the Font Awesome Free download, the SIL OFL license applies to all icons
+packaged as web and desktop font files.
+
+# Code: MIT License (https://opensource.org/licenses/MIT)
+In the Font Awesome Free download, the MIT license applies to all non-font and
+non-icon files.
+
+# Attribution
+Attribution is required by MIT, SIL OFL, and CC BY licenses. Downloaded Font
+Awesome Free files already contain embedded comments with sufficient
+attribution, so you shouldn't need to do anything additional when using these
+files normally.
+
+We've kept attribution comments terse, so we ask that you do not actively work
+to remove them from files, especially code. They're a great way for folks to
+learn about Font Awesome.
+
+# Brand Icons
+All brand icons are trademarks of their respective owners. The use of these
+trademarks does not indicate endorsement of the trademark holder by Font
+Awesome, nor vice versa. **Please do not use brand logos for any purpose except
+to represent the company, product, or service to which they refer.**

文件差异内容过多而无法显示
+ 4556 - 0
healthcareio/report/static/css/fa/css/all.css


文件差异内容过多而无法显示
+ 5 - 0
healthcareio/report/static/css/fa/css/all.min.css


+ 15 - 0
healthcareio/report/static/css/fa/css/brands.css

@@ -0,0 +1,15 @@
+/*!
+ * Font Awesome Free 5.13.0 by @fontawesome - https://fontawesome.com
+ * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
+ */
+@font-face {
+  font-family: 'Font Awesome 5 Brands';
+  font-style: normal;
+  font-weight: 400;
+  font-display: block;
+  src: url("../webfonts/fa-brands-400.eot");
+  src: url("../webfonts/fa-brands-400.eot?#iefix") format("embedded-opentype"), url("../webfonts/fa-brands-400.woff2") format("woff2"), url("../webfonts/fa-brands-400.woff") format("woff"), url("../webfonts/fa-brands-400.ttf") format("truetype"), url("../webfonts/fa-brands-400.svg#fontawesome") format("svg"); }
+
+.fab {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400; }

+ 5 - 0
healthcareio/report/static/css/fa/css/brands.min.css

@@ -0,0 +1,5 @@
+/*!
+ * Font Awesome Free 5.13.0 by @fontawesome - https://fontawesome.com
+ * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
+ */
+@font-face{font-family:"Font Awesome 5 Brands";font-style:normal;font-weight:400;font-display:block;src:url(../webfonts/fa-brands-400.eot);src:url(../webfonts/fa-brands-400.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-brands-400.woff2) format("woff2"),url(../webfonts/fa-brands-400.woff) format("woff"),url(../webfonts/fa-brands-400.ttf) format("truetype"),url(../webfonts/fa-brands-400.svg#fontawesome) format("svg")}.fab{font-family:"Font Awesome 5 Brands";font-weight:400}

文件差异内容过多而无法显示
+ 4522 - 0
healthcareio/report/static/css/fa/css/fontawesome.css


文件差异内容过多而无法显示
+ 5 - 0
healthcareio/report/static/css/fa/css/fontawesome.min.css


+ 15 - 0
healthcareio/report/static/css/fa/css/regular.css

@@ -0,0 +1,15 @@
+/*!
+ * Font Awesome Free 5.13.0 by @fontawesome - https://fontawesome.com
+ * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
+ */
+@font-face {
+  font-family: 'Font Awesome 5 Free';
+  font-style: normal;
+  font-weight: 400;
+  font-display: block;
+  src: url("../webfonts/fa-regular-400.eot");
+  src: url("../webfonts/fa-regular-400.eot?#iefix") format("embedded-opentype"), url("../webfonts/fa-regular-400.woff2") format("woff2"), url("../webfonts/fa-regular-400.woff") format("woff"), url("../webfonts/fa-regular-400.ttf") format("truetype"), url("../webfonts/fa-regular-400.svg#fontawesome") format("svg"); }
+
+.far {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400; }

+ 5 - 0
healthcareio/report/static/css/fa/css/regular.min.css

@@ -0,0 +1,5 @@
+/*!
+ * Font Awesome Free 5.13.0 by @fontawesome - https://fontawesome.com
+ * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
+ */
+@font-face{font-family:"Font Awesome 5 Free";font-style:normal;font-weight:400;font-display:block;src:url(../webfonts/fa-regular-400.eot);src:url(../webfonts/fa-regular-400.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-regular-400.woff2) format("woff2"),url(../webfonts/fa-regular-400.woff) format("woff"),url(../webfonts/fa-regular-400.ttf) format("truetype"),url(../webfonts/fa-regular-400.svg#fontawesome) format("svg")}.far{font-family:"Font Awesome 5 Free";font-weight:400}

+ 16 - 0
healthcareio/report/static/css/fa/css/solid.css

@@ -0,0 +1,16 @@
+/*!
+ * Font Awesome Free 5.13.0 by @fontawesome - https://fontawesome.com
+ * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
+ */
+@font-face {
+  font-family: 'Font Awesome 5 Free';
+  font-style: normal;
+  font-weight: 900;
+  font-display: block;
+  src: url("../webfonts/fa-solid-900.eot");
+  src: url("../webfonts/fa-solid-900.eot?#iefix") format("embedded-opentype"), url("../webfonts/fa-solid-900.woff2") format("woff2"), url("../webfonts/fa-solid-900.woff") format("woff"), url("../webfonts/fa-solid-900.ttf") format("truetype"), url("../webfonts/fa-solid-900.svg#fontawesome") format("svg"); }
+
+.fa,
+.fas {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 900; }

+ 5 - 0
healthcareio/report/static/css/fa/css/solid.min.css

@@ -0,0 +1,5 @@
+/*!
+ * Font Awesome Free 5.13.0 by @fontawesome - https://fontawesome.com
+ * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
+ */
+@font-face{font-family:"Font Awesome 5 Free";font-style:normal;font-weight:900;font-display:block;src:url(../webfonts/fa-solid-900.eot);src:url(../webfonts/fa-solid-900.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-solid-900.woff2) format("woff2"),url(../webfonts/fa-solid-900.woff) format("woff"),url(../webfonts/fa-solid-900.ttf) format("truetype"),url(../webfonts/fa-solid-900.svg#fontawesome) format("svg")}.fa,.fas{font-family:"Font Awesome 5 Free";font-weight:900}

+ 371 - 0
healthcareio/report/static/css/fa/css/svg-with-js.css

@@ -0,0 +1,371 @@
+/*!
+ * Font Awesome Free 5.13.0 by @fontawesome - https://fontawesome.com
+ * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
+ */
+svg:not(:root).svg-inline--fa {
+  overflow: visible; }
+
+.svg-inline--fa {
+  display: inline-block;
+  font-size: inherit;
+  height: 1em;
+  overflow: visible;
+  vertical-align: -.125em; }
+  .svg-inline--fa.fa-lg {
+    vertical-align: -.225em; }
+  .svg-inline--fa.fa-w-1 {
+    width: 0.0625em; }
+  .svg-inline--fa.fa-w-2 {
+    width: 0.125em; }
+  .svg-inline--fa.fa-w-3 {
+    width: 0.1875em; }
+  .svg-inline--fa.fa-w-4 {
+    width: 0.25em; }
+  .svg-inline--fa.fa-w-5 {
+    width: 0.3125em; }
+  .svg-inline--fa.fa-w-6 {
+    width: 0.375em; }
+  .svg-inline--fa.fa-w-7 {
+    width: 0.4375em; }
+  .svg-inline--fa.fa-w-8 {
+    width: 0.5em; }
+  .svg-inline--fa.fa-w-9 {
+    width: 0.5625em; }
+  .svg-inline--fa.fa-w-10 {
+    width: 0.625em; }
+  .svg-inline--fa.fa-w-11 {
+    width: 0.6875em; }
+  .svg-inline--fa.fa-w-12 {
+    width: 0.75em; }
+  .svg-inline--fa.fa-w-13 {
+    width: 0.8125em; }
+  .svg-inline--fa.fa-w-14 {
+    width: 0.875em; }
+  .svg-inline--fa.fa-w-15 {
+    width: 0.9375em; }
+  .svg-inline--fa.fa-w-16 {
+    width: 1em; }
+  .svg-inline--fa.fa-w-17 {
+    width: 1.0625em; }
+  .svg-inline--fa.fa-w-18 {
+    width: 1.125em; }
+  .svg-inline--fa.fa-w-19 {
+    width: 1.1875em; }
+  .svg-inline--fa.fa-w-20 {
+    width: 1.25em; }
+  .svg-inline--fa.fa-pull-left {
+    margin-right: .3em;
+    width: auto; }
+  .svg-inline--fa.fa-pull-right {
+    margin-left: .3em;
+    width: auto; }
+  .svg-inline--fa.fa-border {
+    height: 1.5em; }
+  .svg-inline--fa.fa-li {
+    width: 2em; }
+  .svg-inline--fa.fa-fw {
+    width: 1.25em; }
+
+.fa-layers svg.svg-inline--fa {
+  bottom: 0;
+  left: 0;
+  margin: auto;
+  position: absolute;
+  right: 0;
+  top: 0; }
+
+.fa-layers {
+  display: inline-block;
+  height: 1em;
+  position: relative;
+  text-align: center;
+  vertical-align: -.125em;
+  width: 1em; }
+  .fa-layers svg.svg-inline--fa {
+    -webkit-transform-origin: center center;
+            transform-origin: center center; }
+
+.fa-layers-text, .fa-layers-counter {
+  display: inline-block;
+  position: absolute;
+  text-align: center; }
+
+.fa-layers-text {
+  left: 50%;
+  top: 50%;
+  -webkit-transform: translate(-50%, -50%);
+          transform: translate(-50%, -50%);
+  -webkit-transform-origin: center center;
+          transform-origin: center center; }
+
+.fa-layers-counter {
+  background-color: #ff253a;
+  border-radius: 1em;
+  -webkit-box-sizing: border-box;
+          box-sizing: border-box;
+  color: #fff;
+  height: 1.5em;
+  line-height: 1;
+  max-width: 5em;
+  min-width: 1.5em;
+  overflow: hidden;
+  padding: .25em;
+  right: 0;
+  text-overflow: ellipsis;
+  top: 0;
+  -webkit-transform: scale(0.25);
+          transform: scale(0.25);
+  -webkit-transform-origin: top right;
+          transform-origin: top right; }
+
+.fa-layers-bottom-right {
+  bottom: 0;
+  right: 0;
+  top: auto;
+  -webkit-transform: scale(0.25);
+          transform: scale(0.25);
+  -webkit-transform-origin: bottom right;
+          transform-origin: bottom right; }
+
+.fa-layers-bottom-left {
+  bottom: 0;
+  left: 0;
+  right: auto;
+  top: auto;
+  -webkit-transform: scale(0.25);
+          transform: scale(0.25);
+  -webkit-transform-origin: bottom left;
+          transform-origin: bottom left; }
+
+.fa-layers-top-right {
+  right: 0;
+  top: 0;
+  -webkit-transform: scale(0.25);
+          transform: scale(0.25);
+  -webkit-transform-origin: top right;
+          transform-origin: top right; }
+
+.fa-layers-top-left {
+  left: 0;
+  right: auto;
+  top: 0;
+  -webkit-transform: scale(0.25);
+          transform: scale(0.25);
+  -webkit-transform-origin: top left;
+          transform-origin: top left; }
+
+.fa-lg {
+  font-size: 1.33333em;
+  line-height: 0.75em;
+  vertical-align: -.0667em; }
+
+.fa-xs {
+  font-size: .75em; }
+
+.fa-sm {
+  font-size: .875em; }
+
+.fa-1x {
+  font-size: 1em; }
+
+.fa-2x {
+  font-size: 2em; }
+
+.fa-3x {
+  font-size: 3em; }
+
+.fa-4x {
+  font-size: 4em; }
+
+.fa-5x {
+  font-size: 5em; }
+
+.fa-6x {
+  font-size: 6em; }
+
+.fa-7x {
+  font-size: 7em; }
+
+.fa-8x {
+  font-size: 8em; }
+
+.fa-9x {
+  font-size: 9em; }
+
+.fa-10x {
+  font-size: 10em; }
+
+.fa-fw {
+  text-align: center;
+  width: 1.25em; }
+
+.fa-ul {
+  list-style-type: none;
+  margin-left: 2.5em;
+  padding-left: 0; }
+  .fa-ul > li {
+    position: relative; }
+
+.fa-li {
+  left: -2em;
+  position: absolute;
+  text-align: center;
+  width: 2em;
+  line-height: inherit; }
+
+.fa-border {
+  border: solid 0.08em #eee;
+  border-radius: .1em;
+  padding: .2em .25em .15em; }
+
+.fa-pull-left {
+  float: left; }
+
+.fa-pull-right {
+  float: right; }
+
+.fa.fa-pull-left,
+.fas.fa-pull-left,
+.far.fa-pull-left,
+.fal.fa-pull-left,
+.fab.fa-pull-left {
+  margin-right: .3em; }
+
+.fa.fa-pull-right,
+.fas.fa-pull-right,
+.far.fa-pull-right,
+.fal.fa-pull-right,
+.fab.fa-pull-right {
+  margin-left: .3em; }
+
+.fa-spin {
+  -webkit-animation: fa-spin 2s infinite linear;
+          animation: fa-spin 2s infinite linear; }
+
+.fa-pulse {
+  -webkit-animation: fa-spin 1s infinite steps(8);
+          animation: fa-spin 1s infinite steps(8); }
+
+@-webkit-keyframes fa-spin {
+  0% {
+    -webkit-transform: rotate(0deg);
+            transform: rotate(0deg); }
+  100% {
+    -webkit-transform: rotate(360deg);
+            transform: rotate(360deg); } }
+
+@keyframes fa-spin {
+  0% {
+    -webkit-transform: rotate(0deg);
+            transform: rotate(0deg); }
+  100% {
+    -webkit-transform: rotate(360deg);
+            transform: rotate(360deg); } }
+
+.fa-rotate-90 {
+  -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";
+  -webkit-transform: rotate(90deg);
+          transform: rotate(90deg); }
+
+.fa-rotate-180 {
+  -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";
+  -webkit-transform: rotate(180deg);
+          transform: rotate(180deg); }
+
+.fa-rotate-270 {
+  -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";
+  -webkit-transform: rotate(270deg);
+          transform: rotate(270deg); }
+
+.fa-flip-horizontal {
+  -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";
+  -webkit-transform: scale(-1, 1);
+          transform: scale(-1, 1); }
+
+.fa-flip-vertical {
+  -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";
+  -webkit-transform: scale(1, -1);
+          transform: scale(1, -1); }
+
+.fa-flip-both, .fa-flip-horizontal.fa-flip-vertical {
+  -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";
+  -webkit-transform: scale(-1, -1);
+          transform: scale(-1, -1); }
+
+:root .fa-rotate-90,
+:root .fa-rotate-180,
+:root .fa-rotate-270,
+:root .fa-flip-horizontal,
+:root .fa-flip-vertical,
+:root .fa-flip-both {
+  -webkit-filter: none;
+          filter: none; }
+
+.fa-stack {
+  display: inline-block;
+  height: 2em;
+  position: relative;
+  width: 2.5em; }
+
+.fa-stack-1x,
+.fa-stack-2x {
+  bottom: 0;
+  left: 0;
+  margin: auto;
+  position: absolute;
+  right: 0;
+  top: 0; }
+
+.svg-inline--fa.fa-stack-1x {
+  height: 1em;
+  width: 1.25em; }
+
+.svg-inline--fa.fa-stack-2x {
+  height: 2em;
+  width: 2.5em; }
+
+.fa-inverse {
+  color: #fff; }
+
+.sr-only {
+  border: 0;
+  clip: rect(0, 0, 0, 0);
+  height: 1px;
+  margin: -1px;
+  overflow: hidden;
+  padding: 0;
+  position: absolute;
+  width: 1px; }
+
+.sr-only-focusable:active, .sr-only-focusable:focus {
+  clip: auto;
+  height: auto;
+  margin: 0;
+  overflow: visible;
+  position: static;
+  width: auto; }
+
+.svg-inline--fa .fa-primary {
+  fill: var(--fa-primary-color, currentColor);
+  opacity: 1;
+  opacity: var(--fa-primary-opacity, 1); }
+
+.svg-inline--fa .fa-secondary {
+  fill: var(--fa-secondary-color, currentColor);
+  opacity: 0.4;
+  opacity: var(--fa-secondary-opacity, 0.4); }
+
+.svg-inline--fa.fa-swap-opacity .fa-primary {
+  opacity: 0.4;
+  opacity: var(--fa-secondary-opacity, 0.4); }
+
+.svg-inline--fa.fa-swap-opacity .fa-secondary {
+  opacity: 1;
+  opacity: var(--fa-primary-opacity, 1); }
+
+.svg-inline--fa mask .fa-primary,
+.svg-inline--fa mask .fa-secondary {
+  fill: black; }
+
+.fad.fa-inverse {
+  color: #fff; }

文件差异内容过多而无法显示
+ 5 - 0
healthcareio/report/static/css/fa/css/svg-with-js.min.css


文件差异内容过多而无法显示
+ 2172 - 0
healthcareio/report/static/css/fa/css/v4-shims.css


文件差异内容过多而无法显示
+ 5 - 0
healthcareio/report/static/css/fa/css/v4-shims.min.css


文件差异内容过多而无法显示
+ 4441 - 0
healthcareio/report/static/css/fa/js/all.js


文件差异内容过多而无法显示
+ 5 - 0
healthcareio/report/static/css/fa/js/all.min.js


文件差异内容过多而无法显示
+ 571 - 0
healthcareio/report/static/css/fa/js/brands.js


文件差异内容过多而无法显示
+ 5 - 0
healthcareio/report/static/css/fa/js/brands.min.js


文件差异内容过多而无法显示
+ 998 - 0
healthcareio/report/static/css/fa/js/conflict-detection.js


文件差异内容过多而无法显示
+ 5 - 0
healthcareio/report/static/css/fa/js/conflict-detection.min.js


文件差异内容过多而无法显示
+ 2478 - 0
healthcareio/report/static/css/fa/js/fontawesome.js


文件差异内容过多而无法显示
+ 5 - 0
healthcareio/report/static/css/fa/js/fontawesome.min.js


文件差异内容过多而无法显示
+ 280 - 0
healthcareio/report/static/css/fa/js/regular.js


文件差异内容过多而无法显示
+ 5 - 0
healthcareio/report/static/css/fa/js/regular.min.js


文件差异内容过多而无法显示
+ 1124 - 0
healthcareio/report/static/css/fa/js/solid.js


文件差异内容过多而无法显示
+ 5 - 0
healthcareio/report/static/css/fa/js/solid.min.js


文件差异内容过多而无法显示
+ 68 - 0
healthcareio/report/static/css/fa/js/v4-shims.js


文件差异内容过多而无法显示
+ 5 - 0
healthcareio/report/static/css/fa/js/v4-shims.min.js


+ 19 - 0
healthcareio/report/static/css/fa/less/_animated.less

@@ -0,0 +1,19 @@
+// Animated Icons
+// --------------------------
+
+.@{fa-css-prefix}-spin {
+  animation: fa-spin 2s infinite linear;
+}
+
+.@{fa-css-prefix}-pulse {
+  animation: fa-spin 1s infinite steps(8);
+}
+
+@keyframes fa-spin {
+  0% {
+    transform: rotate(0deg);
+  }
+  100% {
+    transform: rotate(360deg);
+  }
+}

+ 16 - 0
healthcareio/report/static/css/fa/less/_bordered-pulled.less

@@ -0,0 +1,16 @@
+// Bordered & Pulled
+// -------------------------
+
+.@{fa-css-prefix}-border {
+  border-radius: .1em;
+  border: solid .08em @fa-border-color;
+  padding: .2em .25em .15em;
+}
+
+.@{fa-css-prefix}-pull-left { float: left; }
+.@{fa-css-prefix}-pull-right { float: right; }
+
+.@{fa-css-prefix}, .fas, .far, .fal, .fab {
+  &.@{fa-css-prefix}-pull-left { margin-right: .3em; }
+  &.@{fa-css-prefix}-pull-right { margin-left: .3em; }
+}

+ 12 - 0
healthcareio/report/static/css/fa/less/_core.less

@@ -0,0 +1,12 @@
+// Base Class Definition
+// -------------------------
+
+.@{fa-css-prefix}, .fas, .far, .fal, .fad, .fab {
+  -moz-osx-font-smoothing: grayscale;
+  -webkit-font-smoothing: antialiased;
+  display: inline-block;
+  font-style: normal;
+  font-variant: normal;
+  text-rendering: auto;
+  line-height: 1;
+}

+ 6 - 0
healthcareio/report/static/css/fa/less/_fixed-width.less

@@ -0,0 +1,6 @@
+// Fixed Width Icons
+// -------------------------
+.@{fa-css-prefix}-fw {
+  text-align: center;
+  width: (20em / 16);
+}

文件差异内容过多而无法显示
+ 1441 - 0
healthcareio/report/static/css/fa/less/_icons.less


+ 27 - 0
healthcareio/report/static/css/fa/less/_larger.less

@@ -0,0 +1,27 @@
+// Icon Sizes
+// -------------------------
+
+.larger(@factor) when (@factor > 0) {
+  .larger((@factor - 1));
+
+  .@{fa-css-prefix}-@{factor}x {
+    font-size: (@factor * 1em);
+  }
+}
+
+/* makes the font 33% larger relative to the icon container */
+.@{fa-css-prefix}-lg {
+  font-size: (4em / 3);
+  line-height: (3em / 4);
+  vertical-align: -.0667em;
+}
+
+.@{fa-css-prefix}-xs {
+  font-size: .75em;
+}
+
+.@{fa-css-prefix}-sm {
+  font-size: .875em;
+}
+
+.larger(10);

+ 18 - 0
healthcareio/report/static/css/fa/less/_list.less

@@ -0,0 +1,18 @@
+// List Icons
+// -------------------------
+
+.@{fa-css-prefix}-ul {
+  list-style-type: none;
+  margin-left: (@fa-li-width * 5/4);
+  padding-left: 0;
+
+  > li { position: relative; }
+}
+
+.@{fa-css-prefix}-li {
+  left: -@fa-li-width;
+  position: absolute;
+  text-align: center;
+  width: @fa-li-width;
+  line-height: inherit;
+}

+ 56 - 0
healthcareio/report/static/css/fa/less/_mixins.less

@@ -0,0 +1,56 @@
+// Mixins
+// --------------------------
+
+.fa-icon() {
+  -moz-osx-font-smoothing: grayscale;
+  -webkit-font-smoothing: antialiased;
+  display: inline-block;
+  font-style: normal;
+  font-variant: normal;
+  font-weight: normal;
+  line-height: 1;
+}
+
+.fa-icon-rotate(@degrees, @rotation) {
+  -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=@{rotation})";
+  transform: rotate(@degrees);
+}
+
+.fa-icon-flip(@horiz, @vert, @rotation) {
+  -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=@{rotation}, mirror=1)";
+  transform: scale(@horiz, @vert);
+}
+
+
+// Only display content to screen readers. A la Bootstrap 4.
+//
+// See: http://a11yproject.com/posts/how-to-hide-content/
+
+.sr-only() {
+  border: 0;
+  clip: rect(0,0,0,0);
+  height: 1px;
+  margin: -1px;
+  overflow: hidden;
+  padding: 0;
+  position: absolute;
+  width: 1px;
+}
+
+// Use in conjunction with .sr-only to only display content when it's focused.
+//
+// Useful for "Skip to main content" links; see http://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1
+//
+// Credit: HTML5 Boilerplate
+
+.sr-only-focusable() {
+  &:active,
+  &:focus {
+    clip: auto;
+    height: auto;
+    margin: 0;
+    overflow: visible;
+    position: static;
+    width: auto;
+  }
+}

+ 24 - 0
healthcareio/report/static/css/fa/less/_rotated-flipped.less

@@ -0,0 +1,24 @@
+// Rotated & Flipped Icons
+// -------------------------
+
+.@{fa-css-prefix}-rotate-90  { .fa-icon-rotate(90deg, 1);  }
+.@{fa-css-prefix}-rotate-180 { .fa-icon-rotate(180deg, 2); }
+.@{fa-css-prefix}-rotate-270 { .fa-icon-rotate(270deg, 3); }
+
+.@{fa-css-prefix}-flip-horizontal { .fa-icon-flip(-1, 1, 0); }
+.@{fa-css-prefix}-flip-vertical   { .fa-icon-flip(1, -1, 2); }
+.@{fa-css-prefix}-flip-both, .@{fa-css-prefix}-flip-horizontal.@{fa-css-prefix}-flip-vertical { .fa-icon-flip(-1, -1, 2); }
+
+// Hook for IE8-9
+// -------------------------
+
+:root {
+  .@{fa-css-prefix}-rotate-90,
+  .@{fa-css-prefix}-rotate-180,
+  .@{fa-css-prefix}-rotate-270,
+  .@{fa-css-prefix}-flip-horizontal,
+  .@{fa-css-prefix}-flip-vertical,
+  .@{fa-css-prefix}-flip-both {
+    filter: none;
+  }
+}

+ 5 - 0
healthcareio/report/static/css/fa/less/_screen-reader.less

@@ -0,0 +1,5 @@
+// Screen Readers
+// -------------------------
+
+.sr-only { .sr-only(); }
+.sr-only-focusable { .sr-only-focusable(); }

文件差异内容过多而无法显示
+ 2066 - 0
healthcareio/report/static/css/fa/less/_shims.less


+ 22 - 0
healthcareio/report/static/css/fa/less/_stacked.less

@@ -0,0 +1,22 @@
+// Stacked Icons
+// -------------------------
+
+.@{fa-css-prefix}-stack {
+  display: inline-block;
+  height: 2em;
+  line-height: 2em;
+  position: relative;
+  vertical-align: middle;
+  width: 2em;
+}
+
+.@{fa-css-prefix}-stack-1x, .@{fa-css-prefix}-stack-2x {
+  left: 0;
+  position: absolute;
+  text-align: center;
+  width: 100%;
+}
+
+.@{fa-css-prefix}-stack-1x { line-height: inherit; }
+.@{fa-css-prefix}-stack-2x { font-size: 2em; }
+.@{fa-css-prefix}-inverse { color: @fa-inverse; }

文件差异内容过多而无法显示
+ 1453 - 0
healthcareio/report/static/css/fa/less/_variables.less


+ 23 - 0
healthcareio/report/static/css/fa/less/brands.less

@@ -0,0 +1,23 @@
+/*!
+ * Font Awesome Free 5.13.0 by @fontawesome - https://fontawesome.com
+ * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
+ */
+@import "_variables.less";
+
+@font-face {
+  font-family: 'Font Awesome 5 Brands';
+  font-style: normal;
+  font-weight: 400;
+  font-display: @fa-font-display;
+  src: url('@{fa-font-path}/fa-brands-400.eot');
+  src: url('@{fa-font-path}/fa-brands-400.eot?#iefix') format('embedded-opentype'),
+    url('@{fa-font-path}/fa-brands-400.woff2') format('woff2'),
+    url('@{fa-font-path}/fa-brands-400.woff') format('woff'),
+    url('@{fa-font-path}/fa-brands-400.ttf') format('truetype'),
+    url('@{fa-font-path}/fa-brands-400.svg#fontawesome') format('svg');
+}
+
+.fab {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}

+ 16 - 0
healthcareio/report/static/css/fa/less/fontawesome.less

@@ -0,0 +1,16 @@
+/*!
+ * Font Awesome Free 5.13.0 by @fontawesome - https://fontawesome.com
+ * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
+ */
+@import "_variables.less";
+@import "_mixins.less";
+@import "_core.less";
+@import "_larger.less";
+@import "_fixed-width.less";
+@import "_list.less";
+@import "_bordered-pulled.less";
+@import "_animated.less";
+@import "_rotated-flipped.less";
+@import "_stacked.less";
+@import "_icons.less";
+@import "_screen-reader.less";

+ 23 - 0
healthcareio/report/static/css/fa/less/regular.less

@@ -0,0 +1,23 @@
+/*!
+ * Font Awesome Free 5.13.0 by @fontawesome - https://fontawesome.com
+ * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
+ */
+@import "_variables.less";
+
+@font-face {
+  font-family: 'Font Awesome 5 Free';
+  font-style: normal;
+  font-weight: 400;
+  font-display: @fa-font-display;
+  src: url('@{fa-font-path}/fa-regular-400.eot');
+  src: url('@{fa-font-path}/fa-regular-400.eot?#iefix') format('embedded-opentype'),
+    url('@{fa-font-path}/fa-regular-400.woff2') format('woff2'),
+    url('@{fa-font-path}/fa-regular-400.woff') format('woff'),
+    url('@{fa-font-path}/fa-regular-400.ttf') format('truetype'),
+    url('@{fa-font-path}/fa-regular-400.svg#fontawesome') format('svg');
+}
+
+.far {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}

+ 24 - 0
healthcareio/report/static/css/fa/less/solid.less

@@ -0,0 +1,24 @@
+/*!
+ * Font Awesome Free 5.13.0 by @fontawesome - https://fontawesome.com
+ * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
+ */
+@import "_variables.less";
+
+@font-face {
+  font-family: 'Font Awesome 5 Free';
+  font-style: normal;
+  font-weight: 900;
+  font-display: @fa-font-display;
+  src: url('@{fa-font-path}/fa-solid-900.eot');
+  src: url('@{fa-font-path}/fa-solid-900.eot?#iefix') format('embedded-opentype'),
+    url('@{fa-font-path}/fa-solid-900.woff2') format('woff2'),
+    url('@{fa-font-path}/fa-solid-900.woff') format('woff'),
+    url('@{fa-font-path}/fa-solid-900.ttf') format('truetype'),
+    url('@{fa-font-path}/fa-solid-900.svg#fontawesome') format('svg');
+}
+
+.fa,
+.fas {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 900;
+}

+ 6 - 0
healthcareio/report/static/css/fa/less/v4-shims.less

@@ -0,0 +1,6 @@
+/*!
+ * Font Awesome Free 5.13.0 by @fontawesome - https://fontawesome.com
+ * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
+ */
+@import '_variables.less';
+@import '_shims.less';

文件差异内容过多而无法显示
+ 2562 - 0
healthcareio/report/static/css/fa/metadata/categories.yml


文件差异内容过多而无法显示
+ 57762 - 0
healthcareio/report/static/css/fa/metadata/icons.json


文件差异内容过多而无法显示
+ 21485 - 0
healthcareio/report/static/css/fa/metadata/icons.yml


文件差异内容过多而无法显示
+ 2317 - 0
healthcareio/report/static/css/fa/metadata/shims.json


+ 298 - 0
healthcareio/report/static/css/fa/metadata/shims.yml

@@ -0,0 +1,298 @@
+area-chart:
+  name: chart-area
+arrow-circle-o-down:
+  name: arrow-alt-circle-down
+  prefix: far
+arrow-circle-o-left:
+  name: arrow-alt-circle-left
+  prefix: far
+arrow-circle-o-right:
+  name: arrow-alt-circle-right
+  prefix: far
+arrow-circle-o-up:
+  name: arrow-alt-circle-up
+  prefix: far
+arrows:
+  name: arrows-alt
+arrows-alt:
+  name: expand-arrows-alt
+arrows-h:
+  name: arrows-alt-h
+arrows-v:
+  name: arrows-alt-v
+bar-chart:
+  name: chart-bar
+  prefix: far
+bitbucket-square:
+  name: bitbucket
+  prefix: fab
+calendar:
+  name: calendar-alt
+calendar-o:
+  name: calendar
+  prefix: far
+caret-square-o-down:
+  name: caret-square-down
+  prefix: far
+caret-square-o-left:
+  name: caret-square-left
+  prefix: far
+caret-square-o-right:
+  name: caret-square-right
+  prefix: far
+caret-square-o-up:
+  name: caret-square-up
+  prefix: far
+cc:
+  name: closed-captioning
+  prefix: far
+chain-broken:
+  name: unlink
+circle-o-notch:
+  name: circle-notch
+circle-thin:
+  name: circle
+  prefix: far
+clipboard:
+  prefix: far
+clone:
+  prefix: far
+cloud-download:
+  name: cloud-download-alt
+cloud-upload:
+  name: cloud-upload-alt
+code-fork:
+  name: code-branch
+comment-alt:
+  name: comment-dots
+  prefix: far
+commenting:
+  name: comment-dots
+compass:
+  prefix: far
+compress:
+  name: compress-alt
+copyright:
+  prefix: far
+creative-commons:
+  prefix: fab
+credit-card:
+  prefix: far
+credit-card-alt:
+  name: credit-card
+cutlery:
+  name: utensils
+diamond:
+  name: gem
+  prefix: far
+eercast:
+  name: sellcast
+  prefix: fab
+eur:
+  name: euro-sign
+exchange:
+  name: exchange-alt
+expand:
+  name: expand-alt
+external-link:
+  name: external-link-alt
+external-link-square:
+  name: external-link-square-alt
+eye:
+  prefix: far
+eye-dropper:
+  name: eye-dropper
+  prefix: far
+eye-slash:
+  prefix: far
+eyedropper:
+  name: eye-dropper
+facebook:
+  name: facebook-f
+  prefix: fab
+facebook-official:
+  name: facebook
+  prefix: fab
+file-text:
+  name: file-alt
+files-o:
+  name: copy
+  prefix: far
+floppy-o:
+  name: save
+  prefix: far
+gbp:
+  name: pound-sign
+glass:
+  name: glass-martini
+google-plus:
+  name: google-plus-g
+  prefix: fab
+google-plus-circle:
+  name: google-plus
+  prefix: fab
+google-plus-official:
+  name: google-plus
+  prefix: fab
+hand-o-down:
+  name: hand-point-down
+  prefix: far
+hand-o-left:
+  name: hand-point-left
+  prefix: far
+hand-o-right:
+  name: hand-point-right
+  prefix: far
+hand-o-up:
+  name: hand-point-up
+  prefix: far
+header:
+  name: heading
+id-badge:
+  prefix: far
+ils:
+  name: shekel-sign
+inr:
+  name: rupee-sign
+intersex:
+  name: transgender
+jpy:
+  name: yen-sign
+krw:
+  name: won-sign
+level-down:
+  name: level-down-alt
+level-up:
+  name: level-up-alt
+life-ring:
+  prefix: far
+line-chart:
+  name: chart-line
+linkedin:
+  name: linkedin-in
+  prefix: fab
+linkedin-square:
+  name: linkedin
+  prefix: fab
+list-alt:
+  prefix: far
+long-arrow-down:
+  name: long-arrow-alt-down
+long-arrow-left:
+  name: long-arrow-alt-left
+long-arrow-right:
+  name: long-arrow-alt-right
+long-arrow-up:
+  name: long-arrow-alt-up
+map-marker:
+  name: map-marker-alt
+meanpath:
+  name: font-awesome
+  prefix: fab
+mobile:
+  name: mobile-alt
+money:
+  name: money-bill-alt
+  prefix: far
+object-group:
+  prefix: far
+object-ungroup:
+  prefix: far
+paste:
+  prefix: far
+pencil:
+  name: pencil-alt
+pencil-square:
+  name: pen-square
+pencil-square-o:
+  name: edit
+  prefix: far
+picture:
+  name: image
+pie-chart:
+  name: chart-pie
+refresh:
+  name: sync
+registered:
+  prefix: far
+repeat:
+  name: redo
+rub:
+  name: ruble-sign
+scissors:
+  name: cut
+shield:
+  name: shield-alt
+sign-in:
+  name: sign-in-alt
+sign-out:
+  name: sign-out-alt
+sliders:
+  name: sliders-h
+sort-alpha-asc:
+  name: sort-alpha-down
+sort-alpha-desc:
+  name: sort-alpha-down-alt
+sort-amount-asc:
+  name: sort-amount-down
+sort-amount-desc:
+  name: sort-amount-down-alt
+sort-asc:
+  name: sort-up
+sort-desc:
+  name: sort-down
+sort-numeric-asc:
+  name: sort-numeric-down
+sort-numeric-desc:
+  name: sort-numeric-down-alt
+spoon:
+  name: utensil-spoon
+star-half-empty:
+  name: star-half
+star-half-full:
+  name: star-half
+support:
+  name: life-ring
+  prefix: far
+tablet:
+  name: tablet-alt
+tachometer:
+  name: tachometer-alt
+television:
+  name: tv
+thumb-tack:
+  name: thumbtack
+thumbs-o-down:
+  name: thumbs-down
+  prefix: far
+thumbs-o-up:
+  name: thumbs-up
+  prefix: far
+ticket:
+  name: ticket-alt
+trash:
+  name: trash-alt
+trash-o:
+  name: trash-alt
+  prefix: far
+try:
+  name: lira-sign
+usd:
+  name: dollar-sign
+video-camera:
+  name: video
+vimeo:
+  name: vimeo-v
+  prefix: fab
+volume-control-phone:
+  name: phone-volume
+wheelchair-alt:
+  name: accessible-icon
+  prefix: fab
+window-maximize:
+  prefix: far
+window-restore:
+  prefix: far
+youtube-play:
+  name: youtube
+  prefix: fab

+ 688 - 0
healthcareio/report/static/css/fa/metadata/sponsors.yml

@@ -0,0 +1,688 @@
+accusoft:
+  icons:
+    - accusoft
+  label: Accusoft
+  url: 'https://www.accusoft.com'
+administrator-technology:
+  icons:
+    - stream
+  label: Administrator Technology
+  url: 'https://administrator.de'
+adversal:
+  icons:
+    - adversal
+  label: Adversal
+  url: 'https://www.adversal.com'
+affiliatetheme:
+  icons:
+    - affiliatetheme
+  label: affiliatetheme
+  url: 'https://affiliatetheme.io/en'
+algolia:
+  icons:
+    - algolia
+  label: Algolia
+  url: 'http://www.algolia.com'
+amazon-web-services:
+  icons:
+    - aws
+  label: Amazon Web Services
+  url: 'https://aws.amazon.com'
+amilia:
+  icons:
+    - amilia
+  label: Amilia
+  url: 'http://www.amilia.com'
+angry-creative:
+  icons:
+    - angrycreative
+  label: Angry Creative
+  url: 'https://angrycreative.se'
+app-signal:
+  icons:
+    - stroopwafel
+  label: AppSignal
+  url: 'https://appsignal.com'
+apper-systems-ab:
+  icons:
+    - apper
+  label: Apper Systems AB
+  url: 'http://www.apper.com'
+'asymmetrik,ltd':
+  icons:
+    - asymmetrik
+  label: 'Asymmetrik, Ltd.'
+  url: 'http://asymmetrik.com'
+ausmed-education:
+  icons:
+    - user-nurse
+  label: Ausmed Education
+  url: 'https://www.ausmed.com.au'
+avianex:
+  icons:
+    - avianex
+  label: avianex
+  url: 'https://www.avianex.de'
+bi-mobject:
+  icons:
+    - bimobject
+  label: BIMobject
+  url: 'http://bimobject.com'
+bity:
+  icons:
+    - bity
+  label: Bity
+  url: 'http://bity.com'
+blackpulp-designs:
+  icons:
+    - pray
+  label: Blackpulp Designs
+  url: 'https://www.blackpulp.com'
+blissbook:
+  icons:
+    - pen-fancy
+  label: Blissbook
+  url: 'https://blissbook.com'
+büromöbel-experte-gmb-h &co-kg:
+  icons:
+    - buromobelexperte
+  label: Büromöbel-Experte GmbH & Co. KG.
+  url: 'https://www.bueromoebel-experte.de'
+c-panel:
+  icons:
+    - cpanel
+  label: cPanel
+  url: 'http://cpanel.com'
+centercode:
+  icons:
+    - centercode
+  label: Centercode
+  url: 'https://www.centercode.com'
+cibltd:
+  icons:
+    - drum-steelpan
+  label: Comprehensive Insurance Brokers Limited
+  url: 'http://www.cibltd.com'
+clear-blue-technologies:
+  icons:
+    - solar-panel
+  label: Clear Blue Technologies
+  url: 'http://www.clearbluetechnologies.com'
+cloudscale-ch:
+  icons:
+    - cloudscale
+  label: cloudscale.ch
+  url: 'https://www.cloudscale.ch'
+cloudsmith:
+  icons:
+    - cloudsmith
+  label: Cloudsmith
+  url: 'https://cloudsmith.io'
+cloudversify:
+  icons:
+    - cloudversify
+  label: cloudversify
+  url: 'https://www.cloudversify.com'
+cuttlefish:
+  icons:
+    - cuttlefish
+  label: Cuttlefish
+  url: 'http://wearecuttlefish.com'
+cymedica:
+  icons:
+    - wave-square
+  label: CyMedica
+  url: 'https://www.cymedicaortho.com'
+darren-wiebe:
+  icons:
+    - church
+  label: Darren Wiebe
+deploy-dog:
+  icons:
+    - deploydog
+  label: deploy.dog
+  url: 'http://deploy.dog'
+deskpro:
+  icons:
+    - deskpro
+  label: Deskpro
+  url: 'http://www.deskpro.com'
+discourse:
+  icons:
+    - discourse
+  label: Discourse
+  url: 'https://discourse.org'
+doc-hub:
+  icons:
+    - dochub
+  label: DocHub
+  url: 'https://dochub.com'
+draft2-digital:
+  icons:
+    - draft2digital
+  label: Draft2Digital
+  url: 'http://draft2digital.com'
+dyalog-apl:
+  icons:
+    - dyalog
+  label: Dyalog APL
+  url: 'http://www.dyalog.com'
+econopublish:
+  icons:
+    - hat-cowboy-side
+  label: EconoPublish
+  url: 'https://www.econopublish.com'
+firstdraft:
+  icons:
+    - firstdraft
+  label: firstdraft
+  url: 'http://www.firstdraft.com'
+fleetplan:
+  icons:
+    - helicopter
+  label: FLEETPLAN
+  url: 'https://www.fleetplan.net'
+getaroom:
+  icons:
+    - archway
+    - dumbbell
+    - hotel
+    - map-marked
+    - map-marked-alt
+    - monument
+    - spa
+    - swimmer
+    - swimming-pool
+  label: getaroom
+  url: 'https://www.getaroom.com'
+git-kraken:
+  icons:
+    - gitkraken
+  label: GitKraken
+  url: 'https://www.gitkraken.com'
+gofore:
+  icons:
+    - gofore
+  label: Gofore
+  url: 'http://gofore.com'
+'gripfire,inc':
+  icons:
+    - gripfire
+  label: 'Gripfire, Inc.'
+  url: 'http://gripfire.io'
+harvard-medical-school:
+  icons:
+    - allergies
+    - ambulance
+    - band-aid
+    - briefcase-medical
+    - burn
+    - capsules
+    - diagnoses
+    - dna
+    - file-medical
+    - file-medical-alt
+    - first-aid
+    - heart
+    - heartbeat
+    - hospital
+    - hospital-alt
+    - hospital-symbol
+    - id-card-alt
+    - notes-medical
+    - pills
+    - plus
+    - prescription-bottle
+    - prescription-bottle-alt
+    - procedures
+    - smoking
+    - stethoscope
+    - syringe
+    - tablets
+    - thermometer
+    - user-md
+    - vial
+    - vials
+    - weight
+    - x-ray
+  label: Harvard Medical School
+  url: 'https://hms.harvard.edu'
+hips:
+  icons:
+    - hips
+  label: Hips
+  url: 'https://hips.com'
+hire-a-helper:
+  icons:
+    - archive
+    - box-open
+    - couch
+    - dolly
+    - people-carry
+    - route
+    - sign
+    - suitcase
+    - tape
+    - truck-loading
+    - truck-moving
+    - wine-glass
+  label: HireAHelper
+  url: 'https://www.hireahelper.com'
+hornbill:
+  icons:
+    - hornbill
+  label: Hornbill
+  url: 'https://www.hornbill.com'
+hotjar:
+  icons:
+    - hotjar
+  label: Hotjar
+  url: 'https://www.hotjar.com'
+hub-spot:
+  icons:
+    - hubspot
+  label: HubSpot
+  url: 'http://www.HubSpot.com'
+in-site-systems:
+  icons:
+    - toolbox
+  label: InSite Systems
+  url: 'https://www.insitesystems.com'
+inspira-bvba:
+  icons:
+    - chess-knight
+  label: Inspira bvba
+  url: 'https://www.inspira.be'
+joe-emison:
+  icons:
+    - blender-phone
+  label: Joe Emison
+joget:
+  icons:
+    - joget
+  label: Joget
+  url: 'http://www.joget.org'
+jon-galloway:
+  icons:
+    - crow
+  label: Jon Galloway
+kevin-barone:
+  icons:
+    - file-contract
+  label: Kevin Barone
+key-cdn:
+  icons:
+    - keycdn
+  label: KeyCDN
+  url: 'https://www.keycdn.com'
+korvue:
+  icons:
+    - korvue
+  label: Korvue
+  url: 'https://korvue.com'
+max-elman:
+  icons:
+    - frog
+  label: Max Elman
+med-apps:
+  icons:
+    - medapps
+  label: MedApps
+  url: 'http://medapps.com.au'
+medapps:
+  icons:
+    - book-medical
+    - clinic-medical
+    - comment-medical
+    - crutch
+    - disease
+    - hospital-user
+    - laptop-medical
+    - pager
+  label: MedApps
+  url: 'https://medapps.com.au'
+megaport:
+  icons:
+    - megaport
+  label: Megaport
+  url: 'https://www.megaport.com'
+mix:
+  icons:
+    - mix
+  label: Mix
+  url: 'http://mix.com'
+mizuni:
+  icons:
+    - mizuni
+  label: Mizuni
+  url: 'http://www.mizuni.com'
+mrt:
+  icons:
+    - medrt
+  label: MRT
+  url: 'https://medrt.co.jp'
+mylogin-info:
+  icons:
+    - user-shield
+  label: mylogin.info
+  url: 'https://www.mylogin.info'
+napster:
+  icons:
+    - napster
+  label: Napster
+  url: 'http://www.napster.com'
+nimblr:
+  icons:
+    - nimblr
+  label: Nimblr
+  url: 'https://nimblr.ai'
+nompse:
+  icons:
+    - chalkboard
+    - chalkboard-teacher
+  label: Nomp.se
+  url: 'https://nomp.se'
+ns8:
+  icons:
+    - ns8
+  label: NS8
+  url: 'https://www.ns8.com'
+nutritionix:
+  icons:
+    - nutritionix
+  label: Nutritionix
+  url: 'http://www.nutritionix.com'
+page4-corporation:
+  icons:
+    - page4
+  label: page4 Corporation
+  url: 'https://en.page4.com'
+pal-fed:
+  icons:
+    - palfed
+  label: PalFed
+  url: 'https://www.palfed.com'
+pcsg:
+  icons:
+    - horse-head
+  label: PCSG
+  url: 'https://www.pcsg.de'
+phabricator:
+  icons:
+    - phabricator
+  label: Phabricator
+  url: 'http://phacility.com'
+promo-wizard:
+  icons:
+    - hat-wizard
+  label: Promo Wizard
+  url: 'https://promowizard.co.uk'
+pulse-eight:
+  icons:
+    - volume-mute
+  label: Pulse-Eight
+  url: 'https://pulse-eight.com'
+purely-interactive:
+  icons:
+    - kiwi-bird
+  label: Purely Interactive
+  url: 'https://www.purelyinteractive.ca'
+pushed:
+  icons:
+    - pushed
+  label: Pushed
+  url: 'https://pushed.co'
+quin-scape:
+  icons:
+    - quinscape
+  label: QuinScape
+  url: 'https://www.quinscape.de'
+reacteurope:
+  icons:
+    - reacteurope
+  label: ReactEurope
+  url: 'https://www.react-europe.org'
+readme-io:
+  icons:
+    - readme
+  label: Readme.io
+  url: 'http://readme.io'
+red-river:
+  icons:
+    - red-river
+  label: red river
+  url: 'https://river.red'
+replyd:
+  icons:
+    - replyd
+  label: replyd
+resolving:
+  icons:
+    - resolving
+  label: Resolving
+  url: 'https://resolving.com'
+rev-io:
+  icons:
+    - rev
+  label: Rev.io
+  url: 'https://rev.io'
+rock-rms:
+  icons:
+    - rockrms
+  label: Rock RMS
+  url: 'http://rockrms.com'
+rocket-chat:
+  icons:
+    - comment
+    - comment-alt
+    - comment-dots
+    - comment-slash
+    - comments
+    - frown
+    - meh
+    - phone
+    - phone-slash
+    - poo
+    - quote-left
+    - quote-right
+    - rocketchat
+    - smile
+    - video
+    - video-slash
+  label: Rocket.Chat
+  url: 'https://rocket.chat'
+rodney-oliver:
+  icons:
+    - folder-minus
+    - folder-plus
+  label: Rodney Oliver
+schlix:
+  icons:
+    - schlix
+  label: SCHLIX
+  url: 'http://schlix.com'
+search-eng-in:
+  icons:
+    - searchengin
+  label: SearchEng.in
+  url: 'http://searcheng.in'
+service-stack:
+  icons:
+    - servicestack
+  label: ServiceStack
+  url: 'https://servicestack.net'
+shawn-storie:
+  icons:
+    - teeth
+    - teeth-open
+  label: Shawn Storie
+shopware:
+  icons:
+    - shopware
+  label: Shopware
+  url: 'https://shopware.de'
+shp:
+  icons:
+    - school
+  label: SHP
+  url: 'http://shp.com'
+silicon-barn-inc:
+  icons:
+    - project-diagram
+  label: Silicon Barn Inc
+  url: 'https://siliconbarn.com'
+sistrix:
+  icons:
+    - sistrix
+  label: SISTRIX
+  url: 'https://www.sistrix.de'
+smup:
+  icons:
+    - shoe-prints
+  label: Smup
+  url: 'https://www.atomsoftware.com.au'
+speakap:
+  icons:
+    - speakap
+  label: Speakap
+  url: 'https://speakap.com'
+stay-linked:
+  icons:
+    - box
+    - boxes
+    - clipboard-check
+    - clipboard-list
+    - dolly
+    - dolly-flatbed
+    - pallet
+    - shipping-fast
+    - truck
+    - warehouse
+  label: StayLinked
+  url: 'https://www.staylinked.com'
+sticker-mule:
+  icons:
+    - sticker-mule
+  label: Sticker Mule
+  url: 'https://stickermule.com'
+studio-vinari:
+  icons:
+    - studiovinari
+  label: Studio Vinari
+  url: 'https://studiovinari.com'
+supple:
+  icons:
+    - ad
+    - bullhorn
+    - bullseye
+    - comment-dollar
+    - comments-dollar
+    - envelope-open-text
+    - funnel-dollar
+    - mail-bulk
+    - poll
+    - poll-h
+    - search-dollar
+    - search-location
+    - supple
+  label: Supple
+  url: 'https://supple.com.au'
+the-red-yeti:
+  icons:
+    - the-red-yeti
+  label: The Red Yeti
+  url: 'http://theredyeti.com'
+the-us-sunnah-foundation:
+  icons:
+    - dollar-sign
+    - donate
+    - dove
+    - gift
+    - globe
+    - hand-holding-heart
+    - hand-holding-usd
+    - hand-holding-water
+    - hands-helping
+    - handshake
+    - heart
+    - leaf
+    - parachute-box
+    - piggy-bank
+    - ribbon
+    - seedling
+  label: The us-Sunnah Foundation
+  url: 'https://www.ussunnah.org'
+themeco:
+  icons:
+    - themeco
+  label: Themeco
+  url: 'https://theme.co'
+think-peaks:
+  icons:
+    - think-peaks
+  label: Think Peaks
+  url: 'https://thinkpeaks.com/'
+typo3:
+  icons:
+    - typo3
+  label: Typo3
+  url: 'https://typo3.org'
+uniregistry:
+  icons:
+    - uniregistry
+  label: Uniregistry
+  url: 'https://uniregistry.com'
+us-sunnah-foundation:
+  icons:
+    - ussunnah
+  label: us-Sunnah Foundation
+  url: 'https://www.ussunnah.org'
+vaadin:
+  icons:
+    - vaadin
+  label: Vaadin
+  url: 'http://vaadin.com'
+via:
+  icons:
+    - car-crash
+    - draw-polygon
+    - house-damage
+    - layer-group
+    - skull-crossbones
+    - user-injured
+  label: VIA Traffic Software Solutions
+  url: 'https://www.via.software'
+victor-costan:
+  icons:
+    - otter
+  label: Staphany Park and Victor Costan
+vnv:
+  icons:
+    - vnv
+  label: VNV
+  url: 'https://www.vnv.ch'
+weedable:
+  icons:
+    - bong
+    - cannabis
+    - hippo
+    - joint
+    - mortar-pestle
+    - prescription
+  label: Weedable
+  url: 'https://www.weedable.com'
+whmcs:
+  icons:
+    - whmcs
+  label: WHMCS
+  url: 'https://www.whmcs.com'
+workrails:
+  icons:
+    - briefcase
+  label: WorkRails
+  url: 'https://www.workrails.com'
+wpressr:
+  icons:
+    - wpressr
+  label: wpressr
+  url: 'https://wpressr.com'

+ 20 - 0
healthcareio/report/static/css/fa/scss/_animated.scss

@@ -0,0 +1,20 @@
+// Animated Icons
+// --------------------------
+
+.#{$fa-css-prefix}-spin {
+  animation: fa-spin 2s infinite linear;
+}
+
+.#{$fa-css-prefix}-pulse {
+  animation: fa-spin 1s infinite steps(8);
+}
+
+@keyframes fa-spin {
+  0% {
+    transform: rotate(0deg);
+  }
+
+  100% {
+    transform: rotate(360deg);
+  }
+}

+ 20 - 0
healthcareio/report/static/css/fa/scss/_bordered-pulled.scss

@@ -0,0 +1,20 @@
+// Bordered & Pulled
+// -------------------------
+
+.#{$fa-css-prefix}-border {
+  border: solid .08em $fa-border-color;
+  border-radius: .1em;
+  padding: .2em .25em .15em;
+}
+
+.#{$fa-css-prefix}-pull-left { float: left; }
+.#{$fa-css-prefix}-pull-right { float: right; }
+
+.#{$fa-css-prefix},
+.fas,
+.far,
+.fal,
+.fab {
+  &.#{$fa-css-prefix}-pull-left { margin-right: .3em; }
+  &.#{$fa-css-prefix}-pull-right { margin-left: .3em; }
+}

+ 21 - 0
healthcareio/report/static/css/fa/scss/_core.scss

@@ -0,0 +1,21 @@
+// Base Class Definition
+// -------------------------
+
+.#{$fa-css-prefix},
+.fas,
+.far,
+.fal,
+.fad,
+.fab {
+  -moz-osx-font-smoothing: grayscale;
+  -webkit-font-smoothing: antialiased;
+  display: inline-block;
+  font-style: normal;
+  font-variant: normal;
+  text-rendering: auto;
+  line-height: 1;
+}
+
+%fa-icon {
+  @include fa-icon;
+}

+ 6 - 0
healthcareio/report/static/css/fa/scss/_fixed-width.scss

@@ -0,0 +1,6 @@
+// Fixed Width Icons
+// -------------------------
+.#{$fa-css-prefix}-fw {
+  text-align: center;
+  width: $fa-fw-width;
+}

文件差异内容过多而无法显示
+ 1441 - 0
healthcareio/report/static/css/fa/scss/_icons.scss


+ 23 - 0
healthcareio/report/static/css/fa/scss/_larger.scss

@@ -0,0 +1,23 @@
+// Icon Sizes
+// -------------------------
+
+// makes the font 33% larger relative to the icon container
+.#{$fa-css-prefix}-lg {
+  font-size: (4em / 3);
+  line-height: (3em / 4);
+  vertical-align: -.0667em;
+}
+
+.#{$fa-css-prefix}-xs {
+  font-size: .75em;
+}
+
+.#{$fa-css-prefix}-sm {
+  font-size: .875em;
+}
+
+@for $i from 1 through 10 {
+  .#{$fa-css-prefix}-#{$i}x {
+    font-size: $i * 1em;
+  }
+}

+ 18 - 0
healthcareio/report/static/css/fa/scss/_list.scss

@@ -0,0 +1,18 @@
+// List Icons
+// -------------------------
+
+.#{$fa-css-prefix}-ul {
+  list-style-type: none;
+  margin-left: $fa-li-width * 5/4;
+  padding-left: 0;
+
+  > li { position: relative; }
+}
+
+.#{$fa-css-prefix}-li {
+  left: -$fa-li-width;
+  position: absolute;
+  text-align: center;
+  width: $fa-li-width;
+  line-height: inherit;
+}

+ 56 - 0
healthcareio/report/static/css/fa/scss/_mixins.scss

@@ -0,0 +1,56 @@
+// Mixins
+// --------------------------
+
+@mixin fa-icon {
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+  display: inline-block;
+  font-style: normal;
+  font-variant: normal;
+  font-weight: normal;
+  line-height: 1;
+}
+
+@mixin fa-icon-rotate($degrees, $rotation) {
+  -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation})";
+  transform: rotate($degrees);
+}
+
+@mixin fa-icon-flip($horiz, $vert, $rotation) {
+  -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation}, mirror=1)";
+  transform: scale($horiz, $vert);
+}
+
+
+// Only display content to screen readers. A la Bootstrap 4.
+//
+// See: http://a11yproject.com/posts/how-to-hide-content/
+
+@mixin sr-only {
+  border: 0;
+  clip: rect(0, 0, 0, 0);
+  height: 1px;
+  margin: -1px;
+  overflow: hidden;
+  padding: 0;
+  position: absolute;
+  width: 1px;
+}
+
+// Use in conjunction with .sr-only to only display content when it's focused.
+//
+// Useful for "Skip to main content" links; see http://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1
+//
+// Credit: HTML5 Boilerplate
+
+@mixin sr-only-focusable {
+  &:active,
+  &:focus {
+    clip: auto;
+    height: auto;
+    margin: 0;
+    overflow: visible;
+    position: static;
+    width: auto;
+  }
+}

+ 24 - 0
healthcareio/report/static/css/fa/scss/_rotated-flipped.scss

@@ -0,0 +1,24 @@
+// Rotated & Flipped Icons
+// -------------------------
+
+.#{$fa-css-prefix}-rotate-90  { @include fa-icon-rotate(90deg, 1);  }
+.#{$fa-css-prefix}-rotate-180 { @include fa-icon-rotate(180deg, 2); }
+.#{$fa-css-prefix}-rotate-270 { @include fa-icon-rotate(270deg, 3); }
+
+.#{$fa-css-prefix}-flip-horizontal { @include fa-icon-flip(-1, 1, 0); }
+.#{$fa-css-prefix}-flip-vertical   { @include fa-icon-flip(1, -1, 2); }
+.#{$fa-css-prefix}-flip-both, .#{$fa-css-prefix}-flip-horizontal.#{$fa-css-prefix}-flip-vertical { @include fa-icon-flip(-1, -1, 2); }
+
+// Hook for IE8-9
+// -------------------------
+
+:root {
+  .#{$fa-css-prefix}-rotate-90,
+  .#{$fa-css-prefix}-rotate-180,
+  .#{$fa-css-prefix}-rotate-270,
+  .#{$fa-css-prefix}-flip-horizontal,
+  .#{$fa-css-prefix}-flip-vertical,
+  .#{$fa-css-prefix}-flip-both {
+    filter: none;
+  }
+}

+ 5 - 0
healthcareio/report/static/css/fa/scss/_screen-reader.scss

@@ -0,0 +1,5 @@
+// Screen Readers
+// -------------------------
+
+.sr-only { @include sr-only; }
+.sr-only-focusable { @include sr-only-focusable; }

文件差异内容过多而无法显示
+ 2066 - 0
healthcareio/report/static/css/fa/scss/_shims.scss


+ 31 - 0
healthcareio/report/static/css/fa/scss/_stacked.scss

@@ -0,0 +1,31 @@
+// Stacked Icons
+// -------------------------
+
+.#{$fa-css-prefix}-stack {
+  display: inline-block;
+  height: 2em;
+  line-height: 2em;
+  position: relative;
+  vertical-align: middle;
+  width: ($fa-fw-width*2);
+}
+
+.#{$fa-css-prefix}-stack-1x,
+.#{$fa-css-prefix}-stack-2x {
+  left: 0;
+  position: absolute;
+  text-align: center;
+  width: 100%;
+}
+
+.#{$fa-css-prefix}-stack-1x {
+  line-height: inherit;
+}
+
+.#{$fa-css-prefix}-stack-2x {
+  font-size: 2em;
+}
+
+.#{$fa-css-prefix}-inverse {
+  color: $fa-inverse;
+}

文件差异内容过多而无法显示
+ 1458 - 0
healthcareio/report/static/css/fa/scss/_variables.scss


+ 23 - 0
healthcareio/report/static/css/fa/scss/brands.scss

@@ -0,0 +1,23 @@
+/*!
+ * Font Awesome Free 5.13.0 by @fontawesome - https://fontawesome.com
+ * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
+ */
+@import 'variables';
+
+@font-face {
+  font-family: 'Font Awesome 5 Brands';
+  font-style: normal;
+  font-weight: 400;
+  font-display: $fa-font-display;
+  src: url('#{$fa-font-path}/fa-brands-400.eot');
+  src: url('#{$fa-font-path}/fa-brands-400.eot?#iefix') format('embedded-opentype'),
+  url('#{$fa-font-path}/fa-brands-400.woff2') format('woff2'),
+  url('#{$fa-font-path}/fa-brands-400.woff') format('woff'),
+  url('#{$fa-font-path}/fa-brands-400.ttf') format('truetype'),
+  url('#{$fa-font-path}/fa-brands-400.svg#fontawesome') format('svg');
+}
+
+.fab {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}

+ 16 - 0
healthcareio/report/static/css/fa/scss/fontawesome.scss

@@ -0,0 +1,16 @@
+/*!
+ * Font Awesome Free 5.13.0 by @fontawesome - https://fontawesome.com
+ * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
+ */
+@import 'variables';
+@import 'mixins';
+@import 'core';
+@import 'larger';
+@import 'fixed-width';
+@import 'list';
+@import 'bordered-pulled';
+@import 'animated';
+@import 'rotated-flipped';
+@import 'stacked';
+@import 'icons';
+@import 'screen-reader';

+ 23 - 0
healthcareio/report/static/css/fa/scss/regular.scss

@@ -0,0 +1,23 @@
+/*!
+ * Font Awesome Free 5.13.0 by @fontawesome - https://fontawesome.com
+ * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
+ */
+@import 'variables';
+
+@font-face {
+  font-family: 'Font Awesome 5 Free';
+  font-style: normal;
+  font-weight: 400;
+  font-display: $fa-font-display;
+  src: url('#{$fa-font-path}/fa-regular-400.eot');
+  src: url('#{$fa-font-path}/fa-regular-400.eot?#iefix') format('embedded-opentype'),
+  url('#{$fa-font-path}/fa-regular-400.woff2') format('woff2'),
+  url('#{$fa-font-path}/fa-regular-400.woff') format('woff'),
+  url('#{$fa-font-path}/fa-regular-400.ttf') format('truetype'),
+  url('#{$fa-font-path}/fa-regular-400.svg#fontawesome') format('svg');
+}
+
+.far {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}

+ 24 - 0
healthcareio/report/static/css/fa/scss/solid.scss

@@ -0,0 +1,24 @@
+/*!
+ * Font Awesome Free 5.13.0 by @fontawesome - https://fontawesome.com
+ * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
+ */
+@import 'variables';
+
+@font-face {
+  font-family: 'Font Awesome 5 Free';
+  font-style: normal;
+  font-weight: 900;
+  font-display: $fa-font-display;
+  src: url('#{$fa-font-path}/fa-solid-900.eot');
+  src: url('#{$fa-font-path}/fa-solid-900.eot?#iefix') format('embedded-opentype'),
+  url('#{$fa-font-path}/fa-solid-900.woff2') format('woff2'),
+  url('#{$fa-font-path}/fa-solid-900.woff') format('woff'),
+  url('#{$fa-font-path}/fa-solid-900.ttf') format('truetype'),
+  url('#{$fa-font-path}/fa-solid-900.svg#fontawesome') format('svg');
+}
+
+.fa,
+.fas {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 900;
+}

+ 6 - 0
healthcareio/report/static/css/fa/scss/v4-shims.scss

@@ -0,0 +1,6 @@
+/*!
+ * Font Awesome Free 5.13.0 by @fontawesome - https://fontawesome.com
+ * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
+ */
+@import 'variables';
+@import 'shims';

文件差异内容过多而无法显示
+ 1336 - 0
healthcareio/report/static/css/fa/sprites/brands.svg


文件差异内容过多而无法显示
+ 463 - 0
healthcareio/report/static/css/fa/sprites/regular.svg


文件差异内容过多而无法显示
+ 2995 - 0
healthcareio/report/static/css/fa/sprites/solid.svg


文件差异内容过多而无法显示
+ 1 - 0
healthcareio/report/static/css/fa/svgs/brands/500px.svg


文件差异内容过多而无法显示
+ 1 - 0
healthcareio/report/static/css/fa/svgs/brands/accessible-icon.svg


文件差异内容过多而无法显示
+ 1 - 0
healthcareio/report/static/css/fa/svgs/brands/accusoft.svg


文件差异内容过多而无法显示
+ 1 - 0
healthcareio/report/static/css/fa/svgs/brands/acquisitions-incorporated.svg


+ 1 - 0
healthcareio/report/static/css/fa/svgs/brands/adn.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><path d="M248 167.5l64.9 98.8H183.1l64.9-98.8zM496 256c0 136.9-111.1 248-248 248S0 392.9 0 256 111.1 8 248 8s248 111.1 248 248zm-99.8 82.7L248 115.5 99.8 338.7h30.4l33.6-51.7h168.6l33.6 51.7h30.2z"/></svg>

+ 1 - 0
healthcareio/report/static/css/fa/svgs/brands/adobe.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M315.5 64h170.9v384L315.5 64zm-119 0H25.6v384L196.5 64zM256 206.1L363.5 448h-73l-30.7-76.8h-78.7L256 206.1z"/></svg>

文件差异内容过多而无法显示
+ 1 - 0
healthcareio/report/static/css/fa/svgs/brands/adversal.svg


+ 1 - 0
healthcareio/report/static/css/fa/svgs/brands/affiliatetheme.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M159.7 237.4C108.4 308.3 43.1 348.2 14 326.6-15.2 304.9 2.8 230 54.2 159.1c51.3-70.9 116.6-110.8 145.7-89.2 29.1 21.6 11.1 96.6-40.2 167.5zm351.2-57.3C437.1 303.5 319 367.8 246.4 323.7c-25-15.2-41.3-41.2-49-73.8-33.6 64.8-92.8 113.8-164.1 133.2 49.8 59.3 124.1 96.9 207 96.9 150 0 271.6-123.1 271.6-274.9.1-8.5-.3-16.8-1-25z"/></svg>

文件差异内容过多而无法显示
+ 1 - 0
healthcareio/report/static/css/fa/svgs/brands/airbnb.svg


文件差异内容过多而无法显示
+ 1 - 0
healthcareio/report/static/css/fa/svgs/brands/algolia.svg


文件差异内容过多而无法显示
+ 1 - 0
healthcareio/report/static/css/fa/svgs/brands/alipay.svg


+ 0 - 0
healthcareio/report/static/css/fa/svgs/brands/amazon-pay.svg


部分文件因为文件数量过多而无法显示