Browse Source

Merge branch 'dev' of lab/parse-edi into master

steve 4 years ago
parent
commit
ebdfb91cdd
100 changed files with 124046 additions and 137 deletions
  1. 28 11
      README.md
  2. 0 105
      edi/__main__.py
  3. 14 0
      healthcareio/Dockerfile
  4. 19 0
      healthcareio/__init__.py
  5. 1 0
      edi/__init__.py
  6. 564 0
      healthcareio/analytics.py
  7. 448 0
      healthcareio/healthcare-io.py
  8. 4 2
      edi/params.py
  9. 100 19
      edi/parser.py
  10. 84 0
      healthcareio/server/__init__.py
  11. 84 0
      healthcareio/server/index.py
  12. 469 0
      healthcareio/server/out.html
  13. 37 0
      healthcareio/server/static/css/borders.css
  14. 8 0
      healthcareio/server/static/css/default.css
  15. 34 0
      healthcareio/server/static/css/fa/LICENSE.txt
  16. 4556 0
      healthcareio/server/static/css/fa/css/all.css
  17. 5 0
      healthcareio/server/static/css/fa/css/all.min.css
  18. 15 0
      healthcareio/server/static/css/fa/css/brands.css
  19. 5 0
      healthcareio/server/static/css/fa/css/brands.min.css
  20. 4522 0
      healthcareio/server/static/css/fa/css/fontawesome.css
  21. 5 0
      healthcareio/server/static/css/fa/css/fontawesome.min.css
  22. 15 0
      healthcareio/server/static/css/fa/css/regular.css
  23. 5 0
      healthcareio/server/static/css/fa/css/regular.min.css
  24. 16 0
      healthcareio/server/static/css/fa/css/solid.css
  25. 5 0
      healthcareio/server/static/css/fa/css/solid.min.css
  26. 371 0
      healthcareio/server/static/css/fa/css/svg-with-js.css
  27. 5 0
      healthcareio/server/static/css/fa/css/svg-with-js.min.css
  28. 2172 0
      healthcareio/server/static/css/fa/css/v4-shims.css
  29. 5 0
      healthcareio/server/static/css/fa/css/v4-shims.min.css
  30. 4441 0
      healthcareio/server/static/css/fa/js/all.js
  31. 5 0
      healthcareio/server/static/css/fa/js/all.min.js
  32. 571 0
      healthcareio/server/static/css/fa/js/brands.js
  33. 5 0
      healthcareio/server/static/css/fa/js/brands.min.js
  34. 998 0
      healthcareio/server/static/css/fa/js/conflict-detection.js
  35. 5 0
      healthcareio/server/static/css/fa/js/conflict-detection.min.js
  36. 2478 0
      healthcareio/server/static/css/fa/js/fontawesome.js
  37. 5 0
      healthcareio/server/static/css/fa/js/fontawesome.min.js
  38. 280 0
      healthcareio/server/static/css/fa/js/regular.js
  39. 5 0
      healthcareio/server/static/css/fa/js/regular.min.js
  40. 1124 0
      healthcareio/server/static/css/fa/js/solid.js
  41. 5 0
      healthcareio/server/static/css/fa/js/solid.min.js
  42. 68 0
      healthcareio/server/static/css/fa/js/v4-shims.js
  43. 5 0
      healthcareio/server/static/css/fa/js/v4-shims.min.js
  44. 19 0
      healthcareio/server/static/css/fa/less/_animated.less
  45. 16 0
      healthcareio/server/static/css/fa/less/_bordered-pulled.less
  46. 12 0
      healthcareio/server/static/css/fa/less/_core.less
  47. 6 0
      healthcareio/server/static/css/fa/less/_fixed-width.less
  48. 1441 0
      healthcareio/server/static/css/fa/less/_icons.less
  49. 27 0
      healthcareio/server/static/css/fa/less/_larger.less
  50. 18 0
      healthcareio/server/static/css/fa/less/_list.less
  51. 56 0
      healthcareio/server/static/css/fa/less/_mixins.less
  52. 24 0
      healthcareio/server/static/css/fa/less/_rotated-flipped.less
  53. 5 0
      healthcareio/server/static/css/fa/less/_screen-reader.less
  54. 2066 0
      healthcareio/server/static/css/fa/less/_shims.less
  55. 22 0
      healthcareio/server/static/css/fa/less/_stacked.less
  56. 1453 0
      healthcareio/server/static/css/fa/less/_variables.less
  57. 23 0
      healthcareio/server/static/css/fa/less/brands.less
  58. 16 0
      healthcareio/server/static/css/fa/less/fontawesome.less
  59. 23 0
      healthcareio/server/static/css/fa/less/regular.less
  60. 24 0
      healthcareio/server/static/css/fa/less/solid.less
  61. 6 0
      healthcareio/server/static/css/fa/less/v4-shims.less
  62. 2562 0
      healthcareio/server/static/css/fa/metadata/categories.yml
  63. 57762 0
      healthcareio/server/static/css/fa/metadata/icons.json
  64. 21485 0
      healthcareio/server/static/css/fa/metadata/icons.yml
  65. 2317 0
      healthcareio/server/static/css/fa/metadata/shims.json
  66. 298 0
      healthcareio/server/static/css/fa/metadata/shims.yml
  67. 688 0
      healthcareio/server/static/css/fa/metadata/sponsors.yml
  68. 20 0
      healthcareio/server/static/css/fa/scss/_animated.scss
  69. 20 0
      healthcareio/server/static/css/fa/scss/_bordered-pulled.scss
  70. 21 0
      healthcareio/server/static/css/fa/scss/_core.scss
  71. 6 0
      healthcareio/server/static/css/fa/scss/_fixed-width.scss
  72. 1441 0
      healthcareio/server/static/css/fa/scss/_icons.scss
  73. 23 0
      healthcareio/server/static/css/fa/scss/_larger.scss
  74. 18 0
      healthcareio/server/static/css/fa/scss/_list.scss
  75. 56 0
      healthcareio/server/static/css/fa/scss/_mixins.scss
  76. 24 0
      healthcareio/server/static/css/fa/scss/_rotated-flipped.scss
  77. 5 0
      healthcareio/server/static/css/fa/scss/_screen-reader.scss
  78. 2066 0
      healthcareio/server/static/css/fa/scss/_shims.scss
  79. 31 0
      healthcareio/server/static/css/fa/scss/_stacked.scss
  80. 1458 0
      healthcareio/server/static/css/fa/scss/_variables.scss
  81. 23 0
      healthcareio/server/static/css/fa/scss/brands.scss
  82. 16 0
      healthcareio/server/static/css/fa/scss/fontawesome.scss
  83. 23 0
      healthcareio/server/static/css/fa/scss/regular.scss
  84. 24 0
      healthcareio/server/static/css/fa/scss/solid.scss
  85. 6 0
      healthcareio/server/static/css/fa/scss/v4-shims.scss
  86. 1336 0
      healthcareio/server/static/css/fa/sprites/brands.svg
  87. 463 0
      healthcareio/server/static/css/fa/sprites/regular.svg
  88. 2995 0
      healthcareio/server/static/css/fa/sprites/solid.svg
  89. 1 0
      healthcareio/server/static/css/fa/svgs/brands/500px.svg
  90. 1 0
      healthcareio/server/static/css/fa/svgs/brands/accessible-icon.svg
  91. 1 0
      healthcareio/server/static/css/fa/svgs/brands/accusoft.svg
  92. 1 0
      healthcareio/server/static/css/fa/svgs/brands/acquisitions-incorporated.svg
  93. 1 0
      healthcareio/server/static/css/fa/svgs/brands/adn.svg
  94. 1 0
      healthcareio/server/static/css/fa/svgs/brands/adobe.svg
  95. 1 0
      healthcareio/server/static/css/fa/svgs/brands/adversal.svg
  96. 1 0
      healthcareio/server/static/css/fa/svgs/brands/affiliatetheme.svg
  97. 1 0
      healthcareio/server/static/css/fa/svgs/brands/airbnb.svg
  98. 1 0
      healthcareio/server/static/css/fa/svgs/brands/algolia.svg
  99. 1 0
      healthcareio/server/static/css/fa/svgs/brands/alipay.svg
  100. 0 0
      healthcareio/server/static/css/fa/svgs/brands/amazon-pay.svg

+ 28 - 11
README.md

@@ -1,33 +1,50 @@
-## About Parse-Edi
+## About Healthcare/IO Parser
 
-**parse-edi** is an Electronic Data Interchange (EDI) parser developed at Vanderbilt University Medical Center during Khanhly Nguyen's summer internship 2019. Built in a healthcare setting, the parser focuses (for now) on x12 claims (837) and remittances (835)
+The Healthcare/IO **parser** is an Electronic Data Interchange (EDI) parser developed at Vanderbilt University Medical Center during Khanhly Nguyen's summer internship 2019. Built in a healthcare setting, the parser focuses (for now) on x12 claims (837) and remittances (835)
 
 This code is intended to extract x12 837 and 835 and format them into portable and human readable format (JSON). This allows the claims to be stored in document data stores such as Mongodb, couchdb or databases that have support for JSON like PostgreSQL
 
 We wrote this frame to be used in both command line or as a library within in your code. The framework is driven by configurations that derviced from X12 standards.
 
+## Features
 
+| Features | |
+| -------- | --- |
+|X12 claims/remits| parsing of {x12} claims/remittances into JSON format with human readible attributes|
+|Multi Processing| capable of processing multiple files simultaneously to speed up processing|
+|Analytics support| descriptive statistical analytics : distribution, various counts|
+|Process Recovery| capable of recovering interrupted runs|
 
-## Installation
 
-    pip install git+https://hiplab.mc.vanderbilt.edu/git/lab/parse-edi.git
 
+## Installation
 
+    pip install --upgrade git+https://hiplab.mc.vanderbilt.edu/git/lab/parse-edi.git
 
 ## Usage 
 
-**Commandline :**
+**cli :**
+
+1. signup to get parsing configuration
 
+        healthcare-io.py --signup <email> [--store <mongo|sqlite>]
         
-        python edi --config <path> --folder <path> --store <[mongo|disk|couch]> --<db|path]> <id|path>
+2. parsing claims in a folder
 
+        healthcare-io.py --parse <claims|remits> --folder <path> [--batch <n>] [--resume]
+        
         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
+            --parse     tells the engine what to parse claims or remits
+            --folder    location of the claims|remits
+            --batch     number of processes to spawn to parse the files
+            --resume    tells the parser to resume parsing 
+                        if all files weren't processed or new files were added into the folder
+**dashboard**
+    
+    There is a built-in dashboard that has features
     
+        healthcare-io.py --server <port> [--context <name>]    
+        
 **Embedded in Code   :**
 
 Use **parse-edi** within your code base as a library and handle storing data in a data store of choice

+ 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__)

+ 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"]

+ 19 - 0
healthcareio/__init__.py

@@ -0,0 +1,19 @@
+"""
+(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    :
+
+"""
+
+from healthcareio import analytics
+# from healthcareio import server

+ 1 - 0
edi/__init__.py

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

+ 564 - 0
healthcareio/analytics.py

@@ -0,0 +1,564 @@
+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']
+        print (_df)
+        name = _df.columns.tolist()[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']
+        if not set(axis['x']) & set(df.columns.tolist()) :
+            print (set(axis['x']) & set(df.columns.tolist()))
+            print (axis['x'])
+            print (df.columns)
+            # df.columns = 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']
+        _args = self.store_config
+        if self.store_config['type'] == 'mongo.MongoWriter' :
+            _args['type'] = 'mongo.MongoReader'
+        else:
+            _args['type'] = 'disk.SQLiteReader'
+        self.reader = transport.factory.instance(**_args)
+
+    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)
+        DB_TYPE = 'mongo' if (type(self.reader) == transport.mongo.MongoReader) else 'sql'
+        r = []
+        for row in _info :
+            
+            for item in row['pipeline'] :
+                # item['data'] = pd.read_sql(item['sql'],conn)
+                query = {DB_TYPE:item[DB_TYPE]}
+                item['data'] = self.reader.read(**item)
+                if 'serialize' in args :
+                    
+                    item['data'] = json.dumps(item['data'].to_dict(orient='record')) if type(item['data']) == pd.DataFrame else item['data']
+                else:
+                    item['data'] = (pd.DataFrame(item['data']))
+                    
+                    
+                # 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)
+

+ 448 - 0
healthcareio/healthcare-io.py

@@ -0,0 +1,448 @@
+#!/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 healthcareio.params import SYS_ARGS
+from transport import factory
+import requests
+
+from healthcareio import analytics
+from healthcareio import server
+from healthcareio.parser import get_content
+import os
+import json
+import sys
+import numpy as np
+from multiprocessing import Process
+import time
+
+
+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)
+    
+    #
+    # 
+    store = args['store'] if 'store' in args else 'sqlite'
+    headers = {"email":email,"client":platform.node(),"store":store,"db":args['db']}
+    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 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 info['store']['type'] == 'disk.SQLiteWriter'  and  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 resume (files,id,config):
+    _args = config['store'].copy()
+    if 'mongo' in config['store']['type'] :
+        _args['type'] = 'mongo.MongoReader'
+        reader = factory.instance(**_args)
+    _files = []
+    if 'resume' in config['analytics'] :
+        _args = config['analytics']['resume'][id]
+        _files = reader.read(**_args)
+        _files = [item['name'] for item in _files if item['name'] != None]
+        return list(set(files) - set(_files))
+        
+    return files
+    pass
+def apply(files,store_info,logger_info=None):
+    """
+        :files          list of files to be processed in this given thread/process
+        :store_info     information about data-store, for now disk isn't thread safe
+        :logger_info    information about where to store the logs
+    """
+
+    if not logger_info :
+        logger = factory.instance(type='disk.DiskWriter',args={'path':os.sep.join([info['out-folder'],SYS_ARGS['parse']+'.log'])})
+    else:
+        logger = factory.instance(**logger_info)
+
+    writer = factory.instance(**store_info)
+    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(dict(_row,**{"parse":SYS_ARGS['parse']})) for _row in logs]
+            else:
+                logger.write({"parse":SYS_ARGS['parse'],"name":filename,"completed":True,"rows":len(content)})
+        except Exception as e:
+            
+            logger.write({"parse":SYS_ARGS['parse'],"filename":filename,"completed":False,"rows":-1,"msg":e.args[0]})
+        # print ([filename,len(content)])
+        #
+        # @TODO: forward this data to the writer and log engine 
+        #
+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'
+        store = SYS_ARGS['store'] if 'store' in SYS_ARGS else 'sqlite'
+        db='healthcareio' if 'db' not in SYS_ARGS else SYS_ARGS['db']
+        register(email=email,url=url,store=store,db=db)
+    # 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
+        #
+        # if the user has specified to resume, we should look into the logs and pull the files processed and those that haven't
+        #
+        if 'resume' in SYS_ARGS :
+            files = resume(files,SYS_ARGS['parse'],info)
+            print (["Found ",len(files)," files unprocessed"])
+        #
+        # @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]
+            logger = factory.instance(type='disk.DiskWriter',args={'path':os.sep.join([info['out-folder'],SYS_ARGS['parse']+'.log'])})
+            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
+            
+            
+            if info['store']['type'] == 'disk.SQLiteWriter' : 
+                    info['store']['args']['table'] = SYS_ARGS['parse'].strip().lower()
+            else:
+                #
+                # if we are working with no-sql we will put the logs in it (performance )?
+
+                    info['store']['args']['doc'] = SYS_ARGS['parse'].strip().lower()
+                    _info = json.loads(json.dumps(info['store']))
+                    _info['args']['doc'] = 'logs'
+                    logger = factory.instance(**_info)
+
+            writer = factory.instance(**info['store'])
+            
+            #
+            # we need to have batches ready for this in order to run some of these queries in parallel
+            # @TODO: Make sure it is with a persistence storage (not disk .. not thread/process safe yet)
+            #   - Make sure we can leverage this on n-cores later on, for now the assumption is a single core
+            #
+            BATCH_COUNT = 1 if 'batch' not in SYS_ARGS else int (SYS_ARGS['batch'])
+            
+            #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)
+            files = np.array_split(files,BATCH_COUNT)
+            procs = []
+            index = 0
+            for row in files :
+                
+                row = row.tolist()
+                logger.write({"process":index,"parse":SYS_ARGS['parse'],"file_count":len(row)})
+                proc = Process(target=apply,args=(row,info['store'],_info,))
+                proc.start()
+                procs.append(proc)
+                index = index + 1
+            while len(procs) > 0 :
+                procs = [proc for proc in procs if proc.is_alive()]
+                time.sleep(2)
+            #     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(dict(_row,**{"parse":SYS_ARGS['parse']})) for _row in logs]
+            #             else:
+            #                 logger.write({"parse":SYS_ARGS['parse'],"name":filename,"completed":True,"rows":len(content)})
+            #         except Exception as e:
+            #             logger.write({"parse":SYS_ARGS['parse'],"filename":filename,"completed":False,"rows":-1,"msg":e.args[0]})
+            #         # print ([filename,len(content)])
+            #         #
+            #         # @TODO: forward this data to the writer and log engine 
+            #         #
+            
+                
+
+        pass
+    elif 'analytics' in SYS_ARGS :
+        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 = analytics.engine(os.sep.join([PATH,'config.json'])) #--@TODO: make the configuration file globally accessible
+        e.apply(type='claims',serialize=True)
+        SYS_ARGS['engine'] = e
+        
+        pointer = lambda : server.app.run(host='0.0.0.0',port=PORT,debug=DEBUG,threaded=False)
+        pthread = Process(target=pointer,args=())
+        pthread.start()
+        
+    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'
+        
+    else:
+        msg = """
+        cli:
+        
+            healthcare-io.py    --<[signup|init]> <email> --store <sqlite|mongo> [--batch <value>]
+            healthcare-io.py    --parse claims --folder <path> [--batch <value>]
+            healthcare-io.py    --parse remits --folder <path> [--batch <value>] [--resume]
+        
+        parameters :
+            --<[signup|init]>   signup or get a configuration file from a parsing server
+            --store             data store mongo or sqlite or mongodb
+            --resume            will attempt to resume if there was an interruption
+        """
+        print(msg)
+        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__)

+ 4 - 2
edi/params.py

@@ -8,10 +8,12 @@ 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:
+			if key and value and not value.startswith('--'):
 				SYS_ARGS[key] = value
 				
 		

+ 100 - 19
edi/parser.py

@@ -19,7 +19,20 @@
 """
 import os
 import sys
+import hashlib
 import json
+class X12 :
+    def split(self,row,sep='*',prefix='HI') :
+        pass
+    def get_config(self,config,row):
+        pass
+    def hash(self,value):
+        pass
+    def suppress (self,value):
+        pass
+    def format_date(self,value):
+        pass
+    
 def split(row,sep='*',prefix='HI'):
     """
     This function is designed to split an x12 row and 
@@ -67,6 +80,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.version_info[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]
@@ -80,14 +103,41 @@ def format_date(value) :
         return "-".join([year,month,day])
 def format_time(value):
     return ":".join([value[:2],value[2:] ])[:5]
+def sv3_parse(value):
+    if '>' in value :
+        terms = value.split('>')
+        return {'type':terms[0],'code':terms[1]}
+        
+    pass
+def sv2_parse(value):
+    #
+    # @TODO: Sometimes there's a suffix (need to inventory all the variations)
+    #
+    if '>' in value or ':' in value:
+        xchar = '>' if '>' in value else ':'
+        _values = value.split(xchar)
+        modifier = {}
+        
+        if len(_values) > 2 :
+
+            modifier= {"code":_values[2]}
+            if len(_values) > 3 :
+                modifier['type'] = _values[3]
+        _value = {"code":_values[1],"type":_values[0]}
+        if modifier :
+            _value['modifier'] = modifier
+
+        return _value
+    else:
+        return value
 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):
 
@@ -99,11 +149,11 @@ def format_pos(value):
     x =  {"code":x[0],"indicator":x[1],"frequency":x[2]} if len(x) == 3 else {"code":x[0],"indicator":None,"frequency":None}
     return x
     
-def get_map(row,config,version):
+def get_map(row,config,version=None):
     
     label = config['label'] if 'label' in config else None    
     
-    omap = config['map'] if version not in config else config[version]
+    omap = config['map'] if not version or version not in config else config[version]
     anchors = config['anchors'] if 'anchors' in config else []
     if type(row[0]) == str:        
         object_value = {}
@@ -121,13 +171,16 @@ 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 type(value[objkey]) == dict :
+                            continue 
                         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]
@@ -142,8 +195,12 @@ def get_map(row,config,version):
         object_value = []
         
         for row_item in row :
-            value = get_map(row_item,config,version)
+            value = get_map(row_item,config,version)            
             object_value.append(value)
+            #
+            # We need to add the index of the object it matters in determining the claim types
+            #
+            
             # object_value.append( list(get_map(row_item,config,version)))
         # object_value = {label:object_value}
     return object_value
@@ -166,18 +223,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 +279,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 +293,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
                 
@@ -254,19 +331,23 @@ def get_content(filename,config,section=None) :
                     if len(claim[label]) > 0 :                    
                         labels = []
                         for item in claim[label] :
+                            item['_index'] = len(labels)
                             if item not in labels :
+                                
                                 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

+ 84 - 0
healthcareio/server/__init__.py

@@ -0,0 +1,84 @@
+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)

+ 84 - 0
healthcareio/server/index.py

@@ -0,0 +1,84 @@
+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)

File diff suppressed because it is too large
+ 469 - 0
healthcareio/server/out.html


+ 37 - 0
healthcareio/server/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/server/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/server/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.**

File diff suppressed because it is too large
+ 4556 - 0
healthcareio/server/static/css/fa/css/all.css


File diff suppressed because it is too large
+ 5 - 0
healthcareio/server/static/css/fa/css/all.min.css


+ 15 - 0
healthcareio/server/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/server/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}

File diff suppressed because it is too large
+ 4522 - 0
healthcareio/server/static/css/fa/css/fontawesome.css


File diff suppressed because it is too large
+ 5 - 0
healthcareio/server/static/css/fa/css/fontawesome.min.css


+ 15 - 0
healthcareio/server/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/server/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/server/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/server/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/server/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; }

File diff suppressed because it is too large
+ 5 - 0
healthcareio/server/static/css/fa/css/svg-with-js.min.css


File diff suppressed because it is too large
+ 2172 - 0
healthcareio/server/static/css/fa/css/v4-shims.css


File diff suppressed because it is too large
+ 5 - 0
healthcareio/server/static/css/fa/css/v4-shims.min.css


File diff suppressed because it is too large
+ 4441 - 0
healthcareio/server/static/css/fa/js/all.js


File diff suppressed because it is too large
+ 5 - 0
healthcareio/server/static/css/fa/js/all.min.js


File diff suppressed because it is too large
+ 571 - 0
healthcareio/server/static/css/fa/js/brands.js


File diff suppressed because it is too large
+ 5 - 0
healthcareio/server/static/css/fa/js/brands.min.js


File diff suppressed because it is too large
+ 998 - 0
healthcareio/server/static/css/fa/js/conflict-detection.js


File diff suppressed because it is too large
+ 5 - 0
healthcareio/server/static/css/fa/js/conflict-detection.min.js


File diff suppressed because it is too large
+ 2478 - 0
healthcareio/server/static/css/fa/js/fontawesome.js


File diff suppressed because it is too large
+ 5 - 0
healthcareio/server/static/css/fa/js/fontawesome.min.js


File diff suppressed because it is too large
+ 280 - 0
healthcareio/server/static/css/fa/js/regular.js


File diff suppressed because it is too large
+ 5 - 0
healthcareio/server/static/css/fa/js/regular.min.js


File diff suppressed because it is too large
+ 1124 - 0
healthcareio/server/static/css/fa/js/solid.js


File diff suppressed because it is too large
+ 5 - 0
healthcareio/server/static/css/fa/js/solid.min.js


File diff suppressed because it is too large
+ 68 - 0
healthcareio/server/static/css/fa/js/v4-shims.js


File diff suppressed because it is too large
+ 5 - 0
healthcareio/server/static/css/fa/js/v4-shims.min.js


+ 19 - 0
healthcareio/server/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/server/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/server/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/server/static/css/fa/less/_fixed-width.less

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

File diff suppressed because it is too large
+ 1441 - 0
healthcareio/server/static/css/fa/less/_icons.less


+ 27 - 0
healthcareio/server/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/server/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/server/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/server/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/server/static/css/fa/less/_screen-reader.less

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

File diff suppressed because it is too large
+ 2066 - 0
healthcareio/server/static/css/fa/less/_shims.less


+ 22 - 0
healthcareio/server/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; }

File diff suppressed because it is too large
+ 1453 - 0
healthcareio/server/static/css/fa/less/_variables.less


+ 23 - 0
healthcareio/server/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/server/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/server/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/server/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/server/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';

File diff suppressed because it is too large
+ 2562 - 0
healthcareio/server/static/css/fa/metadata/categories.yml


File diff suppressed because it is too large
+ 57762 - 0
healthcareio/server/static/css/fa/metadata/icons.json


File diff suppressed because it is too large
+ 21485 - 0
healthcareio/server/static/css/fa/metadata/icons.yml


File diff suppressed because it is too large
+ 2317 - 0
healthcareio/server/static/css/fa/metadata/shims.json


+ 298 - 0
healthcareio/server/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/server/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/server/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/server/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/server/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/server/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;
+}

File diff suppressed because it is too large
+ 1441 - 0
healthcareio/server/static/css/fa/scss/_icons.scss


+ 23 - 0
healthcareio/server/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/server/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/server/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/server/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/server/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; }

File diff suppressed because it is too large
+ 2066 - 0
healthcareio/server/static/css/fa/scss/_shims.scss


+ 31 - 0
healthcareio/server/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;
+}

File diff suppressed because it is too large
+ 1458 - 0
healthcareio/server/static/css/fa/scss/_variables.scss


+ 23 - 0
healthcareio/server/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/server/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/server/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/server/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/server/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';

File diff suppressed because it is too large
+ 1336 - 0
healthcareio/server/static/css/fa/sprites/brands.svg


File diff suppressed because it is too large
+ 463 - 0
healthcareio/server/static/css/fa/sprites/regular.svg


File diff suppressed because it is too large
+ 2995 - 0
healthcareio/server/static/css/fa/sprites/solid.svg


File diff suppressed because it is too large
+ 1 - 0
healthcareio/server/static/css/fa/svgs/brands/500px.svg


File diff suppressed because it is too large
+ 1 - 0
healthcareio/server/static/css/fa/svgs/brands/accessible-icon.svg


File diff suppressed because it is too large
+ 1 - 0
healthcareio/server/static/css/fa/svgs/brands/accusoft.svg


File diff suppressed because it is too large
+ 1 - 0
healthcareio/server/static/css/fa/svgs/brands/acquisitions-incorporated.svg


+ 1 - 0
healthcareio/server/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/server/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>

File diff suppressed because it is too large
+ 1 - 0
healthcareio/server/static/css/fa/svgs/brands/adversal.svg


+ 1 - 0
healthcareio/server/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>

File diff suppressed because it is too large
+ 1 - 0
healthcareio/server/static/css/fa/svgs/brands/airbnb.svg


File diff suppressed because it is too large
+ 1 - 0
healthcareio/server/static/css/fa/svgs/brands/algolia.svg


File diff suppressed because it is too large
+ 1 - 0
healthcareio/server/static/css/fa/svgs/brands/alipay.svg


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


Some files were not shown because too many files changed in this diff