Repository: cannlytics/cannlytics (language: python)

Issue:
corrected mcrlabs.py lab_email and removed extra lab_website
mcrlabs.py updated lab_email and removed extra lab_website 


Discussion / hints:

Fix patch:
diff --git a/.vscode/settings.json b/.vscode/settings.json
index d8291cf5..baf542a3 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -119,7 +119,12 @@
     "camelcase",
     "cann",
     "cannabavarin",
+    "Cannabichromene",
     "cannabidiol",
+    "Cannabidiolic",
+    "Cannabidivarin",
+    "Cannabigerol",
+    "Cannabigerolic",
     "cannabinoid",
     "cannabinoids",
     "Cannalysis",
@@ -236,6 +241,7 @@
     "efectos",
     "elif",
     "endswith",
+    "enterobacteriaceae",
     "escapeall",
     "esmodules",
     "ethoprophos",
@@ -280,6 +286,7 @@
     "Fludioxonil",
     "fontawesome",
     "fontsize",
+    "Framingham",
     "fredapi",
     "frommotherplant",
     "Fuente",
@@ -391,6 +398,7 @@
     "Mevinphos",
     "mgtog",
     "microbials",
+    "Middlesex",
     "mkdocs",
     "mkdocstrings",
     "Monoterpenes",
@@ -535,6 +543,7 @@
     "Sonoma",
     "sonomoa",
     "spacy",
+    "Speen",
     "spiderfier",
     "spinetoram",
     "Spinetoram",
@@ -555,6 +564,7 @@
     "statsmodels",
     "stec",
     "STEC",
+    "steephill",
     "stopwords",
     "striphtml",
     "subcollection",
@@ -576,6 +586,8 @@
     "terreus",
     "Tesseract",
     "testpypi",
+    "Tetrahydrocannabinolic",
+    "Tetrahydrocannabivarin",
     "textquery",
     "thca",
     "thcv",
@@ -606,6 +618,7 @@
     "unreview",
     "uoms",
     "urandom",
+    "usecols",
     "uslugify",
     "uspto",
     "uuid",
diff --git a/README.md b/README.md
index 24230733..d0813736 100644
--- a/README.md
+++ b/README.md
@@ -53,7 +53,7 @@ Cannlytics is made available with ❤️ and your good will. Please consider mak
 | 💸 PayPal Donation | <https://paypal.me/cannlytics> |
 | 💵 Venmo Donation | <https://www.venmo.com/u/cannlytics> |
 | 🪙 Bitcoin donation address| 34CoUcAFprRnLnDTHt6FKMjZyvKvQHb6c6 |
-| ⚡ Ethereum donation address | 0xa466d0893e3d4f584c5a7aec1104b9f1d541cf1c |
+| ⚡ Ethereum donation address | `cannlytics.eth` |
 
 ## 🏛️ License <a name="license"></a>
 
diff --git a/ai/curation/get_rawgarden_data/get_all_rawgarden_data.py b/ai/curation/get_rawgarden_data/get_all_rawgarden_data.py
index 5e64e5b7..82efef49 100644
--- a/ai/curation/get_rawgarden_data/get_all_rawgarden_data.py
+++ b/ai/curation/get_rawgarden_data/get_all_rawgarden_data.py
@@ -141,6 +141,7 @@ def get_rawgarden_products(
 def download_rawgarden_coas(
         items: pd.DataFrame,
         pause: Optional[float] = 0.24,
+        replace: Optional[bool] = False,
         verbose: Optional[bool] = True,
     ) -> None:
     """Download Raw Garden product COAs to `product_subtype` folders.
@@ -149,6 +150,8 @@ def download_rawgarden_coas(
             and `lab_results_url` to download.
         pause (float): A pause to respect the server serving the PDFs,
             `0.24` seconds by default (optional).
+        replace (bool): Whether or not to replace any existing PDFs,
+            `False` by default (optional).
         verbose (bool): Whether or not to print status, `True` by
             default (optional).
     """
@@ -172,6 +175,8 @@ def download_rawgarden_coas(
         filename = url.split('/')[-1]
         folder = kebab_case(subtype)
         outfile = os.path.join(COA_PDF_DIR, folder, filename)
+        if os.path.isfile(outfile):
+            continue
         response = requests.get(url, headers=DEFAULT_HEADERS)
         with open(outfile, 'wb') as pdf:
             pdf.write(response.content)
@@ -317,65 +322,65 @@ def upload_lab_results(
 #-----------------------------------------------------------------------
 if __name__ == '__main__':
 
-    # # === Setup ===
+    # === Setup ===
     from cannlytics.data.coas import CoADoc
 
-    # # Support command line usage.
-    # # Future work: Allow data dirs to be specified from the command line.
-    # import argparse
-    # try:
-    #     parser = argparse.ArgumentParser()
-    #     parser.add_argument('--days_ago', dest='days_ago', type=int)
-    #     parser.add_argument('--get_all', dest='get_all', type=bool)
-    #     args = parser.parse_args()
-    # except SystemExit:
-    #     args = {}
-
-    # # Specify collection period.
-    # DAYS_AGO = args.get('days_ago', 1)
-    # GET_ALL =  args.get('get_all', True)
-
-    # # === Data Collection ===
-
-    # # Get the most recent Raw Garden products.
-    # start = datetime.now() - timedelta(days=DAYS_AGO)
-    # if GET_ALL:
-    #     start = datetime(year=2018, month=1, day=1)
-    # products = get_rawgarden_products(start=start)
-    # filenames = products['coa_pdf'].to_list()
-
-    # # Download Raw Garden product COAs to `product_subtype` folders.
-    # download_rawgarden_coas(products, pause=0.24, verbose=True)
-
-    # # === Data Curation ===
-
-    # # Parse COA PDFs with CoADoc.
-    # coa_data, unidentified_coas = parse_rawgarden_coas(
-    #     COA_PDF_DIR,
-    #     filenames=filenames,
-    #     temp_path=TEMP_PATH,
-    #     verbose=True,
-    # )
-
-    # # Merge the `products`'s `product_subtype` with the COA data.
-    # coa_df = rmerge(
-    #     pd.DataFrame(coa_data),
-    #     products,
-    #     on='coa_pdf',
-    #     how='left',
-    #     replace='right',
-    # )
-
-    # # Create hashes.
-    # coa_df = coa_df.where(pd.notnull(coa_df), None)
-    # coa_df['results_hash'] = coa_df['results'].apply(
-    #     lambda x: create_hash(x),
-    # )
-    # coa_df['sample_hash'] = coa_df.loc[:, coa_df.columns != 'sample_hash'].apply(
-    #     lambda x: create_hash(x.to_dict()),
-    #     axis=1,
-    # )
-    # datafile_hash = create_hash(coa_df)
+    # Support command line usage.
+    # Future work: Allow data dirs to be specified from the command line.
+    import argparse
+    try:
+        parser = argparse.ArgumentParser()
+        parser.add_argument('--days_ago', dest='days_ago', type=int)
+        parser.add_argument('--get_all', dest='get_all', type=bool)
+        args = parser.parse_args()
+    except SystemExit:
+        args = {}
+
+    # Specify collection period.
+    DAYS_AGO = args.get('days_ago', 1)
+    GET_ALL =  args.get('get_all', True)
+
+    # === Data Collection ===
+
+    # Get the most recent Raw Garden products.
+    start = datetime.now() - timedelta(days=DAYS_AGO)
+    if GET_ALL:
+        start = datetime(year=2018, month=1, day=1)
+    products = get_rawgarden_products(start=start)
+    filenames = products['coa_pdf'].to_list()
+
+    # Download Raw Garden product COAs to `product_subtype` folders.
+    download_rawgarden_coas(products, pause=0.24, verbose=True)
+
+    # === Data Curation ===
+
+    # Parse COA PDFs with CoADoc.
+    coa_data, unidentified_coas = parse_rawgarden_coas(
+        COA_PDF_DIR,
+        filenames=filenames,
+        temp_path=TEMP_PATH,
+        verbose=True,
+    )
+
+    # Merge the `products`'s `product_subtype` with the COA data.
+    coa_df = rmerge(
+        pd.DataFrame(coa_data),
+        products,
+        on='coa_pdf',
+        how='left',
+        replace='right',
+    )
+
+    # Create hashes.
+    coa_df = coa_df.where(pd.notnull(coa_df), None)
+    coa_df['results_hash'] = coa_df['results'].apply(
+        lambda x: create_hash(x),
+    )
+    coa_df['sample_hash'] = coa_df.loc[:, coa_df.columns != 'sample_hash'].apply(
+        lambda x: create_hash(x.to_dict()),
+        axis=1,
+    )
+    datafile_hash = create_hash(coa_df)
 
     # # === Data Archiving ===
 
@@ -414,7 +419,7 @@ def upload_lab_results(
     # upload_file(storage_datafile, datafile, bucket_name=BUCKET_NAME)
     # upload_file(storage_error_file, error_file, bucket_name=BUCKET_NAME)
 
-    # # == Data Aggregation ===
+    # == Data Aggregation ===
 
     # # Initialize the COA parser.
     # parser = CoADoc()
diff --git a/api/auth/__init__.py b/api/auth/__init__.py
index 1bbe31f9..e69de29b 100644
--- a/api/auth/__init__.py
+++ b/api/auth/__init__.py
@@ -1,14 +0,0 @@
-# -*- coding: utf-8 -*-
-
-"""
-api.auth
-~~~~~~~~~~~~~~~~~~~
-Authentication API endpoints.
-"""
-
-
-__author__ = 'Keegan Skeate'
-__contact__ = 'keegan@cannlytics.com'
-
-
-# from .auth import authenticate_request
diff --git a/api/data/__init__.py b/api/data/__init__.py
index 18baf5be..3ebd2d84 100644
--- a/api/data/__init__.py
+++ b/api/data/__init__.py
@@ -4,23 +4,36 @@
 
 Authors: Keegan Skeate <https://github.com/keeganskeate>
 Created: 9/8/2022
-Updated: 9/8/2022
+Updated: 9/26/2022
 License: MIT License <https://github.com/cannlytics/cannlytics/blob/main/LICENSE>
 """
-import coa_data
-import lab_data
-import lab_analyses_data
-import lab_results_data
-import patent_data
-import regulation_data
-import state_data
-import strain_data
+from api.data.data import data_base
+from api.data.coa_data import coa_data, download_coa_data
+from api.data.lab_data import (
+    analyses_data,
+    analytes_data,
+    lab_data,
+    lab_logs,
+    lab_analyses,
+    lab_results_data,
+)
+from api.data.license_data import license_data
+from api.data.patent_data import patent_data
+from api.data.regulation_data import regulation_data
+from api.data.state_data import state_data
+from api.data.strain_data import strain_data
 
 __all__ = [
+    analyses_data,
+    analytes_data,
     coa_data,
+    data_base,
+    download_coa_data,
+    lab_analyses,
     lab_data,
-    lab_analyses_data,
+    lab_logs,
     lab_results_data,
+    license_data,
     patent_data,
     regulation_data,
     state_data,
diff --git a/api/data/ccrs_data.py b/api/data/ccrs_data.py
index ab3f5c12..d84b10ff 100644
--- a/api/data/ccrs_data.py
+++ b/api/data/ccrs_data.py
@@ -5,7 +5,7 @@
 Authors: Keegan Skeate <https://github.com/keeganskeate>
 Created: 4/19/2022
 Updated: 4/20/2022
-License: MIT License <https://github.com/cannlytics/cannlytics-website/blob/main/LICENSE>
+License: MIT License <https://github.com/cannlytics/cannlytics/blob/main/LICENSE>
 
 Description: API endpoints to interface with CCRS data and statistics.
 """
diff --git a/api/data/data.py b/api/data/data.py
index 6ce10d01..884a1c8f 100644
--- a/api/data/data.py
+++ b/api/data/data.py
@@ -5,7 +5,7 @@
 Authors: Keegan Skeate <https://github.com/keeganskeate>
 Created: 5/30/2021
 Updated: 6/26/2022
-License: MIT License <https://github.com/cannlytics/cannlytics-website/blob/main/LICENSE>
+License: MIT License <https://github.com/cannlytics/cannlytics/blob/main/LICENSE>
 
 Description: API endpoints to interface with datasets.
 """
@@ -48,7 +48,7 @@ def data_nfts(request, state=None):
 
 
 @api_view(['GET', 'POST'])
-def datasets(request, state=None):
+def data_base(request, state=None):
     """Get or update information about datasets."""
 
     # Authenticate the user.
diff --git a/api/data/lab_analyses_data.py b/api/data/lab_analyses_data.py
deleted file mode 100644
index 540b3a48..00000000
--- a/api/data/lab_analyses_data.py
+++ /dev/null
@@ -1,98 +0,0 @@
-"""
-Analyses Data Endpoints | Cannlytics API
-Copyright (c) 2022 Cannlytics
-
-Authors: Keegan Skeate <https://github.com/keeganskeate>
-Created: 1/9/2022
-Updated: 1/9/2022
-License: MIT License <https://github.com/cannlytics/cannlytics-website/blob/main/LICENSE>
-
-Description: API endpoints to interface with lab analyses.
-"""
-# External imports
-from rest_framework.decorators import api_view
-from rest_framework.response import Response
-
-# Internal imports
-from cannlytics.firebase import (
-    get_collection,
-    get_document,
-)
-
-
-@api_view(['GET'])
-def analyses_data(request, analysis_id=None):
-    """Get data about lab test analyses (public API endpoint)."""
-    data = []
-    collection = 'public/data/analyses'
-    if request.method == 'GET':
-
-        # Get a specific observation.
-        if analysis_id is not None:
-            data = get_document(f'{collection}/{analysis_id}')
-
-        # Otherwise query observations.
-        else:
-
-            # Define query parameters.
-            filters = []
-            order_by = request.query_params.get('order_by', 'name')
-            limit = request.query_params.get('limit')
-
-            # Optional: Implement more queries the user can use.
-            # Apply user-specified filters.
-            # param = request.query_params.get('param')
-            # if param:
-            #     filters.append({'key': 'param', 'operation': '==', 'value': param})
-
-            # Query and return the docs.
-            data = get_collection(
-                collection,
-                desc=False,
-                filters=filters,
-                limit=limit,
-                order_by=order_by,
-            )
-
-    # Return the data.
-    response = {'success': True, 'data': data}
-    return Response(response, status=200)
-
-
-@api_view(['GET'])
-def analytes_data(request, analysis_id=None):
-    """Get data about lab test analytes (public API endpoint)."""
-    data = []
-    collection = 'public/data/analyses'
-    if request.method == 'GET':
-
-        # Get a specific observation.
-        if analysis_id is not None:
-            data = get_document(f'{collection}/{analysis_id}')
-
-        # Otherwise query observations.
-        else:
-
-            # Define query parameters.
-            filters = []
-            order_by = request.query_params.get('order_by', 'name')
-            limit = request.query_params.get('limit')
-
-            # Optional: Implement more queries the user can use.
-            # Apply user-specified filters.
-            # param = request.query_params.get('param')
-            # if param:
-            #     filters.append({'key': 'param', 'operation': '==', 'value': param})
-
-            # Query and return the docs.
-            data = get_collection(
-                collection,
-                desc=False,
-                filters=filters,
-                limit=limit,
-                order_by=order_by,
-            )
-
-    # Return the data.
-    response = {'success': True, 'data': data}
-    return Response(response, status=200)
diff --git a/api/data/lab_data.py b/api/data/lab_data.py
index 91ed8adc..cf895a5d 100644
--- a/api/data/lab_data.py
+++ b/api/data/lab_data.py
@@ -5,14 +5,15 @@
 Authors: Keegan Skeate <https://github.com/keeganskeate>
 Created: 5/30/2021
 Updated: 12/31/2021
-License: MIT License <https://github.com/cannlytics/cannlytics-website/blob/main/LICENSE>
+License: MIT License <https://github.com/cannlytics/cannlytics/blob/main/LICENSE>
 
 Description: API endpoints to interface with lab data, data about labs and data
 that labs provide.
 """
-
 # Standard imports
 from datetime import datetime
+import json
+import re
 
 # External imports
 from django.template.defaultfilters import slugify
@@ -21,13 +22,90 @@
 from rest_framework.response import Response
 
 # Internal imports
-from api.auth import auth
+from cannlytics.auth import auth
 from cannlytics.firebase import (
     get_collection,
     get_document,
     update_document,
 )
 
+@api_view(['GET'])
+def analyses_data(request, analysis_id=None):
+    """Get data about lab test analyses (public API endpoint)."""
+    data = []
+    collection = 'public/data/analyses'
+    if request.method == 'GET':
+
+        # Get a specific observation.
+        if analysis_id is not None:
+            data = get_document(f'{collection}/{analysis_id}')
+
+        # Otherwise query observations.
+        else:
+
+            # Define query parameters.
+            filters = []
+            order_by = request.query_params.get('order_by', 'name')
+            limit = request.query_params.get('limit')
+
+            # Optional: Implement more queries the user can use.
+            # Apply user-specified filters.
+            # param = request.query_params.get('param')
+            # if param:
+            #     filters.append({'key': 'param', 'operation': '==', 'value': param})
+
+            # Query and return the docs.
+            data = get_collection(
+                collection,
+                desc=False,
+                filters=filters,
+                limit=limit,
+                order_by=order_by,
+            )
+
+    # Return the data.
+    response = {'success': True, 'data': data}
+    return Response(response, status=200)
+
+
+@api_view(['GET'])
+def analytes_data(request, analysis_id=None):
+    """Get data about lab test analytes (public API endpoint)."""
+    data = []
+    collection = 'public/data/analyses'
+    if request.method == 'GET':
+
+        # Get a specific observation.
+        if analysis_id is not None:
+            data = get_document(f'{collection}/{analysis_id}')
+
+        # Otherwise query observations.
+        else:
+
+            # Define query parameters.
+            filters = []
+            order_by = request.query_params.get('order_by', 'name')
+            limit = request.query_params.get('limit')
+
+            # Optional: Implement more queries the user can use.
+            # Apply user-specified filters.
+            # param = request.query_params.get('param')
+            # if param:
+            #     filters.append({'key': 'param', 'operation': '==', 'value': param})
+
+            # Query and return the docs.
+            data = get_collection(
+                collection,
+                desc=False,
+                filters=filters,
+                limit=limit,
+                order_by=order_by,
+            )
+
+    # Return the data.
+    response = {'success': True, 'data': data}
+    return Response(response, status=200)
+
 
 @api_view(['GET'])
 def lab_data(request, license_number=None):
@@ -97,3 +175,147 @@ def lab_analyses(request, org_id, format=None):
     elif request.method == 'POST':
         # TODO: Create an analysis.
         return Response({'success': True, 'data': 'Under construction'}, content_type='application/json')
+
+
+def get_lab_results_filter():
+    """Get any lab results filter."""
+    raise NotImplementedError
+
+
+# FIXME: Get these from `cannlytics.utils.constants`.
+ANALYTES = [
+    'cbc',
+    'cbd',
+    'cbda',
+    'cbg',
+    'cbga',
+    'cbn',
+    'delta_8_thc',
+    'delta_9_thc',
+    'thca',
+    'thcv',
+    'alpha_bisabolol',
+    'alpha_pinene',
+    'alpha_terpinene',
+    'beta_caryophyllene',
+    'beta_myrcene',
+    'beta_pinene',
+    'camphene',
+    'carene',
+    'caryophyllene_oxide',
+    'd_limonene',
+    'eucalyptol',
+    'gamma_terpinene',
+    'geraniol',
+    'guaiol',
+    'humulene',
+    'isopulegol',
+    'linalool',
+    'nerolidol',
+    'ocimene',
+    'p_cymene',
+    'terpinene',
+    'terpinolene',
+    'total_cannabinoids',
+    'total_cbd',
+    'total_cbg',
+    'total_terpenes',
+    'total_thc'
+    'terpinenes',
+]
+
+OPERATIONS = {
+    'ge': '>=',
+    'le': '<=',
+    'l': '<',
+    'g': '>',
+}
+
+
+@api_view(['GET'])
+def lab_results_data(request, strain_name=None):
+    """Get lab results data (public API endpoint)."""
+    data = []
+    collection = 'public/data/lab_results'
+    if request.method == 'GET':
+
+        # FIXME: Search by metrc_id if passed as a parameter!
+
+        # Get a specific observation.
+        if strain_name is None:
+            strain_name = request.query_params.get(
+                'strain',
+                request.query_params.get('strain_name'),
+            )
+        if strain_name is not None:
+            strain_name = strain_name.replace('+', ' ')
+            data = get_document(f'{collection}/{strain_name}')
+
+        # Otherwise query observations.
+        else:
+
+            # Define query parameters.
+            filters = []
+            order_by = request.query_params.get('order', 'strain_name')
+            desc = request.query_params.get('desc', False)
+            limit = request.query_params.get('limit', None)
+            if limit:
+                limit = int(limit)
+
+            # Allow user to specify whether to include all (default) or any.
+            # FIXME: Currently any matches are returned, all matches would be nice.
+            operation = 'array_contains_any'
+            any = request.query_params.get('any')
+            if any:
+                operation = 'array_contains_any'
+
+            # Allow user to query by effect or aroma.
+            # Future work: Is there any way to query by effect AND aroma?
+            effects = request.query_params.get('effects')
+            aromas = request.query_params.get('aromas')
+            if effects:
+                outcomes = [f'effect_{x.replace(" ", "_").lower()}' for x in json.loads(effects)]
+                filters.append({
+                    'key': 'predicted_effects',
+                    'operation': operation,
+                    'value': outcomes,
+                })
+            elif aromas:
+                outcomes = [f'aroma_{x.replace(" ", "_").lower()}' for x in json.loads(aromas)]
+                filters.append({
+                    'key': 'predicted_aromas',
+                    'operation': operation,
+                    'value': outcomes,
+                })
+
+            # Allow user to query by cannabinoid / terpene concentrations.
+            # Handles open and closed ranges for a single analyte.
+            # FIXME: This doesn't appear to work with 2 operations.
+            for param in request.query_params:
+                if param in ANALYTES:
+                    order_by = param
+                    desc = request.query_params.get('desc', True)
+                    value = request.query_params[param]
+                    ops = re.sub(r'\d.+', '', value).split('+')
+                    values = [re.sub(r'[^0-9.]', '', x) for x in value.split('+')]
+                    for i, op in enumerate(ops):
+                        print(param, op, values[i])
+                        filters.append({
+                            'key': param,
+                            'operation': OPERATIONS[op],
+                            'value': float(values[i]),
+                        })
+                    break
+
+            # Query and return the docs.
+            data = get_collection(
+                collection,
+                desc=desc,
+                filters=filters,
+                limit=limit,
+                order_by=order_by,
+            )
+
+    # Return the data.
+    response = {'success': True, 'data': data}
+    return Response(response, status=200)
diff --git a/api/data/lab_results_data.py b/api/data/lab_results_data.py
deleted file mode 100644
index c23434d6..00000000
--- a/api/data/lab_results_data.py
+++ /dev/null
@@ -1,166 +0,0 @@
-"""
-Lab Result Data Endpoints | Cannlytics API
-Copyright (c) 2022 Cannlytics
-
-Authors: Keegan Skeate <https://github.com/keeganskeate>
-Created: 7/10/2022
-Updated: 7/10/2022
-License: MIT License <https://github.com/cannlytics/cannlytics/blob/main/LICENSE>
-
-Description: API endpoints to interface with cannabis lab results data.
-"""
-# External imports.
-import json
-import re
-from rest_framework.decorators import api_view
-from rest_framework.response import Response
-
-# Internal imports.
-from cannlytics.firebase import (
-    get_collection,
-    get_document,
-)
-from cannlytics.utils.utils import kebab_case
-
-
-def get_lab_results_filter():
-    """Get any lab results filter."""
-    raise NotImplementedError
-
-
-ANALYTES = [
-    'cbc',
-    'cbd',
-    'cbda',
-    'cbg',
-    'cbga',
-    'cbn',
-    'delta_8_thc',
-    'delta_9_thc',
-    'thca',
-    'thcv',
-    'alpha_bisabolol',
-    'alpha_pinene',
-    'alpha_terpinene',
-    'beta_caryophyllene',
-    'beta_myrcene',
-    'beta_pinene',
-    'camphene',
-    'carene',
-    'caryophyllene_oxide',
-    'd_limonene',
-    'eucalyptol',
-    'gamma_terpinene',
-    'geraniol',
-    'guaiol',
-    'humulene',
-    'isopulegol',
-    'linalool',
-    'nerolidol',
-    'ocimene',
-    'p_cymene',
-    'terpinene',
-    'terpinolene',
-    'total_cannabinoids',
-    'total_cbd',
-    'total_cbg',
-    'total_terpenes',
-    'total_thc'
-    'terpinenes',
-]
-
-OPERATIONS = {
-    'ge': '>=',
-    'le': '<=',
-    'l': '<',
-    'g': '>',
-}
-
-
-@api_view(['GET'])
-def lab_results_data(request, strain_name=None):
-    """Get lab results data (public API endpoint)."""
-    data = []
-    collection = 'public/data/lab_results'
-    if request.method == 'GET':
-
-        # FIXME: Search by metrc_id if passed as a parameter!
-
-        # Get a specific observation.
-        if strain_name is None:
-            strain_name = request.query_params.get(
-                'strain',
-                request.query_params.get('strain_name'),
-            )
-        if strain_name is not None:
-            strain_name = strain_name.replace('+', ' ')
-            data = get_document(f'{collection}/{strain_name}')
-
-        # Otherwise query observations.
-        else:
-
-            # Define query parameters.
-            filters = []
-            order_by = request.query_params.get('order', 'strain_name')
-            desc = request.query_params.get('desc', False)
-            limit = request.query_params.get('limit', None)
-            if limit:
-                limit = int(limit)
-
-            # Allow user to specify whether to include all (default) or any.
-            # FIXME: Currently any matches are returned, all matches would be nice.
-            operation = 'array_contains_any'
-            any = request.query_params.get('any')
-            if any:
-                operation = 'array_contains_any'
-
-            # Allow user to query by effect or aroma.
-            # Future work: Is there any way to query by effect AND aroma?
-            effects = request.query_params.get('effects')
-            aromas = request.query_params.get('aromas')
-            if effects:
-                outcomes = [f'effect_{x.replace(" ", "_").lower()}' for x in json.loads(effects)]
-                filters.append({
-                    'key': 'predicted_effects',
-                    'operation': operation,
-                    'value': outcomes,
-                })
-            elif aromas:
-                outcomes = [f'aroma_{x.replace(" ", "_").lower()}' for x in json.loads(aromas)]
-                filters.append({
-                    'key': 'predicted_aromas',
-                    'operation': operation,
-                    'value': outcomes,
-                })
-
-            # Allow user to query by cannabinoid / terpene concentrations.
-            # Handles open and closed ranges for a single analyte.
-            # FIXME: This doesn't appear to work with 2 operations.
-            for param in request.query_params:
-                if param in ANALYTES:
-                    order_by = param
-                    desc = request.query_params.get('desc', True)
-                    value = request.query_params[param]
-                    ops = re.sub(r'\d.+', '', value).split('+')
-                    values = [re.sub(r'[^0-9.]', '', x) for x in value.split('+')]
-                    for i, op in enumerate(ops):
-                        print(param, op, values[i])
-                        filters.append({
-                            'key': param,
-                            'operation': OPERATIONS[op],
-                            'value': float(values[i]),
-                        })
-                    break
-
-            # Query and return the docs.
-            data = get_collection(
-                collection,
-                desc=desc,
-                filters=filters,
-                limit=limit,
-                order_by=order_by,
-            )
-
-    # Return the data.
-    response = {'success': True, 'data': data}
-    return Response(response, status=200)
diff --git a/api/data/license_data.py b/api/data/license_data.py
new file mode 100644
index 00000000..6ed4855f
--- /dev/null
+++ b/api/data/license_data.py
@@ -0,0 +1,59 @@
+"""
+License Data Endpoints | Cannlytics API
+Copyright (c) 2021-2022 Cannlytics
+
+Authors: Keegan Skeate <https://github.com/keeganskeate>
+Created: 9/26/2022
+Updated: 9/26/2022
+License: MIT License <https://github.com/cannlytics/cannlytics/blob/main/LICENSE>
+
+Description: API endpoints to interface with cannabis license data.
+"""
+# External imports
+from rest_framework.decorators import api_view
+from rest_framework.response import Response
+
+# Internal imports
+from cannlytics.firebase import (
+    get_collection,
+    get_document,
+)
+
+
+@api_view(['GET'])
+def license_data(request, state=None):
+    """Get data about cannabis licenses (public API endpoint)."""
+    data = []
+    collection = 'public/data/licenses'
+    if request.method == 'GET':
+
+        # Get a specific observation.
+        if state is not None:
+            data = get_document(f'{collection}/{state}')
+
+        # Otherwise query observations.
+        else:
+
+            # Define query parameters.
+            filters = []
+            order_by = request.query_params.get('order_by', 'state')
+            limit = request.query_params.get('limit')
+
+            # Optional: Implement more queries the user can use.
+            # Apply user-specified filters.
+            # param = request.query_params.get('param')
+            # if param:
+            #     filters.append({'key': 'param', 'operation': '==', 'value': param})
+
+            # Query and return the docs.
+            data = get_collection(
+                collection,
+                desc=False,
+                filters=filters,
+                limit=limit,
+                order_by=order_by,
+            )
+
+    # Return the data.
+    response = {'success': True, 'data': data}
+    return Response(response, status=200)
diff --git a/api/data/regulation_data.py b/api/data/regulation_data.py
index e926d219..ef1fc318 100644
--- a/api/data/regulation_data.py
+++ b/api/data/regulation_data.py
@@ -5,7 +5,7 @@
 Authors: Keegan Skeate <https://github.com/keeganskeate>
 Created: 4/21/2021
 Updated: 1/9/2022
-License: MIT License <https://github.com/cannlytics/cannlytics-website/blob/main/LICENSE>
+License: MIT License <https://github.com/cannlytics/cannlytics/blob/main/LICENSE>
 
 Description: API endpoints to interface with cannabis regulation data.
 """
diff --git a/api/data/state_data.py b/api/data/state_data.py
index b3db3c82..45915302 100644
--- a/api/data/state_data.py
+++ b/api/data/state_data.py
@@ -5,7 +5,7 @@
 Authors: Keegan Skeate <https://github.com/keeganskeate>
 Created: 1/9/2022
 Updated: 7/10/2022
-License: MIT License <https://github.com/cannlytics/cannlytics-website/blob/main/LICENSE>
+License: MIT License <https://github.com/cannlytics/cannlytics/blob/main/LICENSE>
 
 Description: API endpoints to interface with datasets.
 """
diff --git a/api/lims/__init__.py b/api/lims/__init__.py
index b59023c4..ce18077f 100644
--- a/api/lims/__init__.py
+++ b/api/lims/__init__.py
@@ -7,34 +7,67 @@
 Updated: 9/8/2022
 License: MIT License <https://github.com/cannlytics/cannlytics/blob/main/LICENSE>
 """
-from analyses import analyses
-from analytes import analytes
-from areas import areas
-from certificates import certificates
-from contacts import contacts
-from instruments import instruments
-from inventory import inventory
-from invoices import invoices
-from measurements import measurements
-from projects import projects
-from results import results
-from samples import samples
-from transfers import transfers
-from waste import waste
+from api.lims.analyses.analyses import analyses
+from api.lims.analytes.analytes import analytes
+from api.lims.areas.areas import areas
+from api.lims.certificates.certificates import (
+    approve_coas,
+    create_coas,
+    generate_coas,
+    post_coas,
+    release_coas,
+    review_coas,
+)
+from api.lims.contacts.contacts import contacts, people
+from api.lims.instruments.instruments import instruments
+from api.lims.inventory.inventory import inventory
+from api.lims.invoices.invoices import invoices
+from api.lims.measurements.measurements import measurements
+from api.lims.projects.projects import projects
+from api.lims.results.results import (
+    results,
+    calculate_results,
+    post_results,
+    release_results,
+    send_results,
+    templates,
+)
+from api.lims.samples.samples import samples
+from api.lims.transfers.transfers import (
+    transfers,
+    transporters,
+    vehicles,
+    receive_transfers,
+)
+from api.lims.waste.waste import waste
 
 __all__ = [
     analyses,
     analytes,
     areas,
-    certificates,
+    approve_coas,
+    create_coas,
+    generate_coas,
+    post_coas,
+    release_coas,
+    review_coas,
     contacts,
     instruments,
     inventory,
     invoices,
     measurements,
+    people,
     projects,
     results,
+    calculate_results,
+    post_results,
+    release_results,
+    send_results,
+    templates,
     samples,
     transfers,
+    transporters,
+    vehicles,
+    receive_transfers,
     waste,
 ]
diff --git a/api/lims/analyses/readme.md b/api/lims/analyses/readme.md
index dca84974..b03877a7 100644
--- a/api/lims/analyses/readme.md
+++ b/api/lims/analyses/readme.md
@@ -18,7 +18,7 @@ For all requests, you will need to pass an `organization_id` parameter. Ensure t
     }
 
     # Define the API and your organization.
-    BASE = 'https://console.cannlytics.com/api'
+    BASE = 'https://cannlytics.com/api'
     ORG_ID = 'test-company'
     ```
 
@@ -35,7 +35,7 @@ For all requests, you will need to pass an `organization_id` parameter. Ensure t
     };
 
     // Define the API and your organization.
-    const base = 'https://console.cannlytics.com/api';
+    const base = 'https://cannlytics.com/api';
     const orgId = 'test-company';
     ```
 
diff --git a/api/lims/certificates/certificates.py b/api/lims/certificates/certificates.py
index 6394a042..b684c5c7 100644
--- a/api/lims/certificates/certificates.py
+++ b/api/lims/certificates/certificates.py
@@ -20,7 +20,7 @@
 # Internal imports
 from api.api import get_objects, update_object, delete_object
 from api.auth.auth import verify_user_pin # Does this belong in `cannlytics`?
-from api.results.results import calculate_results
+from api.lims.results.results import calculate_results
 from cannlytics.auth.auth import authenticate_request, sha256_hmac
 from cannlytics.firebase import get_collection, get_document
 from cannlytics.lims.certificates import generate_coas
diff --git a/api/readme.md b/api/readme.md
index c2ab146b..029148fd 100644
--- a/api/readme.md
+++ b/api/readme.md
@@ -103,7 +103,7 @@ The `traceability` API endpoints are primarily used to add track and trace funct
 | -------- | ------- | ----------- |
 | `\traceability\employees` | `GET`, `POST`, `DELETE` | Manage tracked employees. |
 | `\traceability\items` | `GET`, `POST`, `DELETE` | Manage tracked items. |
-| `\traceability\lab-tests` | `GET`, `POST`, `DELETE` | Manage tracked lab tests. |
+| `\traceability\results` | `GET`, `POST`, `DELETE` | Manage tracked lab tests. |
 | `\traceability\locations` | `GET`, `POST`, `DELETE` | Manage tracked locations. |
 | `\traceability\packages` | `GET`, `POST`, `DELETE` | Manage tracked packages. |
 | `\traceability\strains` | `GET`, `POST`, `DELETE` | Manage tracked strains. |
diff --git a/api/stats/__init__.py b/api/stats/__init__.py
index e5e9924e..ef57edda 100644
--- a/api/stats/__init__.py
+++ b/api/stats/__init__.py
@@ -7,13 +7,14 @@
 Updated: 6/1/2022
 License: MIT License <https://github.com/cannlytics/cannlytics-website/blob/main/LICENSE>
 """
-import effects_stats
-import patent_stats
-import personality_stats
-import recommendation_stats
+from api.stats.effects_stats import effects_stats, record_effects
+from api.stats.patent_stats import patent_stats
+from api.stats.personality_stats import personality_stats
+from api.stats.recommendation_stats import recommendation_stats
 
 __all__ = [
     effects_stats,
+    record_effects,
     patent_stats,
     personality_stats,
     recommendation_stats,
diff --git a/api/traceability/__init__.py b/api/traceability/__init__.py
index 39d269b9..a842e1f9 100644
--- a/api/traceability/__init__.py
+++ b/api/traceability/__init__.py
@@ -4,11 +4,27 @@
 
 Authors: Keegan Skeate <https://github.com/keeganskeate>
 Created: 9/8/2022
-Updated: 9/8/2022
+Updated: 9/26/2022
 License: MIT License <https://github.com/cannlytics/cannlytics/blob/main/LICENSE>
 """
-import traceability
+from api.traceability.traceability import (
+    employees,
+    items,
+    lab_tests,
+    locations,
+    packages,
+    strains,
+    transfers,
+    delete_license,
+)
 
 __all__ = [
-    traceability,
+    employees,
+    items,
+    lab_tests,
+    locations,
+    packages,
+    strains,
+    transfers,
+    delete_license,
 ]
diff --git a/api/traceability/readme.md b/api/traceability/readme.md
index 1b0974f3..e9647bce 100644
--- a/api/traceability/readme.md
+++ b/api/traceability/readme.md
@@ -9,8 +9,8 @@
   /traceability/employees/<license_number>
   /traceability/items
   /traceability/items/<item_id>
-  /traceability/lab-tests
-  /traceability/lab-tests/<test_id>
+  /traceability/results
+  /traceability/results/<test_id>
   /traceability/locations
   /traceability/locations/<area_id>
   /traceability/packages
diff --git a/api/traceability/traceability.py b/api/traceability/traceability.py
index 2eafead1..417a1ccd 100644
--- a/api/traceability/traceability.py
+++ b/api/traceability/traceability.py
@@ -4,7 +4,7 @@
 
 Authors: Keegan Skeate <https://github.com/keeganskeate>
 Created: 6/13/2021
-Updated: 7/19/2021
+Updated: 9/26/2022
 License: MIT License <https://github.com/cannlytics/cannlytics-website/blob/main/LICENSE>
 
 Description: API to interface with the Metrc API.
@@ -62,8 +62,9 @@ def initialize_traceability(project_id, license_number, version_id):
         version_id=version_id
     )
 
-    track = initialize_metrc(vendor_api_key, user_api_key)
-    return track
+    # Return a Metrc client.
+    return initialize_metrc(vendor_api_key, user_api_key)
+
 
 #-----------------------------------------------------------------------
 # Endpoints
diff --git a/api/urls.py b/api/urls.py
index 8a22a42a..1a2308a0 100644
--- a/api/urls.py
+++ b/api/urls.py
@@ -4,10 +4,10 @@
 
 Authors: Keegan Skeate <https://github.com/keeganskeate>
 Created: 4/21/2021
-Updated: 9/11/2022
-License: MIT License <https://github.com/cannlytics/cannlytics-website/blob/main/LICENSE>
+Updated: 9/26/2022
+License: MIT License <https://github.com/cannlytics/cannlytics/blob/main/LICENSE>
 
-Description: API URLs to interface with cannabis analytics.
+Description: API URLs to interface with cannabis data and analytics.
 """
 # External imports.
 from django.urls import include, path
@@ -33,7 +33,7 @@
 
 urlpatterns = [
 
-    # Base API endpoint.
+    # Base API endpoint for users to discover an index of endpoints.
     path('', base.index, name='index'),
 
     # Authentication API endpoints.
@@ -52,33 +52,35 @@
     # Data API endpoints.
     path('data', include([
 
-        # Base data endpoint.
-        path('', api.data.data.datasets),
+        # Base data API endpoint for users to find available datasets.
+        path('', api.data.data_base),
 
-        # Analyses data API endpoints.
-        path('/analyses', api.data.lab_analyses_data.analyses_data),
-        path('/analyses/<analysis_id', api.data.lab_analyses_data.analyses_data),
-        path('/analytes', api.data.lab_analyses_data.analytes_data),
-        path('/analytes/<analyte_id', api.data.lab_analyses_data.analytes_data),
+        # Analyses and analytes data API endpoints.
+        path('/analyses', api.data.analyses_data),
+        path('/analyses/<analysis_id', api.data.analyses_data),
+        path('/analytes', api.data.analytes_data),
+        path('/analytes/<analyte_id', api.data.analytes_data),
 
         # COA data and parser API endpoints.
-        path('/coas', api.data.coa_data.coa_data),
-        path('/coas/download', api.data.coa_data.download_coa_data),
+        path('/coas', api.data.coa_data),
+        path('/coas/download', api.data.download_coa_data),
 
         # Labs data API endpoints.
         path('labs', include([
-            path('', api.data.lab_data.lab_data),
-            path('/<license_number>', api.data.lab_data.lab_data),
-            path('/<license_number>/analyses', api.data.lab_data.lab_analyses),
-            path('/<license_number>/logs', api.data.lab_data.lab_logs),
+            path('', api.data.lab_data),
+            path('/<license_number>', api.data.lab_data),
+            path('/<license_number>/analyses', api.data.lab_analyses),
+            path('/<license_number>/logs', api.data.lab_logs),
         ])),
 
-        # TODO: Licensee data - Get data about specific licenses.
-
-        # Optional: Datasets data - Allow users to find available datasets.
+        # License data.
+        path('/licenses', include([
+            path('', api.data.license_data),
+            path('/<license_number>', api.data.license_data),
+        ])),
 
         # CCRS data endpoints.
-        # TODO: Re-design CCRS endpoints around timeseries statistics.
+        # FIXME: Re-design CCRS endpoints around timeseries statistics.
         # path('/ccrs/areas', ccrs_data.areas),
         # path('/ccrs/contacts', ccrs_data.contacts),
         # path('/ccrs/integrators', ccrs_data.integrators),
@@ -94,153 +96,153 @@
         # path('/ccrs/strains', ccrs_data.strains),
         # path('/ccrs/transfers', ccrs_data.transfers),
 
-        # Regulation data API endpoints.
-        path('/regulations', api.data.regulation_data),
-        path('/regulations/<state>', api.data.regulation_data.regulation_data),
-
-        # State data API endpoints.
-        # TODO: Dynamically route states.
-        # path('/state/<state>', state_data),
-        path('/states', api.data.state_data.state_data),
-        path('/states/ct', api.data.state_data.state_data_ct),
-        path('/states/ma', api.data.state_data.state_data_ma),
-        path('/states/ok', api.data.state_data.state_data_ok),
-        path('/states/or', api.data.state_data.state_data_or),
-        path('/states/wa', api.data.state_data.state_data_wa),
-
-        # TODO: Lab result data API endpoints.
+        # Lab result data API endpoints.
         path('/results', include([
-            path('', api.data.strain_data.strain_data),
-            path('/<lab_result_id>', api.data.strain_data.strain_data),
+            path('', api.data.strain_data),
+            path('/<lab_result_id>', api.data.strain_data),
         ])),
 
         # Strain data API endpoints.
         path('/strains', include([
-            path('', api.data.strain_data.strain_data),
-            path('/<strain_name>', api.data.strain_data.strain_data),
+            path('', api.data.strain_data),
+            path('/<strain_name>', api.data.strain_data),
         ])),
 
-        # TODO: Implement patents data endpoints.
-        # path('/patents', include([
-        #     path('', patent_data.patent_data),
-        #     path('/<patent_number>', patent_data.patent_data),
-        # ])),
+        # Regulation data API endpoints.
+        path('/regulations', api.data.regulation_data),
+        path('/regulations/<state>', api.data.regulation_data),
+
+        # State data API endpoints.
+        path('/states', api.data.state_data),
+        path('/states/<state>', api.data.state_data),
 
     ])),
 
     # Stats endpoints.
     path('stats', include([
-        path('', api.stats.effects_stats.effects_stats),
-        path('/effects', api.stats.effects_stats.effects_stats),
-        path('/effects/actual', api.stats.effects_stats.record_effects),
+        path('', api.stats.effects_stats),
+        path('/effects', api.stats.effects_stats),
+        path('/effects/actual', api.stats.record_effects),
         path('/effects/<strain>', api.stats.effects_stats),
-        path('/personality', api.stats.personality_stats.personality_stats),
-        path('/recommendations', api.stats.recommendation_stats.recommendation_stats),
-        path('/patents', api.stats.patent_stats.patent_stats),
+        path('/personality', api.stats.personality_stats),
+        path('/recommendations', api.stats.recommendation_stats),
+        path('/patents', api.stats.patent_stats),
+        # TODO: Flower Art API endpoint.
     ])),
 
     # LIMS API endpoints.
     path('lims', include([
         path('analyses', include([
-            path('', api.lims.analyses.analyses),
-            path('/<analysis_id>', api.lims.analyses.analyses),
+            path('', api.lims.analyses),
+            path('/<analysis_id>', api.lims.analyses),
         ])),
         path('analytes', include([
-            path('', api.lims.analytes.analytes),
-            path('/<analyte_id>', api.lims.analytes.analytes),
+            path('', api.lims.analytes),
+            path('/<analyte_id>', api.lims.analytes),
         ])),
         path('areas', include([
-            path('', api.lims.areas.areas),
-            path('/<area_id>', api.lims.areas.areas),
+            path('', api.lims.areas),
+            path('/<area_id>', api.lims.areas),
         ])),
         path('certificates', include([
-            path('/generate', api.lims.certificates.generate_coas),
-            path('/review', api.lims.certificates.review_coas),
-            path('/approve', api.lims.certificates.approve_coas),
-            path('/post', api.lims.certificates.post_coas),
-            path('/release', api.lims.certificates.release_coas),
+            path('/generate', api.lims.generate_coas),
+            path('/review', api.lims.review_coas),
+            path('/approve', api.lims.approve_coas),
+            path('/post', api.lims.post_coas),
+            path('/release', api.lims.release_coas),
         ])),
         path('contacts', include([
-            path('', api.lims.contacts.contacts),
-            path('/<contact_id>', api.lims.contacts.contacts),
+            path('', api.lims.contacts),
+            path('/<contact_id>', api.lims.contacts),
         ])),
         path('people', include([
-            path('', api.lims.contacts.people),
-            path('/<person_id>', api.lims.contacts.people),
+            path('', api.lims.people),
+            path('/<person_id>', api.lims.people),
         ])),
         path('inventory', include([
-            path('', api.lims.inventory.inventory),
-            path('/<inventory_id>', api.lims.inventory.inventory),
+            path('', api.lims.inventory),
+            path('/<inventory_id>', api.lims.inventory),
         ])),
         path('instruments', include([
-            path('', api.lims.instruments.instruments),
-            path('/<instrument_id>', api.lims.instruments.instruments),
+            path('', api.lims.instruments),
+            path('/<instrument_id>', api.lims.instruments),
         ])),
         path('invoices', include([
-            path('', api.lims.invoices.invoices),
-            path('/<invoice_id>', api.lims.invoices.invoices),
+            path('', api.lims.invoices),
+            path('/<invoice_id>', api.lims.invoices),
         ])),
         path('measurements', include([
-            path('', api.lims.measurements.measurements),
-            path('/<measurement_id>', api.lims.measurements.measurements),
+            path('', api.lims.measurements),
+            path('/<measurement_id>', api.lims.measurements),
         ])),
         path('projects', include([
-            path('', api.lims.projects.projects),
-            path('/<project_id>', api.lims.projects.projects),
+            path('', api.lims.projects),
+            path('/<project_id>', api.lims.projects),
         ])),
         path('results', include([
-            path('', api.lims.results.results),
-            path('/<result_id>', api.lims.results.results),
-            path('/calculate', api.lims.results.calculate_results),
-            path('/post', api.lims.results.post_results),
-            path('/release', api.lims.results.release_results),
-            path('/send', api.lims.results.send_results),
+            path('', api.lims.results),
+            path('/<result_id>', api.lims.results),
+            path('/calculate', api.lims.calculate_results),
+            path('/post', api.lims.post_results),
+            path('/release', api.lims.release_results),
+            path('/send', api.lims.send_results),
         ])),
         path('samples', include([
-            path('', api.lims.samples.samples),
-            path('/<sample_id>', api.lims.samples.samples),
+            path('', api.lims.samples),
+            path('/<sample_id>', api.lims.samples),
         ])),
         path('templates', include([
-            path('', api.lims.results.templates),
-            path('/<template_id>', api.lims.results.templates),
+            path('', api.lims.templates),
+            path('/<template_id>', api.lims.templates),
         ])),
         path('transfers', include([
-            path('', api.lims.transfers.transfers),
-            path('/<transfer_id>', api.lims.transfers.transfers),
-            path('/receive', api.lims.transfers.receive_transfers),
+            path('', api.lims.transfers),
+            path('/<transfer_id>', api.lims.transfers),
+            path('/receive', api.lims.receive_transfers),
         ])),
         path('transporters', include([
-            path('', api.lims.transfers.transporters),
-            path('/<transporter_id>', api.lims.transfers.transporters),
+            path('', api.lims.transporters),
+            path('/<transporter_id>', api.lims.transporters),
         ])),
         path('vehicles', include([
-            path('', api.lims.transfers.vehicles),
-            path('/<vehicle_id>', api.lims.transfers.vehicles),
+            path('', api.lims.vehicles),
+            path('/<vehicle_id>', api.lims.vehicles),
         ])),
         path('waste', include([
-            path('', api.lims.waste.waste),
-            path('/<waste_item_id>', api.lims.waste.waste),
+            path('', api.lims.waste),
+            path('/<waste_item_id>', api.lims.waste),
         ])),
     ])),
 
     # Traceability API endpoints.
-    # TODO: Implement remaining Metrc endpoints.
     path('traceability', include([
-        path('/delete-license', api.traceability.traceability.delete_license),
-        path('/employees', api.traceability.traceability.employees),
-        path('/employees/<license_number>', api.traceability.traceability.employees),
-        path('/items', api.traceability.traceability.items),
-        path('/items/<item_id>', api.traceability.traceability.items),
-        path('/lab-tests', api.traceability.traceability.lab_tests),
-        path('/lab-tests/<test_id>', api.traceability.traceability.lab_tests),
-        path('/locations', api.traceability.traceability.locations),
-        path('/locations/<area_id>', api.traceability.traceability.locations),
-        path('/packages', api.traceability.traceability.packages),
-        path('/packages/<package_id>', api.traceability.traceability.packages),
-        path('/strains', api.traceability.traceability.strains),
-        path('/strains/<strain_id>', api.traceability.traceability.strains),
-        path('/transfers', api.traceability.traceability.transfers),
-        path('/transfers/<transfer_id>', api.traceability.traceability.transfers),
+        path('/delete-license', api.traceability.delete_license),
+        path('/employees', api.traceability.employees),
+        path('/employees/<license_number>', api.traceability.employees),
+        path('/items', api.traceability.items),
+        path('/items/<item_id>', api.traceability.items),
+        path('/locations', api.traceability.locations),
+        path('/locations/<area_id>', api.traceability.locations),
+        path('/packages', api.traceability.packages),
+        path('/packages/<package_id>', api.traceability.packages),
+        path('/results', api.traceability.lab_tests),
+        path('/results/<test_id>', api.traceability.lab_tests),
+        path('/strains', api.traceability.strains),
+        path('/strains/<strain_id>', api.traceability.strains),
+        path('/transfers', api.traceability.transfers),
+        path('/transfers/<transfer_id>', api.traceability.transfers),
+        # TODO: Implement remaining Metrc endpoints:
+        # - deliveries
+        # - categories
+        # - facilities
+        # - harvests
+        # - patients
+        # - plants
+        # - plantbatches
+        # - receipts
+        # - transfers/templates
+        # - transaction
+        # - waste
     ])),
 
     # Organization API endpoints.
@@ -256,11 +258,13 @@
     path('users', include([
         path('', users.users),
         path('/<user_id>', users.users),
+        path('/<user_id>/about', users.users),
+        path('/<user_id>/consumption', users.users),
+        path('/<user_id>/spending', users.users),
         path('/<user_id>/logs', include([
             path('', settings.logs),
             path('/<log_id>', settings.logs),
         ])),
         path('/<user_id>/settings', users.users),
     ])),
-
 ]
diff --git a/cannlytics/__init__.py b/cannlytics/__init__.py
index 3a668bc5..7078fb26 100644
--- a/cannlytics/__init__.py
+++ b/cannlytics/__init__.py
@@ -12,7 +12,7 @@
 # __all__ = ['Cannlytics']
 
 __title__ = 'cannlytics'
-__version__ = '0.0.11'
+__version__ = '0.0.13'
 __author__ = 'Keegan Skeate <https://github.com/keeganskeate>'
 __license__ = 'MIT <https://opensource.org/licenses/MIT>'
 __copyright__ = 'Copyright (c) 2022 Cannlytics'
diff --git a/cannlytics/data/ccrs/__init__.py b/cannlytics/data/ccrs/__init__.py
index c1d89987..90d73798 100644
--- a/cannlytics/data/ccrs/__init__.py
+++ b/cannlytics/data/ccrs/__init__.py
@@ -1,36 +1,13 @@
 """
-Cannlytics Metrc Client Initialization | Cannlytics
+CCRS Client Initialization | Cannlytics
 Copyright (c) 2021-2022 Cannlytics
 
 Authors: Keegan Skeate <https://github.com/keeganskeate>
 Created: 11/6/2021
-Updated: 11/6/2021
+Updated: 9/22/2022
 """
-from typing import Any, Optional
-from .client import Metrc
+from .ccrs import CCRS
 
-
-def initialize_metrc(
-        vendor_api_key: str,
-        user_api_key: str,
-        primary_license: Optional[str] = '',
-        state: Optional[str] = 'ca',
-        client_class: Any = Metrc,
-) -> Metrc:
-    """This is a shortcut function which instantiates a Metrc
-    client using a user API key and the vendor API key.
-    Args:
-        vendor_api_key (str): The vendor's API key.
-        user_api_key (str): The user's API key.
-        primary_license (str): An optional primary license to use if no license is specified.
-        state (str): The state of the traceability system, `ca` by default.
-        client_class: By default :class:`cannlytics.metrc.client.Client` is used.
-    Returns:
-        (Metrc): Returns an instance of the Metrc client.
-    """
-    return client_class(
-        vendor_api_key,
-        user_api_key,
-        primary_license=primary_license,
-        state=state
-    )
+__all__ = [
+    CCRS,
+]
diff --git a/cannlytics/data/ccrs/ccrs.py b/cannlytics/data/ccrs/ccrs.py
index 0de9ca0c..b97380d5 100644
--- a/cannlytics/data/ccrs/ccrs.py
+++ b/cannlytics/data/ccrs/ccrs.py
@@ -4,7 +4,7 @@
 
 Authors: Keegan Skeate <https://github.com/keeganskeate>
 Created: 4/10/2022
-Updated: 4/21/2022
+Updated: 9/22/2022
 License: <https://github.com/cannlytics/cannlytics/blob/main/LICENSE>
 """
 # Standard imports.
@@ -17,15 +17,21 @@
 import requests
 
 # Internal imports.
-from cannlytics.firebase import initialize_firebase, update_documents
+from cannlytics.data.ccrs.constants import (
+    analytes,
+    analyses,
+    datasets,
+)
+from cannlytics.firebase import (
+    initialize_firebase,
+    update_documents,
+)
 from cannlytics.utils.utils import snake_case
-from .constants import analytes, analyses, datasets
 
 
 class CCRS(object):
     """An instance of this class handles CCRS data."""
 
-
     def __init__(self, data_dir='C:\\data', test=True):
         """Initialize a CCRS client."""
         self.data_dir = data_dir
diff --git a/cannlytics/data/ccrs/constants.py b/cannlytics/data/ccrs/constants.py
index 6a129513..bdab5d7d 100644
--- a/cannlytics/data/ccrs/constants.py
+++ b/cannlytics/data/ccrs/constants.py
@@ -4,9 +4,11 @@
 
 Authors: Keegan Skeate <https://github.com/keeganskeate>
 Created: 4/12/2022
-Updated: 4/20/2022
+Updated: 9/22/2022
 License: <https://github.com/cannlytics/cannlytics/blob/main/LICENSE>
 
+Description: Helpful CCRS constants.
+
 Data Sources:
 
     - WAC 314-55-102 Quality assurance and quality control
diff --git a/cannlytics/data/ccrs/models.py b/cannlytics/data/ccrs/models.py
index 86e6e1a6..3be78255 100644
--- a/cannlytics/data/ccrs/models.py
+++ b/cannlytics/data/ccrs/models.py
@@ -4,10 +4,10 @@
 
 Authors: Keegan Skeate <https://github.com/keeganskeate>
 Created: 4/10/2022
-Updated: 4/10/2022
+Updated: 9/23/2022
 License: <https://github.com/cannlytics/cannlytics/blob/main/LICENSE>
 
-This module contains common CCRS models.
+Description: CCRS models.
 """
 
 # Internal imports.
@@ -16,16 +16,6 @@
     clean_nested_dictionary,
     snake_case
 )
-# from cannlytics.utils.utils import (
-#     camel_to_snake,
-#     clean_dictionary,
-#     clean_nested_dictionary,
-#     get_timestamp,
-#     remove_dict_fields,
-#     remove_dict_nulls,
-#     snake_to_camel,
-#     update_dict,
-# )
 
 
 class Model(object):
@@ -99,33 +89,93 @@ def to_fb(self, ref='', col=''):
             update_document(ref, data)
 
 
+class Area(Model):
+    """A model for controlled cannabis production areas.
+    ```json
+        {
+            "area_id": "",
+            "created_by": "",
+            "created_date": "",
+            "external_identifier": "",
+            "is_deleted": "",
+            "is_quarantine": "",
+            "licensee_id": "",
+            "name": "",
+            "updated_by": "",
+            "updated_date": ""
+        }
+    ```
+    """
+    pass
 
-# TODO: Areas
 
 # TODO: Contacts
 
+
 # TODO: Integrators
 
-# TODO: Inventory
+
+class Inventory(Model):
+    """A model for cannabis inventory.
+    ```json
+        {
+            "area_id": "",
+            "created_by": "",
+            "created_date": "",
+            "external_identifier": "",
+            "initial_quantity": "",
+            "inventory_id": "",
+            "inventory_identifier": "",
+            "is_deleted": "",
+            "is_medical": "",
+            "licensee_id": "",
+            "product_id": "",
+            "quantity_on_hand": "",
+            "strain_id": "",
+            "total_cost": "",
+            "updated_by": "",
+            "updated_date": ""
+        }
+    ```
+    """
+    pass
+
 
 # TODO: Inventory Adjustments
 
+
 # TODO: Inventory Plant Transfers
 
+
 # TODO: Lab Results
 
+
 # TODO: Licensees
 
+
 # TODO: Plants
 
+
 # TODO: Plant destructions
 
+
 # TODO: Products
 
+
 # TODO: Sale Headers
 
+
 # TODO: Sale Details
 
+
 # TODO: Strains
 
+
 # TODO: Transfers (hard)
+
+
+
+# === Tests ===
+# if __name__ == '__main__':
+
+# {camel_to_snake(y): "" for y in sorted(x)}
diff --git a/cannlytics/data/ccrs/utils.py b/cannlytics/data/ccrs/utils.py
index 9ea835ea..85c4bde6 100644
--- a/cannlytics/data/ccrs/utils.py
+++ b/cannlytics/data/ccrs/utils.py
@@ -4,7 +4,7 @@
 
 Authors: Keegan Skeate <https://github.com/keeganskeate>
 Created: 4/11/2022
-Updated: 4/11/2022
+Updated: 9/22/2022
 License: <https://github.com/cannlytics/cannlytics/blob/main/LICENSE>
 """
 import os
@@ -75,7 +75,7 @@ def unzip_files(_dir, extension='.zip'):
     # Create a place for your data to live.
     data_dir = 'D:\\data\\washington'
 
-    # FIXME: Download the latest zipped datasets programattically.
+    # FIXME: Download the latest zipped datasets programmatically.
     # download_latest_ccrs_zip('../../.datasets/cannlytics/ccrs.zip')
     # dropbox_file_download(
     #     None,
diff --git a/cannlytics/data/coas/cannalysis.py b/cannlytics/data/coas/cannalysis.py
index 09f12b00..10cd3313 100644
--- a/cannlytics/data/coas/cannalysis.py
+++ b/cannlytics/data/coas/cannalysis.py
@@ -6,12 +6,12 @@
     Keegan Skeate <https://github.com/keeganskeate>
     Candace O'Sullivan-Sutherland <https://github.com/candy-o>
 Created: 8/2/2022
-Updated: 9/13/2022
+Updated: 9/22/2022
 License: <https://github.com/cannlytics/cannlytics/blob/main/LICENSE>
 
 Description:
 
-    Parse an Cannalysis CoA PDF.
+    Parse Cannalysis CoA PDFs.
 
 Data Points:
 
@@ -30,7 +30,8 @@
     ✓ product_name
     ✓ product_type
     ✓ results
-    ✓ sample_id
+    ✓ results_hash
+    ✓ sample_hash
     ✓ sample_size
     ✓ total_cannabinoids
     ✓ total_cbd
@@ -39,13 +40,13 @@
 
 FIXME:
 
-    - [ ] Occasional: Unknown string format "-02/18/202 11919" from OCR.
-    - [ ] Occasional: list index out of range
+    - [ ] Occasional unknown error: `list index out of range`
 
 """
 # Standard imports.
 from datetime import datetime
 import json
+import os
 import re
 from typing import Any, Optional
 import warnings
@@ -56,11 +57,24 @@
 from PIL import Image
 
 # Internal imports.
+from cannlytics import __version__
 from cannlytics.data.data import (
+    create_hash,
     create_sample_id,
     find_first_value,
 )
-from cannlytics.utils.constants import ANALYSES, ANALYTES, STANDARD_FIELDS
+from cannlytics.firebase import (
+    create_id,
+    create_short_url,
+    get_file_url,
+    initialize_firebase,
+    upload_file,
+)
+from cannlytics.utils.constants import (
+    ANALYSES,
+    ANALYTES,
+    STANDARD_FIELDS,
+)
 from cannlytics.utils.utils import (
     convert_to_numeric,
     snake_case,
@@ -104,6 +118,7 @@
         [0, 0.15, 0.35, 0.9],
         [0.35, 0.15, 1.0, 0.9],
     ],
+    'coa_image_area': [0.049, 0.179, 0.32, 0.3876],
     'coa_result_fields': [
         'name',
         'value',
@@ -122,6 +137,11 @@
         'Batch:',
         'ACCORDANCE',
         'criteria',
+        'identicalorsimilarproducts',
+        'bstractedinanymanner',
+        'Any violation of these conditions',
+        'All LQC samples required',
+        'acceptance criteria',
     ],
 }
 
@@ -130,7 +150,7 @@ def parse_cannalysis_coa(
         parser,
         doc: Any,
         cleanup: Optional[bool] = True,
-        resolution: Optional[int] = 180,
+        resolution: Optional[int] = 300,
         temp_path: Optional[str] = '/tmp',
         coa_pdf: Optional[str] = None,
         **kwargs,
@@ -143,7 +163,7 @@ def parse_cannalysis_coa(
             during OCR, `True` by default (optional).
         temp_path (str): A temporary directory to use for OCR.
         resolution (int): The resolution of rendered PDF images,
-            180 by default (optional).
+            300 by default (optional).
         coa_pdf (str): A filename to use for the `coa_pdf` field (optional).
     Returns:
         (dict): The sample data.
@@ -159,15 +179,14 @@ def parse_cannalysis_coa(
         obs['coa_pdf'] = coa_pdf or report.stream.name.replace('\\', '/').split('/')[-1]
 
     # Get the QR code from the last page.
-    # Note: After OCR, QR code decoding raises warning:
-    # DecompressionBombWarning: Image size (154554704 pixels) exceeds
-    # limit of 89478485 pixels, could be decompression bomb DOS attack.
-    # try:
-    #     obs['lab_results_url'] = parser.find_pdf_qr_code_url(report)
-    # except Image.DecompressionBombWarning:
-    #     obs['lab_results_url'] = ''
-
-    # TODO: Get the image data, `images` and `image_url`.
+    # Note: After OCR, QR code decoding raises DecompressionBombWarning.
+    try:
+        obs['lab_results_url'] = parser.find_pdf_qr_code_url(
+            report,
+            resolution=resolution,
+        )
+    except Image.DecompressionBombWarning:
+        obs['lab_results_url'] = ''
 
     # Get the lab specifics.
     coa_parameters = CANNALYSIS_COA
@@ -217,6 +236,37 @@ def parse_cannalysis_coa(
     # Optional: Is there any way to identify the `producer` and
     # `distributor` with their license numbers? Query Cannlytics API?
 
+    # Get the image data, if the PDF has gone through OCR, then cut out
+    # the image first. Then save the image to the cloud, get the file
+    # URL, create a short URL to use as the `image_url`.
+    # TODO: Resize the image if it is too large?
+    # TODO: Turn this into a function?
+    # try:
+    #     image_data = parser.get_pdf_image_data(
+    #         front_page,
+    #         image_index=1,
+    #         resolution=resolution,
+    #     )
+    # except IndexError:
+    #     area = tuple(coa_parameters['coa_image_area'])
+    #     image_data = parser.get_pdf_image_data(
+    #         front_page,
+    #         bbox=area,
+    #         resolution=resolution,
+    #     )
+    # image_id = obs.get('metrc_lab_id', create_id())
+    # image_ref = f'public/data/lab_results/{image_id}/image.jpg'
+    # api_key = os.environ['FIREBASE_API_KEY']
+    # bucket = os.environ['FIREBASE_STORAGE_BUCKET']
+    # project_name = os.environ['FIREBASE_PROJECT_ID']
+    # # FIXME:
+    # initialize_firebase()
+    # upload_file(image_ref, data_url=image_data, bucket_name=bucket)
+    # file_url = get_file_url(image_ref, bucket_name=bucket)
+    # # short_url = create_short_url(api_key, file_url, project_name)
+    # short_url = file_url
+    # obs['image_url'] = short_url
+
     # Check if OCR is required then re-run the routine.
     line = lines[0]
     if re.match(r'^\(cid:\d+\)', line):
@@ -292,7 +342,7 @@ def parse_cannalysis_coa(
         obs['product_name'] = text[0].replace('G=>', '').strip()
 
     # Get all page text, from the 2nd page on.
-    results, date_tested = [], []
+    results, dates_tested = [], []
     for page_number, page in enumerate(report.pages[1:]):
         for area in coa_page_area:
 
@@ -358,12 +408,12 @@ def parse_cannalysis_coa(
                             date_time = pd.to_datetime(date)
                         else:
                             date_time = pd.to_datetime(' '.join([date, at]))
-                        date_tested.append(date_time)
+                        dates_tested.append(date_time)
                     except:
-                        pass
+                        continue
 
                 # Skip informational rows.
-                elif any(s in line for s in skip_fields):
+                elif any(s in line or s in line_text for s in skip_fields):
                     continue
 
                 # End at the end of the report.
@@ -410,6 +460,11 @@ def parse_cannalysis_coa(
                             result[key] = value
                     except IndexError:
                         continue
+                
+                    # Hot-fix: Skip (non-)analytes that begin with a digit.
+                    # Note: It would be best to improve this logic.
+                    if result['key'][0].isdigit():
+                        continue
 
                     # Record the result.
                     results.append(result)
@@ -418,7 +473,7 @@ def parse_cannalysis_coa(
     report.close()
 
     # Get the latest tested at date.
-    obs['date_tested'] = max(date_tested)
+    obs['date_tested'] = max(dates_tested)
 
     # Turn dates to ISO format.
     date_columns = [x for x in obs.keys() if x.startswith('date')]
@@ -429,15 +484,20 @@ def parse_cannalysis_coa(
             pass
 
     # Finish data collection with a freshly minted sample ID.
-    obs['analyses'] = list(set(analyses))
-    obs['results'] = results
+    # TODO: Fix or make `sample_id` obsolete.
+    obs = {**CANNALYSIS, **obs}
+    obs['analyses'] = json.dumps(list(set(analyses)))
+    obs['coa_algorithm_version'] = __version__
+    obs['coa_parsed_at'] = datetime.now().isoformat()
+    obs['results'] = json.dumps(results)
+    obs['results_hash'] = create_hash(results)
     obs['sample_id'] = create_sample_id(
         private_key=json.dumps(results),
         public_key=obs['product_name'],
-        salt=obs['date_tested'], # Note: The standard is `producer`.
+        salt=obs.get('producer', obs.get('date_tested', 'cannlytics.eth')),
     )
-    obs['coa_parsed_at'] = datetime.now().isoformat()
-    return {**CANNALYSIS, **obs}
+    obs['sample_hash'] = create_hash(obs)
+    return obs
 
 
 # === Test ===
@@ -445,25 +505,37 @@ def parse_cannalysis_coa(
 
     from cannlytics.data.coas import CoADoc
 
-    # [✓] TEST: Parse a Cannalysis CoA PDF.
-    # parser = CoADoc()
-    # doc = '../../../tests/assets/coas/MothersMilk.pdf'
-    # lab = parser.identify_lims(doc, lims={'Cannalysis': CANNALYSIS})
-    # assert lab == 'Cannalysis'
-    # data = parse_cannalysis_coa(parser, doc)
-    # assert data is not None
+    # [✓] TEST: Parse a Cannalysis CoA PDF (for a flower product).
+    parser = CoADoc()
+    doc = '../../../tests/assets/coas/cannalysis/Mothers-Milk-Flower.pdf'
+    lab = parser.identify_lims(doc, lims={'Cannalysis': CANNALYSIS})
+    assert lab == 'Cannalysis'
+    data = parse_cannalysis_coa(parser, doc)
+    assert data is not None
+    print('Parsed:', doc)
+
+    # [✓] TEST: Parse a Cannalysis CoA PDF (for a concentrate product).
+    parser = CoADoc()
+    doc = '../../../tests/assets/coas/cannalysis/Citrus-Slurm-Diamonds.pdf'
+    lab = parser.identify_lims(doc, lims={'Cannalysis': CANNALYSIS})
+    assert lab == 'Cannalysis'
+    data = parse_cannalysis_coa(parser, doc)
+    assert data is not None
+    print('Parsed:', doc)
 
     # [✓] TEST: Parse a Cannalysis CoA PDF after OCR is applied.
-    # parser = CoADoc()
-    # doc = '../../../.datasets/tests/test.pdf'
-    # lab = parser.identify_lims(doc, lims={'Cannalysis': CANNALYSIS})
-    # assert lab == 'Cannalysis'
-    # data = parse_cannalysis_coa(parser, doc)
-    # assert data is not None
+    parser = CoADoc()
+    doc = '../../../.datasets/tests/test.pdf'
+    lab = parser.identify_lims(doc, lims={'Cannalysis': CANNALYSIS})
+    assert lab == 'Cannalysis'
+    data = parse_cannalysis_coa(parser, doc)
+    assert data is not None
+    print('Parsed:', doc)
 
     # [✓] TEST: Parse a Cannalysis CoA PDF, applying OCR.
-    # parser = CoADoc()
-    # doc = '../../../.datasets/tests/mist.pdf'
-    # temp_path = '../../../.datasets/tests/tmp'
-    # data = parser.parse_pdf(doc, temp_path=temp_path)
-    # assert data is not None
+    parser = CoADoc()
+    doc = '../../../.datasets/tests/mist.pdf'
+    temp_path = '../../../.datasets/tests/tmp'
+    data = parser.parse_pdf(doc, temp_path=temp_path)
+    assert data is not None
+    print('Parsed:', doc)
diff --git a/cannlytics/data/coas/cannasafe.py b/cannlytics/data/coas/cannasafe.py
new file mode 100644
index 00000000..36e3fde6
--- /dev/null
+++ b/cannlytics/data/coas/cannasafe.py
@@ -0,0 +1,133 @@
+"""
+Parse CannaSafe CoAs
+Copyright (c) 2022 Cannlytics
+
+Authors:
+    Keegan Skeate <https://github.com/keeganskeate>
+Created: 9/17/2022
+Updated: 9/17/2022
+License: <https://github.com/cannlytics/cannlytics/blob/main/LICENSE>
+
+Description:
+
+    Parse a CannaSafe CoA PDF.
+
+Data Points:
+
+"""
+# Standard imports.
+from ast import literal_eval
+from datetime import datetime
+import json
+import re
+from typing import Any, Optional
+
+# External imports.
+import pandas as pd
+import pdfplumber
+
+# Internal imports.
+from cannlytics.data.data import create_sample_id
+from cannlytics.utils.constants import STANDARD_UNITS
+from cannlytics.utils.utils import (
+    convert_to_numeric,
+    snake_case,
+    split_list,
+    strip_whitespace,
+)
+
+# It is assumed that the lab has the following details.
+CANNASAFE = {
+    'coa_algorithm': 'cannasafe.py',
+    'coa_algorithm_entry_point': 'parse_cannasafe_coa',
+    'lims': 'CannaSafe',
+    'lab': 'CannaSafe',
+    'lab_image_url': '',
+    'lab_license_number': ' ', # <- Make dynamic.
+    'lab_address': '', # <- Make dynamic.
+    'lab_street': '', # <- Make dynamic.
+    'lab_city': '', # <- Make dynamic.
+    'lab_county': '', # <- Make dynamic.
+    'lab_state': 'CA', # <- Make dynamic.
+    'lab_zipcode': '', # <- Make dynamic.
+    'lab_phone': '', # <- Make dynamic.
+    'lab_email': '', # <- Make dynamic.
+    'lab_website': '',
+    'lab_latitude': 0, # <- Make dynamic.
+    'lab_longitude': 0, # <- Make dynamic.
+}
+
+
+def parse_cannasafe_coa(
+        parser,
+        doc: Any,
+        temp_path: Optional[str] = None,
+        **kwargs,
+    ) -> dict:
+    """Parse a CannaSafe CoA PDF.
+    Args:
+        doc (str or PDF): A PDF file path or pdfplumber PDF.
+    Returns:
+        (dict): The sample data.
+    """
+
+    # Get the lab's analyses.
+    # lab_analyses = GREEN_LEAF_LAB_ANALYSES
+    # coa_parameters = GREEN_LEAF_LAB_COA
+    # standard_analyses = list(lab_analyses.keys())
+    # analysis_names = [x['name'] for x in lab_analyses.values()]
+
+    # TODO: If the `doc` is a URL, then download the PDF to `temp_path`.
+    if temp_path is None:
+        # FIXME: Get the user's temp_path
+        temp_path = '/tmp'
+
+    # Read the PDF.
+    obs = {}
+    if isinstance(doc, str):
+        report = pdfplumber.open(doc)
+        obs['coa_pdf'] = doc.replace('\\', '/').split('/')[-1]
+    else:
+        report = doc
+        obs['coa_pdf'] = report.stream.name.replace('\\', '/').split('/')[-1]
+    front_page = report.pages[0]
+
+    # Add optional ability to collect the image.
+    # - Get the image data.
+    # - Save the image to Firebase Storage
+    # - Create a dynamic link for the image.
+
+    # TODO: Get the lab details.
+
+    # TODO: Get the sample details.
+
+    # TODO: Get the client details.
+
+    # TODO: Get the analyses.
+
+    # TODO: Get the results.
+
+
+# TEST:
+url = 'https://tracker.csalabs.com/public/samples/lllora'
+doc = '../../../tests/assets/coas/cannasafe/Grape-CBD-Gummies.pdf'
+
+# Read the PDF.
+obs = {}
+report = pdfplumber.open(doc)
+front_page = report.pages[0]
+
+
+# TODO: Get the image.
+# - Save the image locally from the image data.
+# - Upload the image to Firebase Storage.
+# - Create a dynamic link for the image (`sample_image_url`).
+# img = front_page.images[3]
+
+# Get the front page text.
+text = front_page.extract_text()
+# split = re.split('\d+ of \d+', text)
+# header = split[0]
+# body = split[-1].split('ND = Not Detected')[0]
+# lines = body.split('\n')
+# lines = [x for x in lines if x.strip()]
diff --git a/cannlytics/data/coas/coas.py b/cannlytics/data/coas/coas.py
index ab20dee2..20e1cab4 100644
--- a/cannlytics/data/coas/coas.py
+++ b/cannlytics/data/coas/coas.py
@@ -6,7 +6,7 @@
     Keegan Skeate <https://github.com/keeganskeate>
     Candace O'Sullivan-Sutherland <https://github.com/candy-o>
 Created: 7/15/2022
-Updated: 9/14/2022
+Updated: 10/10/2022
 License: <https://github.com/cannlytics/cannlytics/blob/main/LICENSE>
 
 Description:
@@ -20,9 +20,9 @@
 
     In order to parse CoAs well, CoADoc takes into consideration:
 
-        - The lab or LIMS that generated the CoA;
-        - PDF properties, such as the fonts used and the page dimensions;
-        - All detected words, lines, columns, white-space, etc.
+        * The lab or LIMS that generated the CoA;
+        * PDF properties, such as the fonts used and the page dimensions;
+        * All detected words, lines, columns, white-space, etc.
     
     The roadmap for CoADoc is to continue adding lab and LIMS CoA
     parsing algorithms until a general CoA parsing algorithm may be able
@@ -39,6 +39,7 @@
     ✓ MCR Labs
     ✓ SC Labs
     ✓ Sonoma Lab Works
+    ✓ Steep Hill Massachusetts
     ✓ TagLeaf LIMS
     ✓ Veda Scientific
 
@@ -78,6 +79,7 @@
 import pandas as pd
 import requests
 import pdfplumber
+from PIL import Image
 from PyPDF2 import PdfMerger
 try:
     from pyzbar.pyzbar import decode
@@ -95,6 +97,7 @@
 
 # Internal imports.
 from cannlytics.data.data import create_hash, write_to_worksheet
+from cannlytics.data.web import download_google_drive_file
 from cannlytics.utils import (
     convert_to_numeric,
     dump_column,
@@ -121,6 +124,7 @@
 from cannlytics.data.coas.sclabs import SC_LABS
 from cannlytics.data.coas.sonoma import SONOMA
 from cannlytics.data.coas.tagleaf import TAGLEAF
+from cannlytics.data.coas.steephill import STEEPHILL
 from cannlytics.data.coas.veda import VEDA_SCIENTIFIC
 
 # Labs and LIMS that CoADoc can parse.
@@ -133,11 +137,13 @@
     'SC Labs': SC_LABS,
     'Sonoma Lab Works': SONOMA,
     'TagLeaf LIMS': TAGLEAF,
+    'Steep Hill': STEEPHILL,
     'Veda Scientific': VEDA_SCIENTIFIC,
 }
 
 # Default preferred order for DataFrame columns.
-DEFAULT_COLUMN_ORDER = ['sample_id', 'product_name', 'producer',
+# TODO: Add `sample_hash` and `results_hash` to the beginning.
+DEFAULT_COLUMN_ORDER = ['product_name', 'producer',
     'product_type', 'date_tested']
 
 # Default nuisance columns to remove during standardization.
@@ -149,7 +155,7 @@
 DEFAULT_NUMERIC_COLUMNS = ['value', 'mg_g', 'lod', 'loq', 'limit', 'margin_of_error']
 
 # Encountered Metrc prefixes used to identify Metrc IDs.
-METRC_PREFIXES = ['1A40603']
+METRC_PREFIXES = ['1A40603', '1A40A01']
 
 # A custom `ValueError` message to raise when no known LIMS is identified.
 UNIDENTIFIED_LIMS = 'CoA not recognized as a CoA from a supported LIMS: '
@@ -377,6 +383,7 @@ def find_pdf_qr_code_url(
             pdf: Any,
             image_index: Optional[int] = None,
             page_index: Optional[int] = 0,
+            resolution: Optional[int] = 300,
         ) -> str:
         """Find the QR code given a CoA PDF or page.
         If no `image_index` is provided, then all images are tried to be
@@ -386,6 +393,8 @@ def find_pdf_qr_code_url(
             pdf (PDF or Page): A pdfplumber PDF or Page.
             image_index (int): A known image index for the QR code.
             page_index (int): The page to search, 0 by default (optional).
+            resolution (int): The resolution to render the QR code,
+                `300` by default (optional).
         Returns:
             (str): The QR code URL.
         """
@@ -399,13 +408,13 @@ def find_pdf_qr_code_url(
             page = pdf
         if image_index:
             img = page.images[image_index]
-            decoded_image = self.decode_pdf_qr_code(page, img)
+            decoded_image = self.decode_pdf_qr_code(page, img, resolution)
             image_data = decoded_image[0].data.decode('utf-8')
         else:
             image_range = sandwich_list(page.images)
             for img in image_range:
                 try:
-                    decoded_image = self.decode_pdf_qr_code(page, img)
+                    decoded_image = self.decode_pdf_qr_code(page, img, resolution)
                     image_data = decoded_image[0].data.decode('utf-8')
                     if image_data:
                         break
@@ -495,6 +504,7 @@ def get_pdf_creation_date(self, pdf: Any) -> str:
     def get_pdf_image_data(
             self,
             page: Any,
+            bbox: Optional[tuple] = None,
             image_index: Optional[int] = 0, 
             resolution: Optional[int] = 300,
         ) -> str:
@@ -506,9 +516,10 @@ def get_pdf_image_data(
         Returns:
             (str): The image data.
         """
-        y = page.height
-        img = page.images[image_index]
-        bbox = (img['x0'], y - img['y1'], img['x1'], y - img['y0'])
+        if bbox is None:
+            y = page.height
+            img = page.images[image_index]
+            bbox = (img['x0'], y - img['y1'], img['x1'], y - img['y0'])
         crop = page.crop(bbox)
         obj = crop.to_image(resolution=resolution)
         buffered = BytesIO()
@@ -520,6 +531,7 @@ def identify_lims(
             self,
             doc: Any,
             lims: Optional[Any] = None,
+            temp_path: Optional[str] = '/tmp',
         ) -> str:
         """Identify if a CoA was created by a common LIMS.
         Search all of the text of the LIMS name or URL.
@@ -529,25 +541,48 @@ def identify_lims(
             doc (str, PDF or Page): A URL or a pdfplumber PDF or Page.
             lims (str or dict): The name of a specific LIMS or a
                 dictionary of known LIMS.
+            temp_path (str): A temporary directory to store any online PDFs
+                if needed for identification, `/tmp` by default (optional).
         Returns:
             (str): Returns LIMS name if found, otherwise returns `None`.
         """
         # Search all of the text of the LIMS name or URL.
         known = None
         if isinstance(doc, str):
+
+            # Handle shortened URLs.
+            text = doc
+            if doc.startswith('https://tinyurl'):
+                response = requests.get(doc, headers=DEFAULT_HEADERS)
+                text = response.url
+        
+            # Handle Google Drive PDFs.
+            if text.startswith('https://drive.google'):
+                temp_pdf = os.path.join(temp_path, 'coa.pdf')
+                if not os.path.exists(temp_path): os.makedirs(temp_path)
+                download_google_drive_file(text, temp_pdf)
+                text = temp_pdf
+
+            # Handle PDFs.
             try:
-                pdf_file = pdfplumber.open(doc)
+                pdf_file = pdfplumber.open(text)
                 text = pdf_file.pages[0].extract_text()
             except (FileNotFoundError, OSError):
-                text = doc
+                pass
+        
+        # Handle PDFs passed directly.
         else:
             if isinstance(doc, pdfplumber.pdf.PDF):
                 page = doc.pages[0]
             else:
                 page = doc
             text = page.extract_text()
+
+        # Handle custom LIMS.
         if lims is None:
             lims = LIMS
+        
+        # Iterate over all known LIMS looking for URLs and LIMS names.
         if isinstance(lims, str):
             try:
                 if self.lims[lims]['url'] in text:
@@ -566,15 +601,23 @@ def identify_lims(
                     known = key
                     break
 
-        # Try to get a QR code to identify the LIMS.
+        # If the LIMS is still unknown, then try to identify the LIMS
+        # with any a QR code, if the COA is a PDF.
         if not known:
-            qr_code_url = self.find_pdf_qr_code_url(doc)
+            try:
+                qr_code_url = self.find_pdf_qr_code_url(doc)
+            except:
+                qr_code_url = None
             if qr_code_url:
                 for key, values in lims.items():
                     url = values.get('url')
                     if url and url in qr_code_url:
                         known = key
                         break
+        
+        # Optional: Can we detect LIMS from a photo for a COA or a label?
+
+        # Return any known LIMS.
         return known
 
     def parse(
@@ -1151,7 +1194,8 @@ def standardize(
             # Identify standard fields, adding analytes for `wide` data.
             fields = standard_fields
             if how == 'wide':
-                fields = {**standard_fields, **standard_analytes}
+                current_fields = {x:x for x in data.keys()}
+                fields = {**current_fields, **standard_analytes}
 
             # Standardize fields.
             std = {}
@@ -1261,8 +1305,8 @@ def standardize(
                     criterion = details_data.columns.str.endswith(c)
                     details_data = details_data.loc[:, ~criterion]
 
-                # FIXME: Apply codings to results.
-                # Note: This is super slow (2-3 mins for 2.5k observations)!
+                # Apply codings to results.
+                # FIXME: This is super slow (2-3 mins for 2.5k observations)!
                 details_data['results'].replace(codings, inplace=True)
 
                 # Convert totals to numeric.
@@ -1389,17 +1433,20 @@ def standardize(
                         # Hot-fix to place unidentified analyses at the end.
                         pairs.append((a, None, 122))
 
-                # Sort the pairs of analytes/analyses.
+                # Sort the pairs of analyses / analytes.
                 pairs.sort(key=operator.itemgetter(2))
 
                 # Create a wide table of values data.
                 values = []
                 for _, item in details_data.iterrows():
 
-                    # Specify the default columns.
-                    std = {}
-                    for c in column_order:
-                        std[c] = item[c]
+                    # Use all of the details in the values.
+                    std = item.to_dict()
+
+                    # Old: Only use the default columns. Make optional?
+                    # std = {}
+                    # for c in column_order:
+                    #     std[c] = item[c]
 
                     # Get the sample results.
                     sample_results = item['results']
@@ -1438,13 +1485,12 @@ def standardize(
                     criterion = values_data.columns.str.endswith(c)
                     values_data = values_data.loc[:, ~criterion]
 
-                # Drop totals.
-                # TODO: Add totals back from the Details worksheet?
-                criterion = values_data.columns.str.startswith('total_')
-                values_data = values_data.loc[:, ~criterion]
+                # Old: Drop totals. Make optional?
+                # criterion = values_data.columns.str.startswith('total_')
+                # values_data = values_data.loc[:, ~criterion]
 
                 # Move certain columns to the beginning.
-                cols = column_order + [x[0] for x in pairs]
+                cols = list(details_data.columns) + [x[0] for x in pairs]
                 values_data = reorder_columns(values_data, cols)
                 return values_data
 
@@ -1466,6 +1512,19 @@ def standardize(
         # Raise an error if an incorrect type is passed.
         else:
             raise ValueError
+    
+    def scan(self, filename: Any) -> str:
+        """Scan an image for a QR code or barcode and return any data.
+        Args:
+            filename (str): A path to an image with a barcode or qr code.
+        Returns:
+            (str): Returns the data from the decoded QR code.
+        """
+        if isinstance(filename, str):
+            code = decode(Image.open(filename))
+        else:
+            code = decode(filename)
+        return code[0].data.decode('utf-8')
 
     def quit(self):
         """Close any driver, end any session, and reset the parameters."""
@@ -1596,6 +1655,12 @@ def quit(self):
     # temp_path = '../../../tests/assets/coas/tmp'
     # data = parser.parse_pdf(doc, temp_path=temp_path)
 
+    # [ ] TEST: Scan a QR code in an image with the `scan` method.
+    parser = CoADoc()
+    file_path = '../../../tests/assets/qr-code/steep-hill-qr-code.jpg'
+    data = parser.scan(file_path)
+    assert data.startswith('https')
+
     # [✓] TEST: Close the parser.
     parser.quit()
     print('All CoADoc tests finished.')
diff --git a/cannlytics/data/coas/confidentcannabis.py b/cannlytics/data/coas/confidentcannabis.py
index 722a2a86..2525a3c7 100644
--- a/cannlytics/data/coas/confidentcannabis.py
+++ b/cannlytics/data/coas/confidentcannabis.py
@@ -11,7 +11,12 @@
 
 Description:
 
-    Parse a Confident Cannabis CoA PDF or URL.
+    Parse a Confident Cannabis CoA PDF or URL. Labs include:
+
+        - CaliGreen Laboratory
+        - California Ag Labs
+        - CB Labs Novato
+        - Harrens Lab Inc
 
 Data Points:
 
diff --git a/cannlytics/data/coas/kaycha.py b/cannlytics/data/coas/kaycha.py
new file mode 100644
index 00000000..b00f8b2e
--- /dev/null
+++ b/cannlytics/data/coas/kaycha.py
@@ -0,0 +1,114 @@
+"""
+Parse Kaycha Labs CoAs
+Copyright (c) 2022 Cannlytics
+
+Authors:
+    Keegan Skeate <https://github.com/keeganskeate>
+Created: 9/17/2022
+Updated: 9/17/2022
+License: <https://github.com/cannlytics/cannlytics/blob/main/LICENSE>
+
+Description:
+
+    Parse a Kaycha Labs CoA PDF.
+
+Data Points:
+
+"""
+# Standard imports.
+from ast import literal_eval
+from datetime import datetime
+import json
+import re
+from typing import Any, Optional
+
+# External imports.
+import pandas as pd
+import pdfplumber
+
+# Internal imports.
+from cannlytics.data.data import create_sample_id
+from cannlytics.utils.constants import STANDARD_UNITS
+from cannlytics.utils.utils import (
+    convert_to_numeric,
+    snake_case,
+    split_list,
+    strip_whitespace,
+)
+
+# It is assumed that the lab has the following details.
+KCA_LABS = {
+    'coa_algorithm': 'kcalabs.py',
+    'coa_algorithm_entry_point': 'parse_kcalabs_coa',
+    'lims': 'KCA Labs',
+    'lab': 'KCA Labs',
+    'lab_image_url': '',
+    'lab_license_number': ' ', # <- Make dynamic.
+    'lab_address': '', # <- Make dynamic.
+    'lab_street': '', # <- Make dynamic.
+    'lab_city': '', # <- Make dynamic.
+    'lab_county': '', # <- Make dynamic.
+    'lab_state': 'CA', # <- Make dynamic.
+    'lab_zipcode': '', # <- Make dynamic.
+    'lab_phone': '', # <- Make dynamic.
+    'lab_email': '', # <- Make dynamic.
+    'lab_website': '',
+    'lab_latitude': 0, # <- Make dynamic.
+    'lab_longitude': 0, # <- Make dynamic.
+}
+
+
+def parse_kaycha_coa(
+        parser,
+        doc: Any,
+        temp_path: Optional[str] = None,
+        **kwargs,
+    ) -> dict:
+    """Parse a Kaycha Labs CoA PDF.
+    Args:
+        doc (str or PDF): A PDF file path or pdfplumber PDF.
+    Returns:
+        (dict): The sample data.
+    """
+
+    # Get the lab's analyses.
+    # lab_analyses = GREEN_LEAF_LAB_ANALYSES
+    # coa_parameters = GREEN_LEAF_LAB_COA
+    # standard_analyses = list(lab_analyses.keys())
+    # analysis_names = [x['name'] for x in lab_analyses.values()]
+
+    # TODO: If the `doc` is a URL, then download the PDF to `temp_path`.
+    if temp_path is None:
+        # FIXME: Get the user's temp_path
+        temp_path = '/tmp'
+
+    # Read the PDF.
+    obs = {}
+    if isinstance(doc, str):
+        report = pdfplumber.open(doc)
+        obs['coa_pdf'] = doc.replace('\\', '/').split('/')[-1]
+    else:
+        report = doc
+        obs['coa_pdf'] = report.stream.name.replace('\\', '/').split('/')[-1]
+    front_page = report.pages[0]
+
+    # Add optional ability to collect the image.
+    # - Get the image data.
+    # - Save the image to Firebase Storage
+    # - Create a dynamic link for the image.
+
+    # TODO: Get the lab details.
+
+    # TODO: Get the sample details.
+
+    # TODO: Get the client details.
+
+    # TODO: Get the analyses.
+
+    # TODO: Get the results.
+
+
+# DEV:
+url = 'https://tn.yourcoa.com/api/coa-download?sample=KN20119003-002&wl_id=291'
+doc = '../../../tests/assets/coas/kca-labs/Elyxer-Flower.pdf'
+report = pdfplumber.open(doc)
diff --git a/cannlytics/data/coas/kcalabs.py b/cannlytics/data/coas/kcalabs.py
new file mode 100644
index 00000000..92fa4c1e
--- /dev/null
+++ b/cannlytics/data/coas/kcalabs.py
@@ -0,0 +1,404 @@
+"""
+Parse KCA Labs CoAs
+Copyright (c) 2022 Cannlytics
+
+Authors:
+    Keegan Skeate <https://github.com/keeganskeate>
+Created: 9/17/2022
+Updated: 9/19/2022
+License: <https://github.com/cannlytics/cannlytics/blob/main/LICENSE>
+
+Description:
+
+    Parse KCA Labs CoA PDFs.
+
+Data Points:
+
+"""
+# Standard imports.
+from datetime import datetime
+import json
+import re
+from typing import Any, Optional
+
+# External imports.
+import pandas as pd
+import pdfplumber
+import requests
+
+# Internal imports.
+from cannlytics import __version__
+from cannlytics.data.data import create_sample_id
+from cannlytics.utils.constants import DEFAULT_HEADERS
+from cannlytics.utils.utils import (
+    convert_to_numeric,
+    snake_case,
+    split_list,
+    strip_whitespace,
+)
+
+# It is assumed that the lab has the following details.
+KCA_LABS = {
+    'coa_algorithm': 'kcalabs.py',
+    'coa_algorithm_entry_point': 'parse_kcalabs_coa',
+    'url': 'https://kcalabs.com',
+    'lims': 'KCA Labs',
+    'lab': 'KCA Labs',
+    'lab_image_url': 'https://kcalabs.com/wp-content/uploads/2021/07/KCALABS_FI-2.png',
+    'lab_license_number': 'P_0058',
+    'lab_address': '232 North Plaza Drive, Nicholasville, KY 40356',
+    'lab_street': '232 North Plaza Drive',
+    'lab_city': 'Nicholasville',
+    'lab_county': 'Jessamine',
+    'lab_state': 'KY',
+    'lab_zipcode': '40356',
+    'lab_phone': '+1-833-KCA-LABS',
+    'lab_email': 'trustedresults@kcalabs.com',
+    'lab_website': 'https://kcalabs.com',
+    'lab_latitude': 37.904740,
+    'lab_longitude': -84.566980,
+}
+KCA_LABS_ANALYSES = {
+    'cannabinoids': {
+        'columns': ['name', 'lod', 'loq', 'value', 'mg_g'],
+    },
+    # 'pesticides': {
+    #     'name': 'Pesticide Analysis by GCMS/LCMS',
+    #     'columns': ['name', 'value', 'limit', 'lod', 'loq', 'units'],
+    #     'double_column': True,
+    # },
+    # 'water_activity': {
+    #     'name': 'Water Activity by Aqua Lab',
+    #     'columns': ['name', 'value', 'units', 'lod', 'loq'],
+    # },
+    # 'moisture_content': {
+    #     'name': 'Moisture by Moisture Balance',
+    #     'columns': ['name', 'value', 'units'],
+    # },
+    # 'terpenes': {
+    #     'name': 'Terpene Analysis by GCMS',
+    #     'columns': ['name', 'value', 'mg_g'],
+    #     'double_column': True,
+    # },
+    # 'heavy_metals': {
+    #     'name': 'Metals Analysis by ICPMS',
+    #     'columns': ['name', 'value', 'limit', 'lod', 'loq', 'units'],
+    # },
+    # 'mycotoxins': {
+    #     'name': 'Mycotoxins by LCMSMS',
+    #     'columns': ['name', 'value', 'limit', 'loq', 'loq', 'units'],
+    # },
+    # 'microbials': {
+    #     'name': 'Microbials by PCR',
+    #     'columns': ['name', 'status', 'limit', 'lod', 'loq', 'units', 'value'],
+    # },
+    # 'foreign_matter': {
+    #     'name': 'Filth and Foreign Material Inspection by Magnification',
+    #     'columns': ['name', 'status', 'limit', 'lod', 'loq', 'units'],
+    # },
+}
+KCA_LABS_COA = {
+   'coa_skip_values': [
+        'Summary',
+        'Test Date Tested Status',
+        'Normalization',
+        'LOD LOQ Result Result',
+        'Analyte',
+        '(%) (%) (%) (mg/unit)',
+    ],
+    'coa_sample_detail_fields': {
+        'Sample ID': 'lab_id',
+        'Batch': 'batch_number',
+        'Received': 'received_at',
+        'Type': 'product_type',
+        'Completed': 'completed_at',
+        'Matrix': 'product_subtype',
+        'Unit Mass (g)': 'sample_weight',
+    },
+}
+
+# Constants
+coa_skip_values = [
+    'Summary',
+    'Test Date Tested Status',
+    'Normalization',
+    'LOD LOQ Result Result',
+    'Analyte',
+    '(%) (%) (%) (mg/unit)',
+]
+coa_sample_detail_fields = {
+    'Sample ID': 'lab_id',
+    'Batch': 'batch_number',
+    'Received': 'date_received',
+    'Type': 'product_type',
+    'Completed': 'date_completed',
+    'Matrix': 'product_subtype',
+    'Unit Mass (g)': 'sample_weight',
+}
+coa_numeric_columns = [
+    'sample_weight',
+]
+
+
+def parse_kcalabs_coa(
+        parser,
+        doc: Any,
+        temp_path: Optional[str] = None,
+        **kwargs,
+    ) -> dict:
+    """Parse a KCA Labs CoA PDF.
+    Args:
+        doc (str or PDF): A PDF file path or pdfplumber PDF.
+    Returns:
+        (dict): The sample data.
+    """
+    obs = {}
+
+    # TODO: Get the lab's parameters.
+    # lab_analyses = GREEN_LEAF_LAB_ANALYSES
+    # coa_parameters = GREEN_LEAF_LAB_COA
+    # standard_analyses = list(lab_analyses.keys())
+    # analysis_names = [x['name'] for x in lab_analyses.values()]
+
+    # If the `doc` is a URL, then download the PDF to `temp_path`.
+    # Otherwise open a PDF file or use a PDF instance.
+    if isinstance(doc, str):
+        if doc.startswith('https'):
+            if temp_path is None:
+                # FIXME: Get the user's temp_path
+                temp_path = '/tmp'
+            filename = doc.split('/')[-1]
+            filepath = f'{temp_path}/{filename}.pdf'
+            # FIXME: Will need to get `coa_url`.
+            with open(filepath, 'wb') as f:
+                f.write(requests.get(url, headers=DEFAULT_HEADERS).content)
+            report = pdfplumber.open(filepath)
+            obs['coa_pdf'] = f'{filename}.pdf'
+        else:
+            report = pdfplumber.open(doc)
+            obs['coa_pdf'] = doc.replace('\\', '/').split('/')[-1]
+    else:
+        report = doc
+        obs['coa_pdf'] = report.stream.name.replace('\\', '/').split('/')[-1]
+    front_page = report.pages[0]
+
+    raise NotImplementedError
+
+
+# === Tests ===
+
+from cannlytics.data.coas import CoADoc
+
+
+# Initialize a COA parser.
+parser = CoADoc()
+
+# Parse a COA with cannabinoid-only testing.
+url = 'https://kcalabs.qbench.net/pr/ee1b30d13037a54'
+doc = '../../../tests/assets/coas/kca-labs/Tangie.pdf'
+
+# Parse a COA with full QC testing.
+# url = 'https://kcalabs.qbench.net/pr/c1f17c307ca6dc2'
+# doc = '../../../tests/assets/coas/kca-labs/Elyxer-Gummies.pdf'
+
+# Read the PDF.
+report = pdfplumber.open(doc)
+front_page = report.pages[0]
+
+# TODO: Get the image.
+# - Save the image locally from the image data.
+# - Upload the image to Firebase Storage.
+# - Create a dynamic link for the image (`sample_image_url`).
+img = front_page.images[3]
+
+# Get the front page text.
+text = front_page.extract_text()
+split = re.split('\d+ of \d+', text)
+header = split[0]
+body = split[-1].split('ND = Not Detected')[0]
+lines = body.split('\n')
+lines = [x for x in lines if x.strip()]
+
+# Optional: Get the lab details from the header.
+# block = header.split('\n')
+# street = block[1].replace('https://kcalabs.com', '').strip()
+# address_parts = block[2].split('Lic.#')
+# obs['lab_license_number'] = address_parts[-1].strip()
+# address = address_parts[0]
+
+# TODO: Get the client details (if present).
+
+# Future work: Get the moisture content and foreign matter.
+
+# Iterate over lines to get sample details and cannabinoid results.
+obs = {}
+analyses = []
+dates_tested = []
+product_name = lines[0]
+results = []
+for line in lines[1:]:
+
+    # Remove excess spaces from the lines.
+    line_text = line.strip()
+
+    # Skip nuisance rows.
+    if line_text in coa_skip_values:
+        continue
+
+    # Get the sample details.
+    # FIXME: Handle COAs with client details.
+    elif ':' in line_text:
+        fields = list(coa_sample_detail_fields.keys())
+        for field in fields:
+            if f'{field}:' in line_text:
+                key = coa_sample_detail_fields[field]
+                if obs.get(key):
+                    continue
+                detail = line_text.replace(f'{field}:', '').strip()
+                secondary_detail = None
+                for secondary_field in fields:
+                    if secondary_field != field and f'{secondary_field}:' in detail:
+                        parts = detail.split(f'{secondary_field}:')
+                        secondary_detail = parts[-1].strip()
+                        secondary_key = coa_sample_detail_fields[secondary_field]
+                        obs[secondary_key] = secondary_detail
+                        obs[key] = parts[0].strip()
+                        continue
+                if secondary_detail is None:
+                    obs[key] = detail
+
+    # Get the analyses and date tested.
+    elif line_text.endswith('Tested'):
+        parts = line_text.replace('Tested', '').strip().split(' ')
+        date = pd.to_datetime(parts[-1])
+        dates_tested.append(date)
+
+    # Get the analysis method.
+    elif ' by ' in line_text:
+        parts = line_text.split(' by ')
+        analysis = parts[0].lower()
+        analysis = parser.analyses.get(analysis, analysis)
+        method = parts[-1]
+        obs[f'{analysis}_method'] = method
+
+    # Get the total cannabinoids.
+    elif line_text.startswith('Total') and not line_text.endswith('Internal Standard'):
+        parts = line_text.split(' ')
+        parts = [x for x in parts if x.strip()]
+        key = snake_case(parts[0])
+        if key == 'total':
+            key = 'total_cannabinoids'
+        key = parser.fields.get(key, key)
+        obs[key] = convert_to_numeric(parts[1])
+
+    # Get the cannabnoid results.
+    else:
+        parts = line_text.split(' ')
+        parts = [x for x in parts if x.strip()]
+        name = parts[0]
+        analyte = snake_case(name)
+        analyte = parser.analytes.get(analyte, analyte)
+        columns = KCA_LABS_ANALYSES['cannabinoids']['columns']
+        result = {
+            'analysis': 'cannabinoids',
+            'units': 'percent',
+            'key': analyte,
+        }
+        for i, column in enumerate(columns):
+            result[column] = convert_to_numeric(parts[i])
+        if name == 'Total' or isinstance(result['name'], float):
+            continue
+        results.append(result)
+
+# Get the results from the subsequent pages.
+for page in report.pages[1:]:
+
+    # Split at page number to remove the header,
+    # then split at the note to remove the footer,
+    # then remove the product name and sample details.
+    text = page.extract_text()
+    split = re.split('\d+ of \d+', text)
+    body = split[-1].split('ND = Not Detected')[0]
+    lines = body.split('\n')
+    lines = [x for x in lines if x.strip()]
+    lines = [x for x in lines if x != product_name and ':' not in x]
+
+    # Get the analysis and analysis method.
+    analysis_parts = lines[0].strip().split(' by ')
+    analysis = snake_case(analysis_parts[0])
+    analysis = parser.analyses.get(analysis, analysis)
+    obs[f'{analysis}_method'] = analysis_parts[-1]
+    analyses.append(analysis)
+
+    # Get the columns.
+    columns = lines[1].split(' ')
+    columns = [x.lower() for x in columns if x and '(' not in x]
+    columns = [x.replace('result', 'value').replace('analyte', 'name') for x in columns]
+    units = lines[1].split('(')[-1].split(')')[0]
+    if 'name' not in columns:
+        if len(columns) == 6:
+            columns = ['name'] + columns[:3] + ['name'] + columns[3:]
+        else:
+            columns.insert(0, 'name')
+
+    # Compile the results.
+    for line in lines[2:]:
+        result = {'analysis': analysis, 'units': units}
+        values = line.split(' ')
+
+        # FIXME: If name is in the columns twice, then handle accordingly.
+        # FIXME: Combine analyte names with spaces
+        k = len(columns)
+        if len(values) > k:
+            half_point = round(k / 2)
+            first_break = 1
+            if k % 2 ==0 :
+                first_break = 2
+            second_break = - (half_point + 1)
+            values = [' '.join(values[:first_break])] + values[first_break:half_point]
+            values = [' '.join(values[half_point:second_break])] + values[second_break:]
+
+        # Record the result values.
+        for i, column in enumerate(columns):
+            result[column] = convert_to_numeric(values[i])
+            if column == 'name':
+                analyte = snake_case(result['name'])
+                analyte = parser.analytes.get(analyte, analyte)
+                result['key'] = analyte
+        results.append(result)
+
+# Turn dates to ISO format.
+date_columns = [x for x in obs.keys() if x.startswith('date')]
+for date_column in date_columns:
+    try:
+        obs[date_column] = pd.to_datetime(obs[date_column]).isoformat()
+    except:
+        pass
+
+# Ensure numeric columns are numeric.
+for col in coa_numeric_columns:
+    obs[col] = convert_to_numeric(obs[col])
+
+# Finish data collection with a freshly minted sample ID.
+date_tested = max(dates_tested).isoformat()
+obs['analyses'] = analyses
+obs['date_tested'] = date_tested
+obs['product_name'] = product_name
+obs['results'] = results
+
+# TODO: Create `sample_hash` and `results_hash`.
+
+
+obs['sample_id'] = create_sample_id(
+    private_key=json.dumps(results),
+    public_key=product_name,
+    salt=date_tested,
+)
+obs['coa_parsed_at'] = datetime.now().isoformat()
+obs['coa_algorithm_version'] = __version__
+print({**KCA_LABS, **obs})
+
+
+# Cannabis Data Science Exercise:
+# Look at the Tangie relative to other Tangie test results from
+# MCR Labs, SC Labs, and PSI Labs.
diff --git a/cannlytics/data/coas/mcrlabs.py b/cannlytics/data/coas/mcrlabs.py
index 4fc39174..b95cf300 100644
--- a/cannlytics/data/coas/mcrlabs.py
+++ b/cannlytics/data/coas/mcrlabs.py
@@ -1,12 +1,12 @@
 """
-Get MCR Labs Test Result Data
+CoADoc | Get MCR Labs Test Result Data
 Copyright (c) 2022 Cannlytics
 
 Authors:
     Keegan Skeate <https://github.com/keeganskeate>
     Candace O'Sullivan-Sutherland <https://github.com/candy-o>
 Created: 7/13/2022
-Updated: 8/30/2022
+Updated: 10/10/2022
 License: MIT License <https://github.com/cannlytics/cannlytics/blob/main/LICENSE>
 
 Description:
@@ -44,7 +44,6 @@
 
 Future development:
 
-    - [ ] Create a parse CoA routine that could handle a MCR labs PDF.
     - [ ] Implement a function to get all of a given client's lab results.
     - Optional: Create necessary data dirs automatically.
     - Optional: Function to download any pre-existing results.
@@ -60,28 +59,74 @@
 
 # External imports.
 from bs4 import BeautifulSoup
+import pandas as pd
+import pdfplumber
 import requests
 
 # Internal imports.
-from cannlytics.data.data import create_sample_id
-from cannlytics.utils.constants import ANALYSES, ANALYTES, DEFAULT_HEADERS
+from cannlytics import __version__
+from cannlytics.data.data import create_hash, create_sample_id
+from cannlytics.utils.constants import (
+    ANALYSES,
+    ANALYTES,
+    DECARB,
+    DEFAULT_HEADERS,
+    STANDARD_UNITS,
+)
 from cannlytics.utils.utils import (
     convert_to_numeric,
     format_iso_date,
     snake_case,
     strip_whitespace,
 )
+from datasets import load_dataset
 
 
 # It is assumed that the lab has the following details.
 MCR_LABS = {
     'coa_algorithm': 'mcrlabs.py',
-    'coa_algorithm_entry_point': 'get_mcr_labs_sample_details',
+    'coa_algorithm_entry_point': 'parse_mcrlabs_coa',
     'lims': 'MCR Labs',
     'url': 'https://reports.mcrlabs.com',
     'lab': 'MCR Labs',
     'lab_website': 'https://mcrlabs.com',
-    'public': True,
+    'lab_license_number': 'IL281277',
+    'lab_image_url': '',
+    'lab_address': '85 Speen St. Lower Level, Framingham, MA 01701',
+    'lab_street': '85 Speen St. Lower Level',
+    'lab_city': 'Framingham',
+    'lab_county': 'Middlesex',
+    'lab_state': 'MA',
+    'lab_zipcode': '01701',
+    'lab_latitude': 42.310820,
+    'lab_longitude': -71.386920,
+    'lab_phone': '508-872-6666',
+    'lab_email': 'hello@mcrlabs.com',
+}
+
+# It is assumed that there are the following analyses on each COA.
+MCR_LABS_COA = {
+    'fields': {
+        'Sample ID #': 'lab_id',
+        'Sample Name': 'product_name',
+        'Batch': 'batch_number',
+        'Matrix': 'product_type',
+        'Date Received' : 'date_received',
+        'Date Tested': 'date_tested',
+        'Serving size weight': 'serving_size',
+        'Analyte': 'name',
+        'Cannabinoid': 'skip',
+        'Conc.': 'value',
+        'Test ID': 'skip',
+        'Test Analysis': 'name',
+        'Result': 'value',
+        'LOD': 'lod',
+        'LOQ': 'loq',
+        'Limits': 'limit',
+        'Disposition': 'status',
+        'Results': 'value',
+        'Unit': 'skip',
+    }
 }
 
 
@@ -552,12 +597,312 @@ def get_mcr_labs_client_details():
     raise NotImplementedError
 
 
+def parse_mcrlabs_pdf(
+        parser,
+        doc: Any,
+        coa_pdf: Optional[str] = '',
+        **kwargs,
+    ) -> dict:
+    """Parse a MCR Labs COA PDF.
+    Args:
+        parser (CoADoc): A CoADoc parsing client.
+        doc (str or PDF): A PDF file path or pdfplumber PDF.
+        coa_pdf (str): A filename to use for the `coa_pdf` field (optional).
+    Returns:
+        (dict): The sample data, including:
+            ✓ analyses
+            ✓ batch_number
+            ✓ methods
+            - {analysis}_status TODO: Record the pass / fail status for each analysis.
+            ✓ date_received
+            ✓ date_tested
+            ✓ lab_id
+            ✓ metrc_lab_id
+            ✓ metrc_source_id
+            ✓ producer
+            ✓ producer_street
+            ✓ producer_city
+            ✓ producer_state
+            ✓ producer_zipcode
+            ✓ producer_license_number
+            ✓ product_name
+            ✓ product_type
+            ✓ results
+            ✓ results_hash
+            ✓ sample_hash
+            ✓ serving_size
+            ✓ sum_of_cannabinoids (Calculate simple sum of all cannabinoids)
+            ✓ total_cannabinoids (Applying decarb rate to all acidic cannabinoids)
+            ✓ total_cbd
+            ✓ total_thc
+            - total_terpenes
+    """
+    # Read the PDF.
+    obs = {}
+    if isinstance(doc, str):
+        report = pdfplumber.open(doc)
+        obs['coa_pdf'] = coa_pdf or doc.replace('\\', '/').split('/')[-1]
+    else:
+        report = doc
+        obs['coa_pdf'] = coa_pdf or report.stream.name.replace('\\', '/').split('/')[-1]
+
+    # Get the sample details.
+    front_page = report.pages[0]
+    tables = front_page.extract_tables()
+
+    # Get the producer data.
+    # FIXME: There is probably a more elegant way to parse this block.
+    producer_details = tables[0][0][0].split('\n')
+    if len(producer_details) == 5:
+        producer = producer_details[1] + producer_details[2]
+        address = ', '.join([producer_details[-2], producer_details[-1]])
+    elif len(producer_details) == 6:
+        producer = producer_details[1] + producer_details[2]
+        address = ', '.join([
+            producer_details[-3] + producer_details[-2],
+            producer_details[-1]
+        ])
+    else:
+        producer = producer_details[1]
+        address = ', '.join([producer_details[-2], producer_details[-1]])
+
+    # Optional: Get the producer address query for Google Places search.
+    # query = tables[0][0][0].replace('\n', ' ').split('Client:')[-1] \
+    #     .replace('  ', ' ').strip()
+
+    # Get producer and producer's address.
+    # FIXME: We may be able to look up the license with the MA Open Data API.
+    address_parts = address.split(', ')
+    state_parts = address_parts[-1].split(' ')
+    obs['producer'] = producer
+    obs['producer_street'] = address_parts[0]
+    obs['producer_city'] = address_parts[1]
+    obs['producer_zipcode'] = state_parts[-1]
+    obs['producer_zipcode'] = state_parts[0].strip()
+
+    # Lookup retailer for `cannabis_licenses` dataset.
+    # FIXME: This may return multiple matches.
+    # TODO: It may be better to lookup the license data with the Cannlytics API.
+    ma_licenses = load_dataset('cannlytics/cannabis_licenses', 'ma')
+    licenses = ma_licenses['data'].to_pandas()
+    criterion = licenses['business_legal_name'].str.contains(obs['producer'])
+    match = licenses.loc[criterion]
+    if len(match) == 0:
+        criterion = licenses['premise_street_address'].str.contains(obs['producer_street'])
+        match = licenses.loc[criterion]
+    if len(match) != 0:
+        licensee = match.iloc[0]
+        obs['producer_county'] = licensee['premise_county']
+        obs['producer_latitude'] = licensee['premise_latitude']
+        obs['producer_longitude'] = licensee['premise_longitude']
+        obs['producer_license_number'] = licensee['license_number']
+
+    # Get the sample data.
+    standard_fields = MCR_LABS_COA['fields']
+    fields = tables[1][0]
+    values = tables[1][-1]
+    for i, field in enumerate(fields):
+        key = field.replace('\n', '').strip()
+        key = standard_fields[key]
+        value = values[i].replace('\n', '').strip()
+        obs[key] = value
+
+    # Get the Metrc source ID.
+    # FIXME: Get both Metrc IDs if present.
+    try:
+        prefix = '1A40A01'
+        front_page_text = front_page.extract_text()
+        metrc_id = prefix + front_page_text.split(prefix)[-1].split('\n')[0].split('Date')[0]
+        obs['metrc_source_id'] = metrc_id
+        obs['metrc_lab_id'] = metrc_id
+    except:
+        obs['metrc_lab_id'] = None
+
+    # Get the date reported to override `date_tested`.
+    try:
+        date = front_page_text.split('Report Date:')[-1].strip().split(' ')[:3]
+        obs['date_tested'] = pd.to_datetime(', '.join(date))
+    except:
+        pass
+
+    # Get and standardize the analyses.
+    analyses_table = tables[-1][1:]
+    analyses = [x[0] for x in analyses_table]
+    analyses = [parser.analyses.get(x, x) for x in analyses]
+
+    # Get the methods and results.
+    # - analysis, units, key, name, value, mg_g, limit, status
+    methods, results = [], []
+    for page in report.pages[1:]:
+
+        # Get the text and the tables.
+        page_text = page.extract_text()
+        tables = page.extract_tables()
+
+        # Skip quality control (QC) pages.
+        if 'Quality control checks' in page_text:
+            continue
+
+        # Get the analysis.
+        analysis = page_text.split('Analyst:')[0].split('\n')[-1]
+        analysis = analysis.split(' [')[0]
+        analysis = parser.analyses.get(analysis, analysis)
+
+        # Get the method.
+        method = 'The sample' + page_text.split('Table ')[0] \
+            .split('The sample')[-1].split('.')[0] + '.'
+        methods.append(method)
+
+        # Get the columns and units.
+        columns = [x.replace('\n', '') for x in tables[0][0]]
+        column_text = ','.join(columns)
+        columns = [re.sub('\(.*?\)|\[.*?\]', '', x).strip() for x in columns]
+        columns = [standard_fields.get(x, x) for x in columns]
+        if '(' in column_text:
+            units = column_text.split('(')[-1].split(')')[0]
+        else:
+            units = STANDARD_UNITS.get(analysis)
+
+        # Get the values from the tables.
+        for index, table in enumerate(tables):
+            if index == 0:
+                table = table[1:]
+            for row in table:
+
+                # Get the result data.
+                result = {'analysis': analysis, 'units': units}
+                total = False
+                for i, cell in enumerate(row):
+                    column = columns[i]
+
+                    # Get the result name and key.
+                    if column == 'name':
+                        name = cell.replace('\n', '').replace('  ', ' ')
+                        key = snake_case(name)
+                        result['key'] = parser.analytes.get(key, key)
+
+                        # Record total THC and total CBD.
+                        if cell.startswith('Total THC'):
+                            obs['total_thc'] = convert_to_numeric(row[2])
+                            total = True
+                            continue
+                        elif cell.startswith('Total CBD'):
+                            obs['total_cbd'] = convert_to_numeric(row[2])
+                            total = True
+                            continue
+                        elif key == 'test_analysis':
+                            total = True
+                            continue
+
+                    # Handle multiple occurrences of value.
+                    elif column == 'value':
+                        
+                        serving_size = result.get('value', None)
+                        if serving_size:
+                            result['serving_size'] = serving_size
+                    
+                    # Skip nuisance values.
+                    elif column == 'skip':
+                        continue
+                    
+                    # Record the value.
+                    value = convert_to_numeric(cell)
+                    if isinstance(value, str):
+                        value = value.replace('\n', '') \
+                            .replace('Not Detected', 'ND') \
+                            .replace('  ', ' ')
+                        # value = value.replace('Δ', 'Delta-')
+                    result[column] = value
+                
+                # Record the result, skipping total rows.
+                if not total:
+                    results.append(result)
+
+    # Close the report.
+    report.close()
+
+    # Calculate total (active) cannabinoids and sum of cannabinoids.
+    total_cannabinoids = 0
+    sum_of_cannabinoids = 0
+    for result in results:
+        if result['analysis'] == 'cannabinoids':
+            value = result['value']
+            if isinstance(value, float):
+                if result['key'].endswith('a'):
+                    total_cannabinoids += DECARB * value
+                else:
+                    total_cannabinoids += value
+                sum_of_cannabinoids += value
+    obs['total_cannabinoids'] = total_cannabinoids
+    obs['sum_of_cannabinoids'] = sum_of_cannabinoids
+
+    # Hot-fix: MCR Labs reports total mycotoxins.
+    # for i, result in enumerate(results):
+    #     if result['analysis'] == 'mycotoxins':
+    #         result['key'] = 'total_aflatoxins'
+    #         results.append(result)
+    #         del results[i]
+    #         break
+
+    # Turn dates to ISO format.
+    date_columns = [x for x in obs.keys() if x.startswith('date')]
+    for date_column in date_columns:
+        try:
+            obs[date_column] = pd.to_datetime(obs[date_column]).isoformat()
+        except:
+            pass
+
+    # Finish data collection with a freshly minted sample ID.
+    obs = {**MCR_LABS, **obs}
+    obs['analyses'] = json.dumps(analyses)
+    obs['coa_algorithm_version'] = __version__
+    obs['coa_parsed_at'] = datetime.now().isoformat()
+    obs['methods'] = json.dumps(methods)
+    obs['results'] = results
+    obs['results_hash'] = create_hash(results)
+    obs['sample_id'] = create_sample_id(
+        private_key=json.dumps(results),
+        public_key=obs['product_name'],
+        salt=obs.get('producer', obs.get('date_tested', 'cannlytics.eth')),
+    )
+    # Note: Fix for `TypeError: Object of type float32 is not JSON serializable`.
+    obs['sample_hash'] = create_hash(str(obs))
+    return obs
+
+
+def parse_mcrlabs_coa(
+        parser,
+        doc: Any,
+        **kwargs,
+    ) -> dict:
+    """Parse a MCR Labs COA PDF or URL.
+    Args:
+        doc (str or PDF): A PDF file path or pdfplumber PDF.
+        kwargs (arguments): Arguments to pass to the parsing algorithms.
+    Returns:
+        (dict): The sample data.
+    """
+    if isinstance(doc, str):
+        if doc.endswith('.pdf'):
+            data = parse_mcrlabs_pdf(parser, doc, **kwargs)
+        else:
+            data = get_mcr_labs_sample_details(parser, doc, **kwargs)
+    else:
+        data = parse_mcrlabs_pdf(parser, doc, **kwargs)
+    if isinstance(doc, str):
+        data['coa_pdf'] = doc.replace('\\', '/').split('/')[-1]
+    elif isinstance(doc, pdfplumber.pdf.PDF):
+        data['coa_pdf'] = doc.stream.name.replace('\\', '/').split('/')[-1]
+    return data
+
+
+# === Tests ===
 if __name__ == '__main__':
 
-    # === Tests ===
-    from cannlytics.utils.utils import to_excel_with_style
-    from datetime import datetime
-    import pandas as pd
+    from cannlytics.data.coas import CoADoc
+    # from cannlytics.utils.utils import to_excel_with_style
+    # from datetime import datetime
+    # import pandas as pd
 
     # Specify where your test data lives.
     DATA_DIR = '../../../.datasets/lab_results'
@@ -597,3 +942,20 @@ def get_mcr_labs_client_details():
     # datafile = f'{DATA_DIR}/mcr-lab-results-{timestamp}.xlsx'
     # to_excel_with_style(pd.DataFrame(recent_results), datafile)
     # print('Tested getting the most recent samples.')
+
+    # [✓] TEST: Parse a MCR Labs COA PDF from 2022.
+    # parser = CoADoc()
+    # doc = '../../../tests/assets/coas/mcr-labs/Bulk 100mg Milk Chocolate Drops.pdf'
+    # lab = parser.identify_lims(doc, lims={'MCR Labs': MCR_LABS})
+    # assert lab == 'MCR Labs'
+    # data = parse_mcrlabs_pdf(parser, doc)
+    # data = parse_mcrlabs_coa(parser, doc)
+    # assert data is not None
+    # print('Parsed:', doc)
+
+    # [✓] TEST: Parse a MCR Labs COA PDF from 2021.
+    # parser = CoADoc()
+    # doc = '../../../tests/assets/coas/mcr-labs/Watermelon 30mg Gummy.pdf'
+    # data = parse_mcrlabs_pdf(parser, doc)
+    # assert data is not None
+    # print('Parsed:', doc)
diff --git a/cannlytics/data/coas/sclabs.py b/cannlytics/data/coas/sclabs.py
index 6aa8218c..ec8ec7ed 100644
--- a/cannlytics/data/coas/sclabs.py
+++ b/cannlytics/data/coas/sclabs.py
@@ -105,7 +105,9 @@
 import requests
 
 # Internal imports.
+from cannlytics import __version__
 from cannlytics.data.data import (
+    create_hash,
     create_sample_id,
     find_first_value,
     parse_data_block,
@@ -594,7 +596,8 @@ def get_sc_labs_sample_details(
         result['limit'] = convert_to_numeric(result.get('limit'))
 
         # Assign a `key` for the analyte.
-        result['key'] = snake_case(result['name'])
+        analyte = snake_case(result['name'])
+        result['key'] = parser.analytes(analyte, analyte)
 
         # Update the result.
         results[i] = result
@@ -807,9 +810,6 @@ def parse_sc_labs_pdf(parser, doc: Any, **kwargs) -> dict:
             name = line[:first_value].replace('\n', ' ').strip()
             analyte_analysis_map[name] = analysis
 
-    # FIXME: There are odd result fields showing up.
-    # E.g. `13_1_percent_tested_06_to_05_to_2021_method_qsp_1224_loss_on_drying_moisture`
-
     # Get the results.
     results = []
     for page in report.pages[1:]:
@@ -830,6 +830,11 @@ def parse_sc_labs_pdf(parser, doc: Any, **kwargs) -> dict:
                 if 'per_unit' in key:
                     continue
 
+                # Hot-fix: Skip (non-)analytes that begin with a digit.
+                # Note: It would be best to improve this logic.
+                if key[0].isdigit():
+                    continue
+
                 # Determine the values, handling screens differently.
                 parts = row[1].split(' / ')
                 subparts = parts[-1].strip().split(' ')
@@ -868,17 +873,22 @@ def parse_sc_labs_pdf(parser, doc: Any, **kwargs) -> dict:
             pass
 
     # Finish data collection with a freshly minted sample ID.
-    obs['analyses'] = analyses
+    # TODO: Fix or make `sample_id` obsolete.
+    obs = {**SC_LABS, **obs}
+    obs['analyses'] = json.dumps(analyses)
+    obs['coa_algorithm_version'] = __version__
+    obs['coa_parsed_at'] = datetime.now().isoformat()
     obs['date_tested'] = date_tested
     obs['product_name'] = product_name
-    obs['results'] = results
+    obs['results'] = json.dumps(results)
+    obs['results_hash'] = create_hash(results)
     obs['sample_id'] = create_sample_id(
         private_key=json.dumps(results),
         public_key=obs['product_name'],
-        salt=obs['producer'],
+        salt=obs.get('producer', obs.get('date_tested', 'cannlytics.eth')),
     )
-    obs['coa_parsed_at'] = datetime.now().isoformat()
-    return {**SC_LABS, **obs}
+    obs['sample_hash'] = create_hash(obs)
+    return obs
 
 
 def parse_sc_labs_coa(
@@ -924,26 +934,30 @@ def parse_sc_labs_coa(
     # parser = CoADoc()
     # lab = parser.identify_lims(sc_labs_coa_url)
     # assert lab == 'SC Labs'
-    # data = get_sc_labs_sample_details(sc_labs_coa_url)
+    # data = get_sc_labs_sample_deta
+    # ils(sc_labs_coa_url)
     # assert data is not None
+    # print('Parsed:', doc)
 
     # [✓] TEST: Parse a SC Labs CoA PDF (with cannabinoids and terpenes).
-    # directory = '../../../tests/assets/coas'
+    # directory = '../../../tests/assets/coas/sc-labs'
     # doc = f'{directory}/Mattole Valley Jack H.pdf'
     # parser = CoADoc()
     # lab = parser.identify_lims(doc)
     # assert lab == 'SC Labs'
     # data = parse_sc_labs_pdf(parser, doc)
     # assert data is not None
+    # print('Parsed:', doc)
 
     # [✓] TEST: Parse a SC Labs CoA PDF (with safety screening).
-    # directory = '../../../tests/assets/coas'
-    # doc = f'{directory}/Cherry Punch.pdf'
-    # parser = CoADoc()
-    # lab = parser.identify_lims(doc)
-    # assert lab == 'SC Labs'
-    # data = parse_sc_labs_pdf(parser, doc)
-    # assert data is not None
+    directory = '../../../tests/assets/coas/sc-labs'
+    doc = f'{directory}/Cherry Punch.pdf'
+    parser = CoADoc()
+    lab = parser.identify_lims(doc)
+    assert lab == 'SC Labs'
+    data = parse_sc_labs_pdf(parser, doc)
+    assert data is not None
+    print('Parsed:', doc)
 
     # FIXME:
     # 220000981-Lime-Mojito-Joints.pdf
diff --git a/cannlytics/data/coas/steephill.py b/cannlytics/data/coas/steephill.py
new file mode 100644
index 00000000..2df0510e
--- /dev/null
+++ b/cannlytics/data/coas/steephill.py
@@ -0,0 +1,511 @@
+"""
+CoADoc | Parse Steep Hill COAs
+Copyright (c) 2022 Cannlytics
+
+Authors:
+    Candace O'Sullivan-Sutherland <https://github.com/candy-o>
+    Keegan Skeate <https://github.com/keeganskeate>
+Created: 9/23/2022
+Updated: 10/10/2022
+License: <https://github.com/cannlytics/cannlytics/blob/main/LICENSE>
+
+Description:
+
+    Parse Steep Hill COA PDFs. Validated in:
+
+        ✓ Massachusetts
+
+Data Points:
+
+    ✓ analyses
+    ✓ methods
+    - {analysis}_status TODO: Record the pass / fail status for each analysis.
+    ✓ date_received
+    ✓ date_tested
+    ✓ lab_results_url
+    ✓ metrc_lab_id
+    ✓ metrc_source_id
+    ✓ producer
+    ✓ producer_street
+    ✓ producer_city
+    ✓ producer_state
+    ✓ producer_zipcode
+    ✓ producer_license_number
+    ✓ product_name
+    ✓ product_type
+    ✓ results
+    ✓ results_hash
+    ✓ sample_hash
+    ✓ total_cannabinoids
+    ✓ total_cbd (Calculated as total_cbd = cbd + 0.877 * cbda)
+    ✓ total_thc (Calculated as total_thc = delta_9_thc + 0.877 * thca)
+    ✓ total_terpenes
+
+"""
+# Standard imports.
+from datetime import datetime
+import json
+import os
+import re
+from typing import Any, Optional
+
+# External imports.
+import pandas as pd
+import pdfplumber
+import requests
+
+# Internal imports.
+from cannlytics import __version__
+from cannlytics.data.data import (
+    create_hash,
+    create_sample_id,
+    find_first_value,
+)
+from cannlytics.data.web import download_google_drive_file
+from cannlytics.utils import convert_to_numeric, snake_case
+from cannlytics.utils.constants import DECARB
+    
+
+# It is assumed that the lab has the following details.
+STEEPHILL = {
+    'coa_algorithm': 'steephill.py',
+    'coa_algorithm_entry_point': 'parse_steephill_coa',
+    'url': 'https://www.steephill.com',
+    'lims': 'Steep Hill',
+    'lab': 'Steep Hill',
+    'lab_license_number': 'IL281277',
+    'lab_image_url': '',
+    'lab_address': '40 Speen Street Suite 301, Framingham, MA, 01701',
+    'lab_street': '40 Speen Street Suite 301',
+    'lab_city': 'Framingham',
+    'lab_county': 'Middlesex',
+    'lab_state': 'MA',
+    'lab_zipcode': '01701',
+    'lab_latitude': 42.312180,
+    'lab_longitude': -71.389410,
+    'lab_phone': '508-465-3470',
+    'lab_email': 'support@steephill.com',
+    'lab_website': 'www.steephill.com',
+}
+
+# It is assumed that there are the following analyses on each CoA.
+STEEPHILL_ANALYSES = {
+    'Cannabinoid': {
+        'key': 'cannabinoids',
+        'columns': ['loq', 'value', 'mg_g', 'mg_serving'],
+        'units': 'percent',
+    },
+    'Heavy Metals': {
+        'key': 'heavy_metals',
+        'columns': ['loq', 'value', 'limit', 'status'],
+        'units': 'ppb',
+        # FIXME: Also has limit and status for edibles.
+    },
+    'Microbial Contaminants': {
+        'key': 'microbes',
+        'columns': ['value', 'notes', 'tested_at', 'limit', 'status'],
+        'units': 'CFU/g',
+    },
+    'Pathogenic Bacteria': {
+        'key': 'microbes',
+        'columns': ['value', 'date_tested', 'limit', 'status'],
+        'units': 'CFU/g',
+    },
+    'Mycotoxins': {
+        'key': 'mycotoxins',
+        'columns': ['loq', 'value', 'limit', 'status'],
+        'units': 'ppb',
+    },
+    'Residual Solvent': {
+        'key': 'residual_solvents',
+        'columns': ['loq', 'value', 'limit', 'status'],
+        'units': 'ppm',
+    },
+    'Pesticides': {
+        'key': 'pesticides',
+        'columns': ['loq', 'value', 'limit', 'status'],
+        'units': 'ppb',
+    },
+    'Vitamin E Acetate': {
+        'key': 'vitamin_e_acetate',
+        'columns': ['loq', 'value', 'limit', 'status'],
+        'units': 'ppb',
+    },
+    'Terpenes': {
+        'key': 'terpenes',
+        'columns': ['loq', 'value', 'mg_g'],
+        'units': 'percent',
+    },
+}
+
+# It is assumed that the CoA has the following parameters.
+STEEPHILL_COA = {
+    'fields': {
+        'Production Stage': 'product_subtype',
+        'Product Class': 'product_type',
+        'Ingestion Only': 'cannabinoids_status',
+        'Extraction Solvent': 'residual_solvent_status',
+        'Retail Name': 'product_name',
+    },
+}
+
+
+def parse_steephill_pdf(
+        parser,
+        doc: Any,
+        coa_pdf: Optional[str] = '',
+        **kwargs,
+    ) -> dict:
+    """Parse a Steep Hill CoA PDF.
+    Args:
+        parser (CoADoc): A CoADoc parsing client.
+        doc (str or PDF): A PDF file path or pdfplumber PDF.
+        coa_pdf (str): A filename to use for the `coa_pdf` field (optional).
+    Returns:
+        (dict): The sample data.
+    """
+    # Read the PDF.
+    obs = {}
+    if isinstance(doc, str):
+        report = pdfplumber.open(doc)
+        obs['coa_pdf'] = coa_pdf or doc.replace('\\', '/').split('/')[-1]
+    else:
+        report = doc
+        obs['coa_pdf'] = coa_pdf or report.stream.name.replace('\\', '/').split('/')[-1]
+
+    # Get the lab data.
+    front_page = report.pages[0]
+    tables = front_page.extract_tables()
+    lab_details = tables[0]
+    lab_details = [[item for item in x if item] for x in lab_details]
+    for row in lab_details:
+        for cell in row:
+            if 'Submitted:' in cell:
+                obs['date_tested'] = cell.split('Submitted:')[-1].strip()
+                break
+
+    # Get the client data.
+    client_details = tables[1][-1][0].split('\n')
+    address_parts = client_details[2].split(',')
+    obs['producer'] = client_details[0]
+    obs['producer_street'] = client_details[1]
+    obs['producer_city'] = address_parts[0]
+    obs['producer_state'] = address_parts[-1].strip().split(' ')[0]
+    obs['producer_zipcode'] = address_parts[-1].strip().split(' ')[-1]
+    obs['producer_license_number'] = client_details[3].split(':')[-1].strip()
+    obs['project_id'] = client_details[4].split(':')[-1].strip()
+    obs['date_received'] = client_details[5].split(':')[-1].strip()
+
+    # Get the sample IDs.
+    sample_ids = tables[2][-1][0].split('\n')
+    obs['batch_number'] = sample_ids[0].split(':')[-1].strip()
+    obs['metrc_lab_id'] = sample_ids[1].split(':')[-1].strip()
+    obs['metrc_source_id'] = sample_ids[2].split(':')[-1].strip()
+
+    # Get the sample data.
+    sample_data = tables[3][-1][0].split('\n')
+    obs['sample_weight'] = sample_data[0].split(':')[-1].strip()
+    obs['serving_size'] = sample_data[1].split(':')[-1].strip()
+
+    # Get the product data, appending long rows to the row before.
+    standard_fields = STEEPHILL_COA['fields']
+    product_data = tables[4][1:]
+    for row in product_data:
+        cells = row[0].split('\n')
+        for cell in cells:
+            parts = cell.split(':')
+            try:
+                key = standard_fields[parts[0]]
+            except KeyError:
+                obs[key] = obs[key] + cell
+                continue
+            value = parts[-1].replace('---', '').strip()
+            obs[key] = value
+
+    # Get and standardize the analyses.
+    analyses = []
+    for table in tables:
+        row = table[0]
+        if len(row) == 2:
+            if row[1] in ['Y', 'P', 'F']:
+                analysis = row[0].replace('\n', '')
+                analysis = parser.analyses.get(analysis, analysis)
+                analyses.append(analysis)
+
+    # Get the methods and results.
+    methods, results = [], []
+    for page in report.pages[1:]:
+
+        # Stop at the QC section.
+        page_text = page.extract_text()
+        if 'QA/QC Section' in page_text:
+            break
+
+        # Find all of the tables, then extract the lines of each table.
+        tables = page.find_tables()
+        for table in tables:
+            crop = page.within_bbox(table.bbox)
+            text = crop.extract_text()
+            lines = text.split('\n')
+            for line in lines[:-1]: # Skip the footnote.
+
+                # Get the analysis, e.g. "[H] Cannabinoid Profile Metrc...".
+                if re.match(r'\[[A-Z]+\] ', line[:4]):
+                    analysis_name = line[4:].split('Metrc')[0].strip()
+                    analysis_name = analysis_name.replace(' Results', '') \
+                        .replace(' Analysis', '') \
+                        .replace(' Profile', '')
+                    analysis_data = STEEPHILL_ANALYSES[analysis_name]
+                    analysis = analysis_data['key']
+                    units = analysis_data['units']
+                    columns = analysis_data['columns']
+                    continue
+
+                # Skip column rows and detail rows.
+                elif ';' in line:
+                    continue
+                elif line.startswith('Cannabinoid'):
+                    continue
+                elif line.startswith('Terpene'):
+                    continue
+                elif line.startswith('Analyte'):
+                    continue
+                elif line.startswith('LOQ'):
+                    continue
+                elif line.startswith('Datafile'):
+                    continue
+                elif line.startswith('Analyst'):
+                    continue
+                elif line.startswith('Result'):
+                    continue
+
+                # Collect the methods.
+                elif 'were analyzed' in line or 'were measured' in line or \
+                    'were incubated' in line or 'was determined' in line:
+                    methods.append(line)
+                    continue
+
+                # Handle totals appropriately.
+                elif line.startswith('Total Terpenes'):
+                    value = line.split(' - ')[1]
+                    obs['total_terpenes'] = convert_to_numeric(value)
+                    continue
+                elif line.startswith('Total Available Cannabinoids'):
+                    value = value = line.split(' - ')[1]
+                    obs['total_cannabinoids'] = convert_to_numeric(value)
+                    continue
+                elif line.startswith('Total Mycotoxins'):
+                    continue
+
+                # Record each result, skipping not-tested analytes.
+                elif '(' in line:
+                    name = line.split('(')[1].split(')')[0].strip()
+                    values = line.split(')')[-1].strip().split(' ')
+                    if len(values) < len(columns):
+                        continue
+                    key = snake_case(name)
+                    key = parser.analytes.get(name, parser.analytes.get(key, key))
+                    result = {
+                        'analysis': analysis,
+                        'units': units,
+                        'key': key,
+                        'name': name,
+                    }
+                    for i, column in enumerate(columns):
+                        result[column] = convert_to_numeric(values[i])
+                    results.append(result)
+                    continue
+
+                # Handle standard result lines.
+                line = line.replace('Not Detected', 'ND')
+                first_value = find_first_value(line, [' \d+', 'ND', 'NT'])
+                name = line[:first_value].replace('\n', ' ').strip()
+                key = snake_case(name)
+                key = parser.analytes.get(name, parser.analytes.get(key, key))
+                values = line[first_value:].strip().split(' ')
+                if len(values) < len(columns):
+                    continue
+                result = {
+                    'analysis': analysis,
+                    'units': units,
+                    'key': key,
+                    'name': name,
+                }
+                for i, column in enumerate(columns):
+                    result[column] = convert_to_numeric(values[i])
+                results.append(result)
+
+                # FIXME: microbes status is not correct.
+
+    # Close the report.
+    report.close()
+
+    # Calculate total CBD and total THC if present.
+    specials = {}
+    total_keys = ['cbd', 'cbda', 'delta_9_thc', 'thca']
+    for result in results:
+        if result['key'] in total_keys:
+            specials[result['key']] = result['value']
+    if specials.get('cbd') and specials.get('cbda'):
+        try:
+            acidic = DECARB * specials['cbda']
+        except:
+            acidic = 0
+        try:
+            obs['total_cbd'] = specials['cbd'] + acidic
+        except:
+            obs['total_cbd'] = 'ND'
+    if specials.get('delta_9_thc') and specials.get('thca'):
+        try:
+            acidic = DECARB * specials['thca']
+        except:
+            acidic = 0
+        try:
+            obs['total_thc'] = specials['delta_9_thc'] + acidic
+        except:
+            obs['total_thc'] = 'ND'
+        
+    # FIXME: Calculate total cannabinoids! Sum of cannabinoids!
+
+    # Turn dates to ISO format.
+    date_columns = [x for x in obs.keys() if x.startswith('date')]
+    for date_column in date_columns:
+        try:
+            obs[date_column] = pd.to_datetime(obs[date_column]).isoformat()
+        except:
+            pass
+
+    # Finish data collection with a freshly minted sample ID.
+    obs = {**STEEPHILL, **obs}
+    obs['analyses'] = json.dumps(analyses)
+    obs['coa_algorithm_version'] = __version__
+    obs['coa_parsed_at'] = datetime.now().isoformat()
+    obs['methods'] = json.dumps(methods)
+    obs['results'] = results
+    obs['results_hash'] = create_hash(results)
+    obs['sample_id'] = create_sample_id(
+        private_key=json.dumps(results),
+        public_key=obs['product_name'],
+        salt=obs.get('producer', obs.get('date_tested', 'cannlytics.eth')),
+    )
+    obs['sample_hash'] = create_hash(obs)
+    return obs
+
+
+def parse_steephill_url(
+        parser,
+        url: str,
+        headers: Optional[Any] = None,
+        temp_path: Optional[str] = '/tmp',
+        **kwargs
+    ) -> dict:
+    """Parse a Steep Hill COA URL.
+    Args:
+        url (str): The COA URL.
+        headers (Any): Optional headers for standardization.
+        max_delay (float): The maximum number of seconds to wait
+            for the page to load.
+        persist (bool): Whether to persist the driver.
+            The default is `False`. If you do persist
+            the driver, then make sure to call `quit`
+            when you are finished.
+    Returns:
+        (dict): The sample data.
+    """
+    # Ensure that the URL is direct to Google Drive.
+    pdf_url = str(url)
+    if not pdf_url.startswith('https://drive.google'):
+        response = requests.get(pdf_url, headers=headers)
+        pdf_url = response.url
+    if not pdf_url.startswith('https://drive.google'):
+        raise ValueError('COA URL not recognized as a Steep Hill URL.')
+
+    # Download the PDF from Google Drive.
+    temp_pdf = os.path.join(temp_path, 'coa.pdf')
+    if not os.path.exists(temp_path): os.makedirs(temp_path)
+    drive_id = pdf_url.split('/d/')[-1].split('/')[0]
+    download_google_drive_file(drive_id, temp_pdf)
+
+    # Extract the data with the PDF parsing algorithm and record the URL.
+    data = parse_steephill_pdf(
+        parser,
+        temp_pdf,
+        coa_pdf=url,
+        **kwargs,
+    )
+    data['lab_results_url'] = pdf_url
+    return data
+
+
+def parse_steephill_coa(
+        parser,
+        doc: Any,
+        **kwargs,
+    ) -> dict:
+    """Parse a Steep Hill COA PDF or URL.
+    Args:
+        doc (str or PDF): A PDF file path or pdfplumber PDF.
+        kwargs (arguments): Arguments to pass to the parsing algorithms.
+    Returns:
+        (dict): The sample data.
+    """
+    if isinstance(doc, str):
+        if doc.startswith('http'):
+            return parse_steephill_url(parser, doc, **kwargs)
+        elif doc.endswith('.pdf'):
+            data = parse_steephill_pdf(parser, doc, **kwargs)
+        else:
+            data = parse_steephill_pdf(parser, doc, **kwargs)
+    else:
+        data = parse_steephill_pdf(parser, doc, **kwargs)
+    if isinstance(doc, str):
+        data['coa_pdf'] = doc.replace('\\', '/').split('/')[-1]
+    elif isinstance(doc, pdfplumber.pdf.PDF):
+        data['coa_pdf'] = doc.stream.name.replace('\\', '/').split('/')[-1]
+    return data
+
+
+# === Tests ===
+if __name__ == '__main__':
+
+    from cannlytics.data.coas import CoADoc
+
+    # Specify testing constants.
+    temp_path = '../../../tests/assets/coas/tmp'
+    short_url = 'https://tinyurl.com/mr4cnhm3'
+    long_url = 'https://drive.google.com/file/d/10S_odKnB5B76Zhgjd0ALb3ffkSp649sL/view'
+    doc = '../../../tests/assets/coas/steep-hill/Dosi-Woah.pdf'
+    coas = [short_url, long_url, doc] 
+
+    # # [✓] TEST: Identify Steep Hill COAs.
+    # parser = CoADoc()
+    # for coa in coas:
+    #     lab = parser.identify_lims(coa)
+    #     assert lab == 'Steep Hill'
+
+    # # [✓] Parse a Steep Hill COA from a short URL.
+    # parser = CoADoc()
+    # data = parse_steephill_url(parser, short_url, temp_path=temp_path)
+    # assert data is not None
+    # print('Parsed:', short_url) 
+
+    # # [✓] Parse a Steep Hill COA from a long URL.
+    # parser = CoADoc()
+    # data = parse_steephill_url(parser, long_url, temp_path=temp_path)
+    # assert data is not None
+    # print('Parsed:', long_url) 
+
+    # # [✓] TEST: Parse a Steep Hill COA from a PDF.
+    parser = CoADoc()
+    doc = '../../../tests/assets/coas/steep-hill/SEL.SL.220601.pdf'
+    data = parse_steephill_pdf(parser, doc)
+    assert data is not None
+    print('Parsed:', doc)
+
+    # # [✓] TEST: Parse each URL and PDF ambiguously.
+    # parser = CoADoc()
+    # coas = [short_url, long_url, doc]
+    # for coa in coas:
+    #     data = parser.parse(coa, temp_path=temp_path)
+    #     assert data is not None
+    # print('✓ Completed Steep Hill COA parsing tests.')
diff --git a/cannlytics/data/coas/tagleaf.py b/cannlytics/data/coas/tagleaf.py
index 401a1d9e..a901e3ae 100644
--- a/cannlytics/data/coas/tagleaf.py
+++ b/cannlytics/data/coas/tagleaf.py
@@ -11,7 +11,10 @@
 
 Description:
 
-    Parse a TagLeaf LIMS CoA PDF or URL.
+    Parse a TagLeaf LIMS CoA PDF or URL. Labs include:
+
+        - 2 River Labs, Inc
+        - BelCosta Labs
 
 Data Points:
 
diff --git a/cannlytics/data/coas/veda.py b/cannlytics/data/coas/veda.py
index ea54e2c7..a7f967c6 100644
--- a/cannlytics/data/coas/veda.py
+++ b/cannlytics/data/coas/veda.py
@@ -545,7 +545,7 @@ def parse_veda_coa(parser, doc: Any, **kwargs) -> dict:
     from cannlytics.data.coas import CoADoc
 
     # Specify where your test CoA lives.
-    DATA_DIR = '../../../tests/assets/coas'
+    DATA_DIR = './tests/assets/coas/veda-scientific'
     doc = f'{DATA_DIR}/Veda Scientific Sample COA.pdf'
 
     # [✓] TEST: Detect the lab / LIMS that generated the CoA.
diff --git a/cannlytics/data/data.py b/cannlytics/data/data.py
index 9ea1d700..9e2bb5f3 100644
--- a/cannlytics/data/data.py
+++ b/cannlytics/data/data.py
@@ -44,6 +44,40 @@ def list_datasets() -> list:
     return CANNLYTICS_DATASETS
 
 
+def load_google_sheet(
+        sheet_key,
+        sheet_name='',
+        header_row=1
+    ):
+    """Load a public Google Sheet by key or URL into a DataFrame. E.g.
+
+        https://docs.google.com/spreadsheets/d/{key}/edit/
+
+    Args:
+        sheet_key (str): A Google Sheets URL or the Sheet key found in in the URL.
+        sheet_name (str): A name of a particular sheet, the first sheet
+            is loaded by default (optional).
+        header_row (int): The row of the headers, 1 by default (optional).
+    Returns:
+        (DataFrame): The Google Sheet data in a DataFrame.    
+    Credit: Gianmario Spacagna <https://stackoverflow.com/a/48986530/5021266>
+    License: CC BY-SA 3.0 <https://creativecommons.org/licenses/by-sa/3.0/>
+    """
+    if sheet_key.startswith('https'):
+        key = sheet_key.split('/d/')[-1].split('/edit?')[0]
+    else:
+        key = sheet_key
+    url = 'https://docs.google.com/spreadsheets/d/{key}/gviz/tq?tqx=out:csv&sheet={sheet_name}&headers={header_row}'
+    url = url.format(
+        key=key,
+        sheet_name=sheet_name.replace(' ', '%20'),
+        header_row=header_row,
+    )
+    df = pd.read_csv(url)
+    return df.drop([col for col in df.columns if col.startswith('Unnamed')], axis=1)
+
+
+
 # === Data aggregation tools. ===
 
 def aggregate_datasets(
diff --git a/cannlytics/data/gis.py b/cannlytics/data/gis.py
index 6fae30fd..1e7179b3 100644
--- a/cannlytics/data/gis.py
+++ b/cannlytics/data/gis.py
@@ -13,10 +13,12 @@
 
 """
 # Standard imports.
+from datetime import datetime
 from time import sleep
-from typing import Any, List, Optional
+from typing import Any, List, Optional, Tuple
 
 # External imports.
+from dotenv import dotenv_values
 from fredapi import Fred
 from googlemaps import Client, places
 import zipcodes
@@ -37,6 +39,33 @@ def get_google_maps_api_key() -> str:
     return data['google_maps_api_key']
 
 
+def get_state_data(
+        state: str,
+        code: str,
+        fred_api_key: Optional[str] = None,
+        district: Optional[str] = '',
+        obs_start: Optional[Any] = None,
+        obs_end: Optional[Any] = None,
+    ) -> dict:
+    """Get a given state's data from the Fed Fred API, given a data code.
+    Args:
+        state (str): The state abbreviation for the state to retrieve data.
+        code (str): The FRED code for the data, for example "POP".
+        fred_api_key (str): A Fed FRED API key. You can sign up for a free API key at
+            http://research.stlouisfed.org/fred2/. You can also pass `None`
+            and set the environment variable 'FRED_API_KEY' to the value of
+            your API key.
+    Returns:
+        (dict): Returns a dictionary with population values and source.
+    """
+    fred = Fred(api_key=fred_api_key)
+    code = f'{state.upper()}{code.upper()}{district.upper()}'
+    series = fred.get_series(code, obs_start, obs_end)
+    if len(series) == 1:
+        return series[0]
+    return series
+
+
 def get_state_population(
         state: str,
         fred_api_key: Optional[str] = None,
@@ -131,6 +160,8 @@ def search_for_address(
         fields (list): Optional, `formatted_address` is included by default.
     Returns:
         (list): A list of potential results.
+    Raises:
+        (IndexError): Raises an `IndexError` if no candidate is found.
     """
     if api_key is None:
         api_key = get_google_maps_api_key()
@@ -144,23 +175,90 @@ def search_for_address(
     search = places.find_place(gmaps, query, 'textquery')
     place_id = search['candidates'][0]['place_id']
     place = places.place(gmaps, place_id, fields=fields)
-    location = place['result']['geometry']['location']
-    formatted_address = place['result']['formatted_address']
-    parts = formatted_address.split(',')
-    street = parts[0]
-    city = parts[1].strip()
-    state, zipcode = tuple(parts[2].strip().split(' '))
-    try:
-        county = zipcodes.matching(zipcode)[0]['county']
-    except:
-        county = ''
-    return {
-        'formatted_address': formatted_address,
-        'street': street,
-        'city': city,
-        'county': county,
-        'state': state,
-        'zipcode': zipcode,
-        'latitude': location['lat'],
-        'longitude': location['lat'],
-    }
+    result = place['result']
+    candidate = {}
+    if result.get('geometry'):
+        location = result['geometry']['location']
+        candidate['latitude'] = location['lat']
+        candidate['longitude'] = location['lng']
+        del result['geometry']
+    if result.get('formatted_address'):
+        formatted_address = result['formatted_address']
+        parts = formatted_address.split(',')
+        candidate['street'] = parts[0]
+        candidate['city'] = parts[1].strip()
+        candidate['state'], candidate['zipcode'] = tuple(parts[2].strip().split(' '))
+        try:
+            candidate['county'] = zipcodes.matching(candidate['zipcode'])[0]['county']
+        except:
+            candidate['county'] = ''
+    return {**result, **candidate}
+
+
+def get_transfer_distance(
+        api_key,
+        start,
+        end,
+        mode='driving',
+) -> Tuple[int, int]:
+    """Get the distance and duration of a transfer.
+    Args:
+        client (Client): A googlemaps API client.
+        start (string): The starting point, either lat,long as a string or an address.
+        end (string): The ending point, either lat,long as a string or an address.
+        mode (string): The transportation method, driving by default.
+    Returns:
+        (int, int): Returns a tuple of the distance in kilometers and the
+            duration in seconds.
+    """
+    client = Client(key=api_key)
+    driving_distances = client.distance_matrix(start, end, mode=mode)
+    elements = driving_distances['rows'][0]['elements'][0]
+    km = elements['distance']['value']
+    duration = elements['duration']['value']
+    return km, duration
+
+
+def get_transfer_route(
+        api_key,
+        start,
+        end,
+        departure_time=None,
+        mode='driving',
+) -> str:
+    """Get the route of a transfer.
+    Args:
+        client (Client): A googlemaps API client.
+        start (string): The starting point, either lat,long as a string or an address.
+        end (string): The ending point, either lat,long as a string or an address.
+        departure_time (datetime): The time of departure, defaults to now (optional).
+        mode (string): The transportation method, driving by default (optional).
+    Returns:
+        (str): Returns the route as a polyline string.
+    """
+    client = Client(key=api_key)
+    if departure_time is None:
+        departure_time = datetime.now()
+    driving_directions = client.directions(
+        start,
+        end,
+        mode=mode,
+        departure_time=departure_time
+    )
+    m = driving_directions[0]['legs'][0]['distance']['value']
+    min = driving_directions[0]['legs'][0]['duration']['value']
+    polyline = driving_directions[0]['overview_polyline']['points']
+    return m, min, polyline
+
+
+def initialize_googlemaps(env_file: Optional[str] = './.env') -> Any:
+    """Initialize the Google Maps client.
+    Args:
+        env_file (str): A file path to a .env file with a `GOOGLE_MAPS_API_KEY`
+            environment variable.
+    Returns:
+        (Client): A googlemaps API client.
+    """
+    config = dotenv_values(env_file)
+    google_maps_api_key = config['GOOGLE_MAPS_API_KEY']
+    return Client(key=google_maps_api_key)
diff --git a/cannlytics/data/opendata.py b/cannlytics/data/opendata.py
index a6779379..058cef04 100644
--- a/cannlytics/data/opendata.py
+++ b/cannlytics/data/opendata.py
@@ -21,6 +21,7 @@
 
 """
 # Standard imports.
+import json
 import os
 from typing import Any, Optional
 
@@ -154,7 +155,11 @@ def get(self, endpoint:str, params: Optional[dict] = None) -> Any:
             response = self.session.get(url, headers=self.headers, params=params)
         if response.status_code != 200:
             raise APIError(response)
-        data = pd.DataFrame(response.json())
+        try:
+            body = response.json()
+        except:
+            body = json.loads(response.text.replace('\r', '').replace('\ufeff', ''))
+        data = pd.DataFrame(body)
         data.columns = map(str.lower, data.columns)
         data.columns = [x.replace('$', 'dollars').replace('%', 'percent') for x in data.columns]
         data.rename(columns=OPENDATA_CODINGS, inplace=True)
diff --git a/cannlytics/data/web.py b/cannlytics/data/web.py
index 085e2084..377c6365 100644
--- a/cannlytics/data/web.py
+++ b/cannlytics/data/web.py
@@ -1,10 +1,10 @@
 """
-Web Data Functionality | Cannlytics
+Web Data Tools | Cannlytics
 Copyright (c) 2021-2022 Cannlytics
 
 Authors: Keegan Skeate <https://github.com/keeganskeate>
 Created: 1/10/2021
-Updated: 8/13/2021
+Updated: 10/8/2022
 License: <https://github.com/cannlytics/cannlytics/blob/main/LICENSE>
 
 Resources:
@@ -24,6 +24,8 @@
 from bs4 import BeautifulSoup
 
 
+# === General Web Scraping Tools ===
+
 def format_params(parameters, **kwargs):
     """Format given keyword arguments HTTP request parameters.
     Returns:
@@ -221,3 +223,56 @@ def find_company_url():
     TODO: Find a company's website URL. (Google search for name?)
     """
     raise NotImplementedError
+
+
+# === Google Drive Tools ===
+
+def download_google_drive_file(drive_file, destination):
+    """Download a public Google Drive file given its ID and a destination.
+    Args:
+        drive_file (str): A Google Drive ID or URL for a file.
+        destination (str): The local file path and name.
+    Credit: turdus-merula <https://stackoverflow.com/a/39225272/5021266>
+    License: CC BY-SA 3.0 <https://creativecommons.org/licenses/by-sa/3.0/>
+    """
+    drive_id = drive_file
+    if drive_id.startswith('https://drive.google'):
+        drive_id = drive_id.split('/d/')[-1].split('/')[0]
+    drive_base = 'https://docs.google.com/uc?export=download'
+    drive_session = requests.Session()
+    drive_response = drive_session.get(
+        drive_base,
+        params={'id': drive_id},
+        stream=True,
+    )
+    drive_token = download_google_drive_file_confirm_token(drive_response)
+    if drive_token:
+        drive_response = drive_session.get(
+            drive_base,
+            params={'id': drive_id, 'confirm': drive_token},
+            stream = True
+        )
+    download_google_drive_file_save_response(drive_response, destination)    
+
+
+def download_google_drive_file_confirm_token(drive_response):
+    """
+    Credit: turdus-merula <https://stackoverflow.com/a/39225272/5021266>
+    License: CC BY-SA 3.0 <https://creativecommons.org/licenses/by-sa/3.0/>
+    """
+    for k, v in drive_response.cookies.items():
+        if k.startswith('download_warning'):
+            return v
+    return None
+
+
+def download_google_drive_file_save_response(drive_response, destination):
+    """
+    Credit: turdus-merula <https://stackoverflow.com/a/39225272/5021266>
+    License: CC BY-SA 3.0 <https://creativecommons.org/licenses/by-sa/3.0/>
+    """
+    CHUNK_SIZE = 32768
+    with open(destination, 'wb') as f:
+        for chunk in drive_response.iter_content(CHUNK_SIZE):
+            if chunk:
+                f.write(chunk)
diff --git a/cannlytics/utils/constants.py b/cannlytics/utils/constants.py
index 6a5e9684..37eda05a 100644
--- a/cannlytics/utils/constants.py
+++ b/cannlytics/utils/constants.py
@@ -129,6 +129,9 @@
     'cannabinoids_status': 'cannabinoids',
     'heavy_metals_status': 'heavy_metals',
     'water_activity_status': 'water_activity',
+    'Cannabinoid Profile': 'cannabinoids',
+    'Microbiological Screen': 'microbes',
+    'Mycotoxin Screen': 'mycotoxins',
 }
 
 # A map of encountered analytes to their standardized analyte,
@@ -327,6 +330,34 @@
     'methomy': 'methomyl',
     # FIXME: Standardize `fm`, `sscd`, `cep` without messing everything up.
     # 'ocimene': 'alpha_ocimene',
+    'Tetrahydrocannabinolic acid': 'thca',
+    'D9-Tetrahydrocannabinol': 'delta_9_thc',
+    'Cannabidiolic acid': 'cbda',
+    'Cannabidiol': 'cbd',
+    'Cannabinol': 'cbn',
+    'Cannabichromene': 'cbc',
+    'Cannabigerolic acid': 'cbga',
+    'Cannabigerol': 'cbg',
+    'Cannabidivarin': 'cbdv',
+    'Tetrahydrocannabivarin': 'thcv',
+    'D8-Tetrahydrocannabinol': 'delta_8_thc',
+    'Total Yeast and Mold': 'mold',
+    'Bile-Tolerant Gram-Neg. Bacteria': 'enterobacteriaceae',
+    'btgn': 'enterobacteriaceae',
+    'Total Arsenic': 'arsenic',
+    'Total Mercury': 'mercury',
+    'stec_e_coli': 'e_coli',
+    'stec': 'e_coli',
+    'iso_butane': 'isobutane',
+    'YM': 'mold',
+    'CC': 'total_coliforms',
+    'TAC': 'total_viable_aerobic_bacteria',
+    'total_yeast_and_mold': 'mold',
+    'total_bile_tolerant_gram_negative_bacteria': 'enterobacteriaceae',
+    'mycotoxin': 'total_aflatoxins',
+    # TODO: Identify:
+    # 'Total Coliforms': '',
+    # 'Total Viable Aerobic Bacteria': ''
 }
 # TODO: Find out how to map:
 # cis_nerolidol vs. nerolidol vs. nerolidol_1 vs. nerolidol_2 vs. trans_nerolidol?
@@ -602,6 +633,7 @@
     '< LOQ': 0.0000001,
     '<LLoQ': 0.0000001,
     '<LLOQ': 0.0000001,
+    'BLQ': 0.0000001,
     '<LLoa': 0.0000001,
     '≥ LOD': 10_001,
     'NR': None,
diff --git a/cannlytics/utils/utils.py b/cannlytics/utils/utils.py
index dbf0df2b..dd834e58 100644
--- a/cannlytics/utils/utils.py
+++ b/cannlytics/utils/utils.py
@@ -752,15 +752,29 @@ def get_blocks(files, size=65536):
         yield block
 
 
-def get_number_of_lines(file_name, encoding='utf-16', errors='ignore'):
+def get_number_of_lines(
+        file_name: str,
+        encoding: Optional[str] = 'utf-16',
+        errors: Optional[str] = 'ignore',
+        verbose: Optional[bool] = False,
+    ) -> int:
     """
-    Read the number of lines in a large file.
+    Read the number of lines in a large file, such as a .csv or .tsv file.
+    Args:
+        file_name (str): The file to count the lines, expecting a .csv file.
+        encoding (str): The encoding of the file, "utf-16" by default (optional).
+        errors (str): How to handle the errors, "ignore" by default (optional).
+        verbose (bool): Whether or not to print the count, `False` by
+            default (optional).
+    Returns:
+        (int): The number of lines of the file.
     Credit: glglgl, SU3 <https://stackoverflow.com/a/9631635/5021266>
     License: CC BY-SA 3.0 <https://creativecommons.org/licenses/by-sa/3.0/>
     """
     with open(file_name, 'r', encoding=encoding, errors=errors) as f:
         count = sum(bl.count('\n') for bl in get_blocks(f))
-        print('Number of rows:', count)
+        if verbose:
+            print('Number of rows:', count)
         return count
 
 
diff --git a/console/assets/js/traceability/traceability.js b/console/assets/js/traceability/traceability.js
index 4928c956..4a49b14e 100644
--- a/console/assets/js/traceability/traceability.js
+++ b/console/assets/js/traceability/traceability.js
@@ -161,7 +161,7 @@ const labTests = {
      * @param {String} versionId The secret version, the organization's state.
      */
     const query = `license=${licenseNumber}&org_id=${orgId}&version_id=${versionId}`;
-    const url = `/api/traceability/lab-tests?${query}`;
+    const url = `/api/traceability/results?${query}`;
     authRequest(url).then((response) => {
       const { data } = response;
       if (data) {
diff --git a/console/settings.py b/console/settings.py
index 1dbcdee0..1cc0e007 100644
--- a/console/settings.py
+++ b/console/settings.py
@@ -4,7 +4,7 @@
 
 Authors: Keegan Skeate <https://github.com/keeganskeate>
 Created: 6/5/2021
-Updated: 12/20/2021
+Updated: 9/24/2022
 License: License: MIT License <https://github.com/cannlytics/cannlytics-console/blob/main/LICENSE>
 
 Description: Django settings secured by Google Cloud Secret Manager.
@@ -100,6 +100,8 @@
     'django.contrib.staticfiles',
     'rest_framework',
     'django_feather',
+    'django_robohash',
+    'django.contrib.humanize',
 ]
 
 #-------------------------------------------------------------#
@@ -176,7 +178,7 @@
 #-------------------------------------------------------------#
 
 # Specify allowed domains depending on production or development status.
-ALLOWED_HOSTS = ['*']
+ALLOWED_HOSTS = []
 if PRODUCTION != 'True':
     ALLOWED_HOSTS.extend(['*'])
 try:
@@ -237,7 +239,7 @@
 
 # The directory from where files are served. (web accessible folder)
 STATIC_ROOT = os.path.abspath(
-    os.path.join(os.path.dirname(__file__), '..', 'public/static')
+    os.path.join(os.path.dirname(__file__), '..', 'public/console/static')
 )
 
 # The relative path to serve files.
@@ -258,8 +260,8 @@
 # Whether to expire the session when the user closes their browser.
 SESSION_EXPIRE_AT_BROWSER_CLOSE = True
 
-# The age of session cookies, in seconds. (Currently: 20 minutes)
-SESSION_COOKIE_AGE = 60 * 20
+# The age of session cookies, in seconds. (Currently: 30 days)
+SESSION_COOKIE_AGE = 60 * 60 * 24 * 30
 
 #-------------------------------------------------------------#
 # Customization
@@ -271,3 +273,8 @@
 # Allow Django template tags to span multiple lines.
 # https://stackoverflow.com/questions/49110044/django-template-tag-on-multiple-line
 base.tag_re = re.compile(base.tag_re.pattern, re.DOTALL)
+
+# Make certain Firebase variables easy to reference.
+FIREBASE_API_KEY = env('FIREBASE_API_KEY')
+FIREBASE_PROJECT_ID = env('FIREBASE_PROJECT_ID')
+STORAGE_BUCKET = env('FIREBASE_STORAGE_BUCKET')
diff --git a/console/state.py b/console/state.py
index f9baae25..bf0da8c7 100644
--- a/console/state.py
+++ b/console/state.py
@@ -789,8 +789,8 @@
             },
             {
                 "name": "Lab Tests",
-                "section": "lab-tests",
-                "url": "/traceability/lab-tests",
+                "section": "results",
+                "url": "/traceability/results",
                 "description": "Manage details for each individual lab test performed on submitted packages.",
                 "org_types": ["lab", "producer", "retailer"],
                 "selected": True,
diff --git a/console/static/console/images/logos/cannlytics_logo_with_text.png b/console/static/console/images/logos/cannlytics_logo_with_text.png
new file mode 100644
index 00000000..d2db2114
Binary files /dev/null and b/console/static/console/images/logos/cannlytics_logo_with_text.png differ
diff --git a/console/static/console/images/logos/cannlytics_logo_with_text.svg b/console/static/console/images/logos/cannlytics_logo_with_text.svg
new file mode 100644
index 00000000..a4cba4a2
--- /dev/null
+++ b/console/static/console/images/logos/cannlytics_logo_with_text.svg
@@ -0,0 +1,1658 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   version="1.1"
+   id="svg2"
+   xml:space="preserve"
+   width="1500"
+   height="300"
+   viewBox="0 0 1500 300.00001"
+   sodipodi:docname="cannlytics_logo_with_text_light.svg"
+   inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)"
+   inkscape:export-filename="C:\Users\keega\Documents\cannlytics\cannlytics_website\cannlytics_website\static\cannlytics_website\images\logos\cannlytics_logo_with_text_light.png"
+   inkscape:export-xdpi="96"
+   inkscape:export-ydpi="96"><metadata
+     id="metadata8"><rdf:RDF><cc:Work
+         rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><defs
+     id="defs6"><clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath18"><path
+         d="M 0,1000 H 1000 V 0 H 0 Z"
+         id="path16"
+         inkscape:connector-curvature="0" /></clipPath><radialGradient
+       fx="0"
+       fy="0"
+       cx="0"
+       cy="0"
+       r="1"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(153.89828,0,0,-153.89828,448.69803,468.43307)"
+       spreadMethod="pad"
+       id="radialGradient80"><stop
+         style="stop-opacity:1;stop-color:#b1e3fa"
+         offset="0"
+         id="stop76" /><stop
+         style="stop-opacity:1;stop-color:#159fda"
+         offset="1"
+         id="stop78" /></radialGradient><radialGradient
+       fx="0"
+       fy="0"
+       cx="0"
+       cy="0"
+       r="1"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(326.76865,-23.724398,-23.724398,-326.76865,469.08594,435.46677)"
+       spreadMethod="pad"
+       id="radialGradient108"><stop
+         style="stop-opacity:1;stop-color:#b3e2f2"
+         offset="0"
+         id="stop96" /><stop
+         style="stop-opacity:1;stop-color:#b3e2f2"
+         offset="0.13711485"
+         id="stop98" /><stop
+         style="stop-opacity:1;stop-color:#45ade2"
+         offset="0.483146"
+         id="stop100" /><stop
+         style="stop-opacity:1;stop-color:#45ade2"
+         offset="0.5656981"
+         id="stop102" /><stop
+         style="stop-opacity:1;stop-color:#3a58a7"
+         offset="0.859551"
+         id="stop104" /><stop
+         style="stop-opacity:1;stop-color:#3a58a7"
+         offset="1"
+         id="stop106" /></radialGradient><radialGradient
+       fx="0"
+       fy="0"
+       cx="0"
+       cy="0"
+       r="1"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(-42.873138,-263.83469,-263.83469,42.873138,437.15289,658.31616)"
+       spreadMethod="pad"
+       id="radialGradient128"><stop
+         style="stop-opacity:1;stop-color:#b1e3fa"
+         offset="0"
+         id="stop124" /><stop
+         style="stop-opacity:1;stop-color:#159fda"
+         offset="1"
+         id="stop126" /></radialGradient><linearGradient
+       x1="0"
+       y1="0"
+       x2="1"
+       y2="0"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(242.65645,-204.34229,-204.34229,-242.65645,413.51254,699.70782)"
+       spreadMethod="pad"
+       id="linearGradient152"><stop
+         style="stop-opacity:1;stop-color:#65b045"
+         offset="0"
+         id="stop144" /><stop
+         style="stop-opacity:1;stop-color:#65b045"
+         offset="0.179775"
+         id="stop146" /><stop
+         style="stop-opacity:1;stop-color:#cddd4d"
+         offset="0.66363503"
+         id="stop148" /><stop
+         style="stop-opacity:1;stop-color:#cddd4d"
+         offset="1"
+         id="stop150" /></linearGradient><linearGradient
+       x1="0"
+       y1="0"
+       x2="1"
+       y2="0"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(209.07001,0,0,-209.07001,412.03143,436.01825)"
+       spreadMethod="pad"
+       id="linearGradient174"><stop
+         style="stop-opacity:1;stop-color:#1b5f2f"
+         offset="0"
+         id="stop168" /><stop
+         style="stop-opacity:1;stop-color:#1b5f2f"
+         offset="0.00561798"
+         id="stop170" /><stop
+         style="stop-opacity:1;stop-color:#90c73e"
+         offset="1"
+         id="stop172" /></linearGradient><linearGradient
+       x1="0"
+       y1="0"
+       x2="1"
+       y2="0"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(209.49777,0,0,-209.49777,412.03143,469.83325)"
+       spreadMethod="pad"
+       id="linearGradient198"><stop
+         style="stop-opacity:1;stop-color:#65b045"
+         offset="0"
+         id="stop190" /><stop
+         style="stop-opacity:1;stop-color:#65b045"
+         offset="0.179775"
+         id="stop192" /><stop
+         style="stop-opacity:1;stop-color:#cddd4d"
+         offset="0.66363503"
+         id="stop194" /><stop
+         style="stop-opacity:1;stop-color:#cddd4d"
+         offset="1"
+         id="stop196" /></linearGradient><linearGradient
+       x1="0"
+       y1="0"
+       x2="1"
+       y2="0"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(208.01198,0,0,-208.01198,481.93671,589.20868)"
+       spreadMethod="pad"
+       id="linearGradient222"><stop
+         style="stop-opacity:1;stop-color:#65b045"
+         offset="0"
+         id="stop214" /><stop
+         style="stop-opacity:1;stop-color:#65b045"
+         offset="0.179775"
+         id="stop216" /><stop
+         style="stop-opacity:1;stop-color:#cddd4d"
+         offset="0.66363503"
+         id="stop218" /><stop
+         style="stop-opacity:1;stop-color:#cddd4d"
+         offset="1"
+         id="stop220" /></linearGradient><linearGradient
+       x1="0"
+       y1="0"
+       x2="1"
+       y2="0"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(126.65002,0,0,-126.65002,556.92139,479.14822)"
+       spreadMethod="pad"
+       id="linearGradient244"><stop
+         style="stop-opacity:1;stop-color:#1b5f2f"
+         offset="0"
+         id="stop238" /><stop
+         style="stop-opacity:1;stop-color:#1b5f2f"
+         offset="0.00561798"
+         id="stop240" /><stop
+         style="stop-opacity:1;stop-color:#90c73e"
+         offset="1"
+         id="stop242" /></linearGradient><linearGradient
+       x1="0"
+       y1="0"
+       x2="1"
+       y2="0"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(171.59546,0,0,-171.59546,512.54138,532.59827)"
+       spreadMethod="pad"
+       id="linearGradient268"><stop
+         style="stop-opacity:1;stop-color:#65b045"
+         offset="0"
+         id="stop260" /><stop
+         style="stop-opacity:1;stop-color:#65b045"
+         offset="0.179775"
+         id="stop262" /><stop
+         style="stop-opacity:1;stop-color:#cddd4d"
+         offset="0.66363503"
+         id="stop264" /><stop
+         style="stop-opacity:1;stop-color:#cddd4d"
+         offset="1"
+         id="stop266" /></linearGradient><radialGradient
+       fx="0"
+       fy="0"
+       cx="0"
+       cy="0"
+       r="1"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(10.814745,0,0,-10.814745,533.18097,536.84039)"
+       spreadMethod="pad"
+       id="radialGradient288"><stop
+         style="stop-opacity:1;stop-color:#ffffff"
+         offset="0"
+         id="stop284" /><stop
+         style="stop-opacity:1;stop-color:#a6ce38"
+         offset="1"
+         id="stop286" /></radialGradient><clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath298"><path
+         d="M 0,1000 H 1000 V 0 H 0 Z"
+         id="path296"
+         inkscape:connector-curvature="0" /></clipPath><radialGradient
+       fx="0"
+       fy="0"
+       cx="0"
+       cy="0"
+       r="1"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(8.1565046,0,0,-8.1565046,612.09229,594.8092)"
+       spreadMethod="pad"
+       id="radialGradient336"><stop
+         style="stop-opacity:1;stop-color:#ffffff"
+         offset="0"
+         id="stop332" /><stop
+         style="stop-opacity:1;stop-color:#a6ce38"
+         offset="1"
+         id="stop334" /></radialGradient><clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath346"><path
+         d="M 0,1000 H 1000 V 0 H 0 Z"
+         id="path344"
+         inkscape:connector-curvature="0" /></clipPath><linearGradient
+       x1="0"
+       y1="0"
+       x2="1"
+       y2="0"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(2.0458386,-116.57463,-116.57463,-2.0458386,457.48242,537.37598)"
+       spreadMethod="pad"
+       id="linearGradient10793"><stop
+         style="stop-opacity:1;stop-color:#f79c00"
+         offset="0"
+         id="stop10787" /><stop
+         style="stop-opacity:1;stop-color:#ff5b00"
+         offset="0.542105"
+         id="stop10789" /><stop
+         style="stop-opacity:1;stop-color:#ffae00"
+         offset="1"
+         id="stop10791" /></linearGradient><linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient152"
+       id="linearGradient3300"
+       x1="317.0575"
+       y1="468.43301"
+       x2="580.33899"
+       y2="468.43301"
+       gradientUnits="userSpaceOnUse" /><linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient268"
+       id="linearGradient3308"
+       x1="359.427"
+       y1="435.46701"
+       x2="578.745"
+       y2="435.46701"
+       gradientUnits="userSpaceOnUse" /><linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient244"
+       id="linearGradient3316"
+       x1="349.82901"
+       y1="476.5835"
+       x2="580.33899"
+       y2="476.5835"
+       gradientUnits="userSpaceOnUse" /><linearGradient
+       x1="0"
+       y1="0"
+       x2="1"
+       y2="0"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(2.0458112,-116.57307,-116.57307,-2.0458112,455.1538,531.86409)"
+       spreadMethod="pad"
+       id="linearGradient10725"><stop
+         style="stop-opacity:1;stop-color:#f79c00"
+         offset="0"
+         id="stop10719" /><stop
+         style="stop-opacity:1;stop-color:#ff5b00"
+         offset="0.542105"
+         id="stop10721" /><stop
+         style="stop-opacity:1;stop-color:#ffae00"
+         offset="1"
+         id="stop10723" /></linearGradient><linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient152"
+       id="linearGradient3383"
+       gradientUnits="userSpaceOnUse"
+       x1="317.0575"
+       y1="468.43301"
+       x2="580.33899"
+       y2="468.43301" /><linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient152"
+       id="linearGradient3385"
+       gradientUnits="userSpaceOnUse"
+       x1="317.0575"
+       y1="468.43301"
+       x2="580.33899"
+       y2="468.43301" /><linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient152"
+       id="linearGradient3387"
+       gradientUnits="userSpaceOnUse"
+       x1="317.0575"
+       y1="468.43301"
+       x2="580.33899"
+       y2="468.43301" /><linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient152"
+       id="linearGradient3389"
+       gradientUnits="userSpaceOnUse"
+       x1="317.0575"
+       y1="468.43301"
+       x2="580.33899"
+       y2="468.43301" /><linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient268"
+       id="linearGradient3391"
+       gradientUnits="userSpaceOnUse"
+       x1="359.427"
+       y1="435.46701"
+       x2="578.745"
+       y2="435.46701" /><linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient268"
+       id="linearGradient3393"
+       gradientUnits="userSpaceOnUse"
+       x1="359.427"
+       y1="435.46701"
+       x2="578.745"
+       y2="435.46701" /><linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient268"
+       id="linearGradient3395"
+       gradientUnits="userSpaceOnUse"
+       x1="359.427"
+       y1="435.46701"
+       x2="578.745"
+       y2="435.46701" /><linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient268"
+       id="linearGradient3397"
+       gradientUnits="userSpaceOnUse"
+       x1="359.427"
+       y1="435.46701"
+       x2="578.745"
+       y2="435.46701" /><linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient244"
+       id="linearGradient3399"
+       gradientUnits="userSpaceOnUse"
+       x1="349.82901"
+       y1="476.5835"
+       x2="580.33899"
+       y2="476.5835" /><linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient244"
+       id="linearGradient3401"
+       gradientUnits="userSpaceOnUse"
+       x1="349.82901"
+       y1="476.5835"
+       x2="580.33899"
+       y2="476.5835" /><linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient244"
+       id="linearGradient3403"
+       gradientUnits="userSpaceOnUse"
+       x1="349.82901"
+       y1="476.5835"
+       x2="580.33899"
+       y2="476.5835" /><linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient244"
+       id="linearGradient3405"
+       gradientUnits="userSpaceOnUse"
+       x1="349.82901"
+       y1="476.5835"
+       x2="580.33899"
+       y2="476.5835" /><linearGradient
+       x1="0"
+       y1="0"
+       x2="1"
+       y2="0"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(2.0458112,-116.57307,-116.57307,-2.0458112,481.4248,537.79492)"
+       spreadMethod="pad"
+       id="linearGradient10725-9"><stop
+         style="stop-opacity:1;stop-color:#f79c00"
+         offset="0"
+         id="stop10719-7" /><stop
+         style="stop-opacity:1;stop-color:#ff5b00"
+         offset="0.542105"
+         id="stop10721-2" /><stop
+         style="stop-opacity:1;stop-color:#ffae00"
+         offset="1"
+         id="stop10723-8" /></linearGradient><linearGradient
+       x1="0"
+       y1="0"
+       x2="1"
+       y2="0"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(2.0458114,-116.57308,-116.57308,-2.0458114,505.5918,538.21826)"
+       spreadMethod="pad"
+       id="linearGradient10759"><stop
+         style="stop-opacity:1;stop-color:#f79c00"
+         offset="0"
+         id="stop10753" /><stop
+         style="stop-opacity:1;stop-color:#ff5b00"
+         offset="0.542105"
+         id="stop10755" /><stop
+         style="stop-opacity:1;stop-color:#ffae00"
+         offset="1"
+         id="stop10757" /></linearGradient><linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient10793"
+       id="linearGradient1954"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(2.0458386,-116.57463,-116.57463,-2.0458386,457.48242,537.37598)"
+       x1="0"
+       y1="0"
+       x2="1"
+       y2="0"
+       spreadMethod="pad" /><linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient10793"
+       id="linearGradient2040"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(2.0458386,-116.57463,-116.57463,-2.0458386,457.48242,537.37598)"
+       x1="0"
+       y1="0"
+       x2="1"
+       y2="0"
+       spreadMethod="pad" /><clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath346-5"><path
+         d="M 0,1000 H 1000 V 0 H 0 Z"
+         id="path344-3"
+         inkscape:connector-curvature="0" /></clipPath><radialGradient
+       fx="0"
+       fy="0"
+       cx="0"
+       cy="0"
+       r="1"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(8.1565046,0,0,-8.1565046,612.09229,594.8092)"
+       spreadMethod="pad"
+       id="radialGradient336-7"><stop
+         style="stop-opacity:1;stop-color:#ffffff"
+         offset="0"
+         id="stop332-4" /><stop
+         style="stop-opacity:1;stop-color:#a6ce38"
+         offset="1"
+         id="stop334-3" /></radialGradient><clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath346-1"><path
+         d="M 0,1000 H 1000 V 0 H 0 Z"
+         id="path344-9"
+         inkscape:connector-curvature="0" /></clipPath><radialGradient
+       fx="0"
+       fy="0"
+       cx="0"
+       cy="0"
+       r="1"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(8.1565046,0,0,-8.1565046,612.09229,594.8092)"
+       spreadMethod="pad"
+       id="radialGradient336-0"><stop
+         style="stop-opacity:1;stop-color:#ffffff"
+         offset="0"
+         id="stop332-1" /><stop
+         style="stop-opacity:1;stop-color:#a6ce38"
+         offset="1"
+         id="stop334-4" /></radialGradient><clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath346-11"><path
+         d="M 0,1000 H 1000 V 0 H 0 Z"
+         id="path344-93"
+         inkscape:connector-curvature="0" /></clipPath><radialGradient
+       fx="0"
+       fy="0"
+       cx="0"
+       cy="0"
+       r="1"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(8.1565046,0,0,-8.1565046,612.09229,594.8092)"
+       spreadMethod="pad"
+       id="radialGradient336-6"><stop
+         style="stop-opacity:1;stop-color:#ffffff"
+         offset="0"
+         id="stop332-2" /><stop
+         style="stop-opacity:1;stop-color:#a6ce38"
+         offset="1"
+         id="stop334-9" /></radialGradient><clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath346-0"><path
+         d="M 0,1000 H 1000 V 0 H 0 Z"
+         id="path344-5"
+         inkscape:connector-curvature="0" /></clipPath><radialGradient
+       fx="0"
+       fy="0"
+       cx="0"
+       cy="0"
+       r="1"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(8.1565046,0,0,-8.1565046,612.09229,594.8092)"
+       spreadMethod="pad"
+       id="radialGradient336-07"><stop
+         style="stop-opacity:1;stop-color:#ffffff"
+         offset="0"
+         id="stop332-5" /><stop
+         style="stop-opacity:1;stop-color:#a6ce38"
+         offset="1"
+         id="stop334-2" /></radialGradient><clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath346-9"><path
+         d="M 0,1000 H 1000 V 0 H 0 Z"
+         id="path344-1"
+         inkscape:connector-curvature="0" /></clipPath><radialGradient
+       fx="0"
+       fy="0"
+       cx="0"
+       cy="0"
+       r="1"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(8.1565046,0,0,-8.1565046,612.09229,594.8092)"
+       spreadMethod="pad"
+       id="radialGradient336-8"><stop
+         style="stop-opacity:1;stop-color:#ffffff"
+         offset="0"
+         id="stop332-7" /><stop
+         style="stop-opacity:1;stop-color:#a6ce38"
+         offset="1"
+         id="stop334-8" /></radialGradient><clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath346-12"><path
+         d="M 0,1000 H 1000 V 0 H 0 Z"
+         id="path344-2"
+         inkscape:connector-curvature="0" /></clipPath><radialGradient
+       fx="0"
+       fy="0"
+       cx="0"
+       cy="0"
+       r="1"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(8.1565046,0,0,-8.1565046,612.09229,594.8092)"
+       spreadMethod="pad"
+       id="radialGradient336-1"><stop
+         style="stop-opacity:1;stop-color:#ffffff"
+         offset="0"
+         id="stop332-79" /><stop
+         style="stop-opacity:1;stop-color:#a6ce38"
+         offset="1"
+         id="stop334-0" /></radialGradient><clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath346-7"><path
+         d="M 0,1000 H 1000 V 0 H 0 Z"
+         id="path344-6"
+         inkscape:connector-curvature="0" /></clipPath><radialGradient
+       fx="0"
+       fy="0"
+       cx="0"
+       cy="0"
+       r="1"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(8.1565046,0,0,-8.1565046,612.09229,594.8092)"
+       spreadMethod="pad"
+       id="radialGradient336-74"><stop
+         style="stop-opacity:1;stop-color:#ffffff"
+         offset="0"
+         id="stop332-9" /><stop
+         style="stop-opacity:1;stop-color:#a6ce38"
+         offset="1"
+         id="stop334-42" /></radialGradient><clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath346-00"><path
+         d="M 0,1000 H 1000 V 0 H 0 Z"
+         id="path344-61"
+         inkscape:connector-curvature="0" /></clipPath><radialGradient
+       fx="0"
+       fy="0"
+       cx="0"
+       cy="0"
+       r="1"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(8.1565046,0,0,-8.1565046,612.09229,594.8092)"
+       spreadMethod="pad"
+       id="radialGradient336-66"><stop
+         style="stop-opacity:1;stop-color:#ffffff"
+         offset="0"
+         id="stop332-8" /><stop
+         style="stop-opacity:1;stop-color:#a6ce38"
+         offset="1"
+         id="stop334-5" /></radialGradient><clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath346-3"><path
+         d="M 0,1000 H 1000 V 0 H 0 Z"
+         id="path344-26"
+         inkscape:connector-curvature="0" /></clipPath><radialGradient
+       fx="0"
+       fy="0"
+       cx="0"
+       cy="0"
+       r="1"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(8.1565046,0,0,-8.1565046,612.09229,594.8092)"
+       spreadMethod="pad"
+       id="radialGradient336-4"><stop
+         style="stop-opacity:1;stop-color:#ffffff"
+         offset="0"
+         id="stop332-48" /><stop
+         style="stop-opacity:1;stop-color:#a6ce38"
+         offset="1"
+         id="stop334-97" /></radialGradient><clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath346-6"><path
+         d="M 0,1000 H 1000 V 0 H 0 Z"
+         id="path344-37"
+         inkscape:connector-curvature="0" /></clipPath><radialGradient
+       fx="0"
+       fy="0"
+       cx="0"
+       cy="0"
+       r="1"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(8.1565046,0,0,-8.1565046,612.09229,594.8092)"
+       spreadMethod="pad"
+       id="radialGradient336-70"><stop
+         style="stop-opacity:1;stop-color:#ffffff"
+         offset="0"
+         id="stop332-0" /><stop
+         style="stop-opacity:1;stop-color:#a6ce38"
+         offset="1"
+         id="stop334-92" /></radialGradient><clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath346-55"><path
+         d="M 0,1000 H 1000 V 0 H 0 Z"
+         id="path344-36"
+         inkscape:connector-curvature="0" /></clipPath><radialGradient
+       fx="0"
+       fy="0"
+       cx="0"
+       cy="0"
+       r="1"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(8.1565046,0,0,-8.1565046,612.09229,594.8092)"
+       spreadMethod="pad"
+       id="radialGradient336-71"><stop
+         style="stop-opacity:1;stop-color:#ffffff"
+         offset="0"
+         id="stop332-3" /><stop
+         style="stop-opacity:1;stop-color:#a6ce38"
+         offset="1"
+         id="stop334-6" /></radialGradient><clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath346-03"><path
+         d="M 0,1000 H 1000 V 0 H 0 Z"
+         id="path344-0"
+         inkscape:connector-curvature="0" /></clipPath><radialGradient
+       fx="0"
+       fy="0"
+       cx="0"
+       cy="0"
+       r="1"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(8.1565046,0,0,-8.1565046,612.09229,594.8092)"
+       spreadMethod="pad"
+       id="radialGradient336-2"><stop
+         style="stop-opacity:1;stop-color:#ffffff"
+         offset="0"
+         id="stop332-99" /><stop
+         style="stop-opacity:1;stop-color:#a6ce38"
+         offset="1"
+         id="stop334-41" /></radialGradient><clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath346-57"><path
+         d="M 0,1000 H 1000 V 0 H 0 Z"
+         id="path344-02"
+         inkscape:connector-curvature="0" /></clipPath><radialGradient
+       fx="0"
+       fy="0"
+       cx="0"
+       cy="0"
+       r="1"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(8.1565046,0,0,-8.1565046,612.09229,594.8092)"
+       spreadMethod="pad"
+       id="radialGradient336-68"><stop
+         style="stop-opacity:1;stop-color:#ffffff"
+         offset="0"
+         id="stop332-15" /><stop
+         style="stop-opacity:1;stop-color:#a6ce38"
+         offset="1"
+         id="stop334-89" /></radialGradient><clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath346-53"><path
+         d="M 0,1000 H 1000 V 0 H 0 Z"
+         id="path344-8"
+         inkscape:connector-curvature="0" /></clipPath><radialGradient
+       fx="0"
+       fy="0"
+       cx="0"
+       cy="0"
+       r="1"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(8.1565046,0,0,-8.1565046,612.09229,594.8092)"
+       spreadMethod="pad"
+       id="radialGradient336-84"><stop
+         style="stop-opacity:1;stop-color:#ffffff"
+         offset="0"
+         id="stop332-791" /><stop
+         style="stop-opacity:1;stop-color:#a6ce38"
+         offset="1"
+         id="stop334-7" /></radialGradient><clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath346-4"><path
+         d="M 0,1000 H 1000 V 0 H 0 Z"
+         id="path344-7"
+         inkscape:connector-curvature="0" /></clipPath><radialGradient
+       fx="0"
+       fy="0"
+       cx="0"
+       cy="0"
+       r="1"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(8.1565046,0,0,-8.1565046,612.09229,594.8092)"
+       spreadMethod="pad"
+       id="radialGradient336-73"><stop
+         style="stop-opacity:1;stop-color:#ffffff"
+         offset="0"
+         id="stop332-34" /><stop
+         style="stop-opacity:1;stop-color:#a6ce38"
+         offset="1"
+         id="stop334-90" /></radialGradient><clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath346-59"><path
+         d="M 0,1000 H 1000 V 0 H 0 Z"
+         id="path344-24"
+         inkscape:connector-curvature="0" /></clipPath><radialGradient
+       fx="0"
+       fy="0"
+       cx="0"
+       cy="0"
+       r="1"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(8.1565046,0,0,-8.1565046,612.09229,594.8092)"
+       spreadMethod="pad"
+       id="radialGradient336-89"><stop
+         style="stop-opacity:1;stop-color:#ffffff"
+         offset="0"
+         id="stop332-6" /><stop
+         style="stop-opacity:1;stop-color:#a6ce38"
+         offset="1"
+         id="stop334-87" /></radialGradient><clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath346-8"><path
+         d="M 0,1000 H 1000 V 0 H 0 Z"
+         id="path344-4"
+         inkscape:connector-curvature="0" /></clipPath><radialGradient
+       fx="0"
+       fy="0"
+       cx="0"
+       cy="0"
+       r="1"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(8.1565046,0,0,-8.1565046,612.09229,594.8092)"
+       spreadMethod="pad"
+       id="radialGradient336-5"><stop
+         style="stop-opacity:1;stop-color:#ffffff"
+         offset="0"
+         id="stop332-87" /><stop
+         style="stop-opacity:1;stop-color:#a6ce38"
+         offset="1"
+         id="stop334-54" /></radialGradient><rect
+       x="16.83798"
+       y="84.457169"
+       width="165.70711"
+       height="106.37327"
+       id="rect835" /><rect
+       x="16.83798"
+       y="84.457169"
+       width="165.70711"
+       height="106.37327"
+       id="rect848" /></defs><sodipodi:namedview
+     pagecolor="#343434"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1920"
+     inkscape:window-height="1094"
+     id="namedview4"
+     showgrid="false"
+     fit-margin-top="0"
+     fit-margin-left="0"
+     fit-margin-right="0"
+     fit-margin-bottom="0"
+     inkscape:zoom="0.50063166"
+     inkscape:cx="693.86143"
+     inkscape:cy="185.07083"
+     inkscape:window-x="-11"
+     inkscape:window-y="-11"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="g10"
+     inkscape:document-rotation="0" /><g
+     id="g10"
+     inkscape:groupmode="layer"
+     inkscape:label="Free Natural Water &amp; Leaf Logo"
+     transform="matrix(1.3333333,0,0,-1.3333333,-425.82495,1148.7508)"><g
+       transform="matrix(3.3924064,0,0,3.3924064,-929.92066,-433.16548)"
+       id="g10707-3"><g
+         id="g10709-2"><g
+           id="g10715-0"><g
+             id="g10717-6" /></g></g></g><g
+       id="g1727"
+       transform="matrix(0.35118299,0,0,0.35118299,207.21186,558.99679)"><g
+         id="g64"
+         transform="translate(2.3112026,2.7287708)"
+         style="fill:url(#linearGradient3300);fill-opacity:1"><g
+           id="g66"
+           style="fill:url(#linearGradient3389);fill-opacity:1"><g
+             id="g72"
+             style="fill:url(#linearGradient3387);fill-opacity:1"><g
+               id="g74"
+               style="fill:url(#linearGradient3385);fill-opacity:1"><path
+                 d="m 357.262,365.864 c 30.696,-39.504 112.281,-95.572 212.54,-58.76 v 0 c 3.608,1.352 7.12,2.79 10.537,4.309 v 0 c 0,0 -4.998,53.97 -47.32,101.87 v 0 c -42.323,47.9 -95.472,71.522 -125.657,106.299 v 0 c -30.183,34.777 -48.094,71.976 -48.094,122.172 v 0 C 283.068,514.396 327.52,404.14 357.262,365.864"
+                 style="fill:url(#linearGradient3383);fill-opacity:1;stroke:none"
+                 id="path82"
+                 inkscape:connector-curvature="0" /></g></g></g></g><g
+         id="g84"
+         transform="translate(2.3112026,2.7287708)"
+         style="fill:url(#linearGradient3308);fill-opacity:1"><g
+           id="g86"
+           style="fill:url(#linearGradient3397);fill-opacity:1"><g
+             id="g92"
+             style="fill:url(#linearGradient3395);fill-opacity:1"><g
+               id="g94"
+               style="fill:url(#linearGradient3393);fill-opacity:1"><path
+                 d="m 359.427,478.45 c 0,-91.617 74.27,-165.887 165.887,-165.887 v 0 c 18.694,0 36.665,3.095 53.431,8.795 v 0 c -3.551,17.787 -14.437,56.512 -45.726,91.925 v 0 c -42.323,47.9 -95.472,71.522 -125.657,106.299 v 0 c -10.809,12.456 -20.035,25.226 -27.438,38.789 v 0 c -13.06,-23.706 -20.497,-50.944 -20.497,-79.921"
+                 style="fill:url(#linearGradient3391);fill-opacity:1;stroke:none"
+                 id="path110"
+                 inkscape:connector-curvature="0" /></g></g></g></g><g
+         id="g112"
+         transform="translate(2.3112026,2.7287708)"
+         style="fill:url(#linearGradient3316);fill-opacity:1"><g
+           id="g114"
+           style="fill:url(#linearGradient3405);fill-opacity:1"><g
+             id="g120"
+             style="fill:url(#linearGradient3403);fill-opacity:1"><g
+               id="g122"
+               style="fill:url(#linearGradient3401);fill-opacity:1"><path
+                 d="m 349.829,624.894 c 2.201,-66.212 41.131,-115.337 94.552,-152.201 v 0 c 77.097,-48.842 122.678,-96.799 135.958,-161.28 v 0 c -0.002,0.011 -5.002,53.974 -47.32,101.87 v 0 c -42.323,47.9 -95.472,71.522 -125.657,106.299 v 0 c -30.183,34.777 -48.094,71.976 -48.094,122.172 v 0 c -3.384,-5.656 -6.518,-11.276 -9.439,-16.86"
+                 style="fill:url(#linearGradient3399);fill-opacity:1;stroke:none"
+                 id="path130"
+                 inkscape:connector-curvature="0" /></g></g></g></g><g
+         transform="matrix(5.2653498,0.1925542,-0.1925542,5.2653498,-1829.6205,-1960.5381)"
+         id="g10775-0-6"><g
+           id="g10777-3-8"><g
+             id="g10783-06-3"><g
+               id="g10785-0-1"><path
+                 d="m 444.876,458.441 c 5.091,-6.891 13.535,-10.484 20.015,-16.027 v 0 c 7.019,-6.005 10.32637,-16.06825 10.32637,-23.60425 v 0 c 9.132,17.176 1.79663,29.77425 -10.30837,43.19125 v 0 c -11.451,12.691 -20.32545,24.21525 -13.67745,40.55325 v 0 C 440.53055,490.77225 433.078,474.408 444.876,458.441"
+                 style="fill:url(#linearGradient1954);stroke:none"
+                 id="path10795-7-2"
+                 inkscape:connector-curvature="0"
+                 sodipodi:nodetypes="cccccccccc" /></g></g></g></g><g
+         transform="matrix(5.2147927,-0.75294258,0.75294258,5.2147927,-2171.3998,-1419.4017)"
+         id="g10775-0-6-2"><g
+           id="g10777-3-8-1"><g
+             id="g10783-06-3-8"><g
+               id="g10785-0-1-1"><path
+                 d="m 444.876,458.441 c 5.091,-6.891 13.535,-10.484 20.015,-16.027 v 0 c 7.019,-6.005 10.32637,-16.06825 10.32637,-23.60425 v 0 c 9.132,17.176 1.79663,29.77425 -10.30837,43.19125 v 0 c -11.451,12.691 -20.32545,24.21525 -13.67745,40.55325 v 0 C 440.53055,490.77225 433.078,474.408 444.876,458.441"
+                 style="fill:url(#linearGradient2040);stroke:none"
+                 id="path10795-7-2-1"
+                 inkscape:connector-curvature="0"
+                 sodipodi:nodetypes="cccccccccc" /></g></g></g></g><g
+         id="g320"
+         transform="translate(-59.086268,-261.78682)"><g
+           id="g322"><g
+             id="g328"><g
+               id="g330"><path
+                 d="m 597.631,595.763 c 2.049,-21.116 42.65,-9.236 13.822,6.899 v 0 c -1.206,0.675 -2.77,1.009 -4.411,1.009 v 0 c -4.65,0 -9.919,-2.683 -9.411,-7.908"
+                 style="fill:url(#radialGradient336-7);stroke:none"
+                 id="path338"
+                 inkscape:connector-curvature="0" /></g></g></g></g><g
+         id="g340"
+         transform="translate(-59.086268,-261.78682)"><g
+           id="g342"
+           clip-path="url(#clipPath346-5)"><g
+             id="g348"
+             transform="translate(618.419,597.6913)"><path
+               d="m 0,0 c 12.223,-11.924 -12.638,-19.168 -19.29,-7.01 0.137,-0.206 4.762,-6.999 13.682,-4.775 C 3.434,-9.53 3.35,-4.453 0,0"
+               style="fill:#008845;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path350"
+               inkscape:connector-curvature="0" /></g><g
+             id="g352"
+             transform="translate(617.6553,592.3096)"><path
+               d="m 0,0 c 0,0 2.26,2.019 -1.239,5.759 -3.499,3.741 -9.138,5.554 -12.757,3.282 -3.619,-2.273 -1.794,-6.134 0.847,-4.47 2.64,1.664 6.33,0.471 6.003,-1.055 -0.328,-1.526 -2.955,-2.645 -1.409,-4.925 1.545,-2.28 6.858,-1.284 7.706,0.063 z"
+               style="fill:#dae79c;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path354"
+               inkscape:connector-curvature="0" /></g><g
+             id="g356"
+             transform="translate(612.5725,599.311)"><path
+               d="M 0,0 C -1.751,1.832 -4.351,2.794 -6.25,1.862 -8.375,0.82 -7.594,-1.412 -6.043,-0.648 -5.346,-0.305 -1.88,-0.333 -2.95,-1.87 -2.298,-1.877 -0.388,-0.425 0,0"
+               style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path358"
+               inkscape:connector-curvature="0" /></g><g
+             id="g360"
+             transform="translate(616.0858,593.34)"><path
+               d="M 0,0 C 1.119,0.805 0.156,2.591 -0.388,3.292 -1.409,2.36 -3.343,0.919 -4.858,0.896 -5.88,-1.089 -1.544,-1.961 -0.545,-0.693 Z"
+               style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path362"
+               inkscape:connector-curvature="0" /></g><g
+             id="g364"
+             transform="translate(614.7651,587.9883)"><path
+               d="m 0,0 c 0,0 -3.414,-3.185 -8.948,0.409 -5.534,3.593 -5.9,5.948 -6.461,7.892 -0.561,1.945 -2.206,-0.811 0.479,-4.809 2.687,-3.999 5.606,-4.249 8.329,-5.089 C -3.878,-2.436 0,0 0,0"
+               style="fill:#53ba62;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path366"
+               inkscape:connector-curvature="0" /></g></g></g><g
+         id="g320-8"
+         transform="translate(-77.812612,-223.58507)"><g
+           id="g322-7"><g
+             id="g328-6"><g
+               id="g330-3"><path
+                 d="m 597.631,595.763 c 2.049,-21.116 42.65,-9.236 13.822,6.899 v 0 c -1.206,0.675 -2.77,1.009 -4.411,1.009 v 0 c -4.65,0 -9.919,-2.683 -9.411,-7.908"
+                 style="fill:url(#radialGradient336-0);stroke:none"
+                 id="path338-3"
+                 inkscape:connector-curvature="0" /></g></g></g></g><g
+         id="g340-7"
+         transform="translate(-77.812612,-223.58507)"><g
+           id="g342-7"
+           clip-path="url(#clipPath346-1)"><g
+             id="g348-0"
+             transform="translate(618.419,597.6913)"><path
+               d="m 0,0 c 12.223,-11.924 -12.638,-19.168 -19.29,-7.01 0.137,-0.206 4.762,-6.999 13.682,-4.775 C 3.434,-9.53 3.35,-4.453 0,0"
+               style="fill:#008845;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path350-3"
+               inkscape:connector-curvature="0" /></g><g
+             id="g352-3"
+             transform="translate(617.6553,592.3096)"><path
+               d="m 0,0 c 0,0 2.26,2.019 -1.239,5.759 -3.499,3.741 -9.138,5.554 -12.757,3.282 -3.619,-2.273 -1.794,-6.134 0.847,-4.47 2.64,1.664 6.33,0.471 6.003,-1.055 -0.328,-1.526 -2.955,-2.645 -1.409,-4.925 1.545,-2.28 6.858,-1.284 7.706,0.063 z"
+               style="fill:#dae79c;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path354-1"
+               inkscape:connector-curvature="0" /></g><g
+             id="g356-2"
+             transform="translate(612.5725,599.311)"><path
+               d="M 0,0 C -1.751,1.832 -4.351,2.794 -6.25,1.862 -8.375,0.82 -7.594,-1.412 -6.043,-0.648 -5.346,-0.305 -1.88,-0.333 -2.95,-1.87 -2.298,-1.877 -0.388,-0.425 0,0"
+               style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path358-7"
+               inkscape:connector-curvature="0" /></g><g
+             id="g360-5"
+             transform="translate(616.0858,593.34)"><path
+               d="M 0,0 C 1.119,0.805 0.156,2.591 -0.388,3.292 -1.409,2.36 -3.343,0.919 -4.858,0.896 -5.88,-1.089 -1.544,-1.961 -0.545,-0.693 Z"
+               style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path362-1"
+               inkscape:connector-curvature="0" /></g><g
+             id="g364-6"
+             transform="translate(614.7651,587.9883)"><path
+               d="m 0,0 c 0,0 -3.414,-3.185 -8.948,0.409 -5.534,3.593 -5.9,5.948 -6.461,7.892 -0.561,1.945 -2.206,-0.811 0.479,-4.809 2.687,-3.999 5.606,-4.249 8.329,-5.089 C -3.878,-2.436 0,0 0,0"
+               style="fill:#53ba62;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path366-4"
+               inkscape:connector-curvature="0" /></g></g></g><g
+         id="g320-0"
+         transform="translate(-102.53138,-273.02262)"><g
+           id="g322-6"><g
+             id="g328-9"><g
+               id="g330-5"><path
+                 d="m 597.631,595.763 c 2.049,-21.116 42.65,-9.236 13.822,6.899 v 0 c -1.206,0.675 -2.77,1.009 -4.411,1.009 v 0 c -4.65,0 -9.919,-2.683 -9.411,-7.908"
+                 style="fill:url(#radialGradient336-6);stroke:none"
+                 id="path338-7"
+                 inkscape:connector-curvature="0" /></g></g></g></g><g
+         id="g340-1"
+         transform="translate(-102.53138,-273.02262)"><g
+           id="g342-79"
+           clip-path="url(#clipPath346-11)"><g
+             id="g348-4"
+             transform="translate(618.419,597.6913)"><path
+               d="m 0,0 c 12.223,-11.924 -12.638,-19.168 -19.29,-7.01 0.137,-0.206 4.762,-6.999 13.682,-4.775 C 3.434,-9.53 3.35,-4.453 0,0"
+               style="fill:#008845;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path350-9"
+               inkscape:connector-curvature="0" /></g><g
+             id="g352-4"
+             transform="translate(617.6553,592.3096)"><path
+               d="m 0,0 c 0,0 2.26,2.019 -1.239,5.759 -3.499,3.741 -9.138,5.554 -12.757,3.282 -3.619,-2.273 -1.794,-6.134 0.847,-4.47 2.64,1.664 6.33,0.471 6.003,-1.055 -0.328,-1.526 -2.955,-2.645 -1.409,-4.925 1.545,-2.28 6.858,-1.284 7.706,0.063 z"
+               style="fill:#dae79c;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path354-5"
+               inkscape:connector-curvature="0" /></g><g
+             id="g356-3"
+             transform="translate(612.5725,599.311)"><path
+               d="M 0,0 C -1.751,1.832 -4.351,2.794 -6.25,1.862 -8.375,0.82 -7.594,-1.412 -6.043,-0.648 -5.346,-0.305 -1.88,-0.333 -2.95,-1.87 -2.298,-1.877 -0.388,-0.425 0,0"
+               style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path358-72"
+               inkscape:connector-curvature="0" /></g><g
+             id="g360-9"
+             transform="translate(616.0858,593.34)"><path
+               d="M 0,0 C 1.119,0.805 0.156,2.591 -0.388,3.292 -1.409,2.36 -3.343,0.919 -4.858,0.896 -5.88,-1.089 -1.544,-1.961 -0.545,-0.693 Z"
+               style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path362-3"
+               inkscape:connector-curvature="0" /></g><g
+             id="g364-62"
+             transform="translate(614.7651,587.9883)"><path
+               d="m 0,0 c 0,0 -3.414,-3.185 -8.948,0.409 -5.534,3.593 -5.9,5.948 -6.461,7.892 -0.561,1.945 -2.206,-0.811 0.479,-4.809 2.687,-3.999 5.606,-4.249 8.329,-5.089 C -3.878,-2.436 0,0 0,0"
+               style="fill:#53ba62;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path366-8"
+               inkscape:connector-curvature="0" /></g></g></g><g
+         id="g320-6"
+         transform="translate(-116.76341,-229.5775)"><g
+           id="g322-5"><g
+             id="g328-68"><g
+               id="g330-37"><path
+                 d="m 597.631,595.763 c 2.049,-21.116 42.65,-9.236 13.822,6.899 v 0 c -1.206,0.675 -2.77,1.009 -4.411,1.009 v 0 c -4.65,0 -9.919,-2.683 -9.411,-7.908"
+                 style="fill:url(#radialGradient336-07);stroke:none"
+                 id="path338-5"
+                 inkscape:connector-curvature="0" /></g></g></g></g><g
+         id="g340-75"
+         transform="translate(-116.76341,-229.5775)"><g
+           id="g342-6"
+           clip-path="url(#clipPath346-0)"><g
+             id="g348-5"
+             transform="translate(618.419,597.6913)"><path
+               d="m 0,0 c 12.223,-11.924 -12.638,-19.168 -19.29,-7.01 0.137,-0.206 4.762,-6.999 13.682,-4.775 C 3.434,-9.53 3.35,-4.453 0,0"
+               style="fill:#008845;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path350-5"
+               inkscape:connector-curvature="0" /></g><g
+             id="g352-5"
+             transform="translate(617.6553,592.3096)"><path
+               d="m 0,0 c 0,0 2.26,2.019 -1.239,5.759 -3.499,3.741 -9.138,5.554 -12.757,3.282 -3.619,-2.273 -1.794,-6.134 0.847,-4.47 2.64,1.664 6.33,0.471 6.003,-1.055 -0.328,-1.526 -2.955,-2.645 -1.409,-4.925 1.545,-2.28 6.858,-1.284 7.706,0.063 z"
+               style="fill:#dae79c;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path354-10"
+               inkscape:connector-curvature="0" /></g><g
+             id="g356-5"
+             transform="translate(612.5725,599.311)"><path
+               d="M 0,0 C -1.751,1.832 -4.351,2.794 -6.25,1.862 -8.375,0.82 -7.594,-1.412 -6.043,-0.648 -5.346,-0.305 -1.88,-0.333 -2.95,-1.87 -2.298,-1.877 -0.388,-0.425 0,0"
+               style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path358-8"
+               inkscape:connector-curvature="0" /></g><g
+             id="g360-1"
+             transform="translate(616.0858,593.34)"><path
+               d="M 0,0 C 1.119,0.805 0.156,2.591 -0.388,3.292 -1.409,2.36 -3.343,0.919 -4.858,0.896 -5.88,-1.089 -1.544,-1.961 -0.545,-0.693 Z"
+               style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path362-7"
+               inkscape:connector-curvature="0" /></g><g
+             id="g364-9"
+             transform="translate(614.7651,587.9883)"><path
+               d="m 0,0 c 0,0 -3.414,-3.185 -8.948,0.409 -5.534,3.593 -5.9,5.948 -6.461,7.892 -0.561,1.945 -2.206,-0.811 0.479,-4.809 2.687,-3.999 5.606,-4.249 8.329,-5.089 C -3.878,-2.436 0,0 0,0"
+               style="fill:#53ba62;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path366-5"
+               inkscape:connector-curvature="0" /></g></g></g><g
+         id="g320-9"
+         transform="translate(-110.77098,-187.63049)"><g
+           id="g322-1"><g
+             id="g328-3"><g
+               id="g330-7"><path
+                 d="m 597.631,595.763 c 2.049,-21.116 42.65,-9.236 13.822,6.899 v 0 c -1.206,0.675 -2.77,1.009 -4.411,1.009 v 0 c -4.65,0 -9.919,-2.683 -9.411,-7.908"
+                 style="fill:url(#radialGradient336-8);stroke:none"
+                 id="path338-70"
+                 inkscape:connector-curvature="0" /></g></g></g></g><g
+         id="g340-71"
+         transform="translate(-110.77098,-187.63049)"><g
+           id="g342-3"
+           clip-path="url(#clipPath346-9)"><g
+             id="g348-2"
+             transform="translate(618.419,597.6913)"><path
+               d="m 0,0 c 12.223,-11.924 -12.638,-19.168 -19.29,-7.01 0.137,-0.206 4.762,-6.999 13.682,-4.775 C 3.434,-9.53 3.35,-4.453 0,0"
+               style="fill:#008845;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path350-98"
+               inkscape:connector-curvature="0" /></g><g
+             id="g352-7"
+             transform="translate(617.6553,592.3096)"><path
+               d="m 0,0 c 0,0 2.26,2.019 -1.239,5.759 -3.499,3.741 -9.138,5.554 -12.757,3.282 -3.619,-2.273 -1.794,-6.134 0.847,-4.47 2.64,1.664 6.33,0.471 6.003,-1.055 -0.328,-1.526 -2.955,-2.645 -1.409,-4.925 1.545,-2.28 6.858,-1.284 7.706,0.063 z"
+               style="fill:#dae79c;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path354-56"
+               inkscape:connector-curvature="0" /></g><g
+             id="g356-8"
+             transform="translate(612.5725,599.311)"><path
+               d="M 0,0 C -1.751,1.832 -4.351,2.794 -6.25,1.862 -8.375,0.82 -7.594,-1.412 -6.043,-0.648 -5.346,-0.305 -1.88,-0.333 -2.95,-1.87 -2.298,-1.877 -0.388,-0.425 0,0"
+               style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path358-0"
+               inkscape:connector-curvature="0" /></g><g
+             id="g360-15"
+             transform="translate(616.0858,593.34)"><path
+               d="M 0,0 C 1.119,0.805 0.156,2.591 -0.388,3.292 -1.409,2.36 -3.343,0.919 -4.858,0.896 -5.88,-1.089 -1.544,-1.961 -0.545,-0.693 Z"
+               style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path362-6"
+               inkscape:connector-curvature="0" /></g><g
+             id="g364-2"
+             transform="translate(614.7651,587.9883)"><path
+               d="m 0,0 c 0,0 -3.414,-3.185 -8.948,0.409 -5.534,3.593 -5.9,5.948 -6.461,7.892 -0.561,1.945 -2.206,-0.811 0.479,-4.809 2.687,-3.999 5.606,-4.249 8.329,-5.089 C -3.878,-2.436 0,0 0,0"
+               style="fill:#53ba62;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path366-83"
+               inkscape:connector-curvature="0" /></g></g></g><g
+         id="g320-4"
+         transform="translate(-154.96515,-193.62292)"><g
+           id="g322-8"><g
+             id="g328-30"><g
+               id="g330-36"><path
+                 d="m 597.631,595.763 c 2.049,-21.116 42.65,-9.236 13.822,6.899 v 0 c -1.206,0.675 -2.77,1.009 -4.411,1.009 v 0 c -4.65,0 -9.919,-2.683 -9.411,-7.908"
+                 style="fill:url(#radialGradient336-1);stroke:none"
+                 id="path338-0"
+                 inkscape:connector-curvature="0" /></g></g></g></g><g
+         id="g340-5"
+         transform="translate(-154.96515,-193.62292)"><g
+           id="g342-33"
+           clip-path="url(#clipPath346-12)"><g
+             id="g348-24"
+             transform="translate(618.419,597.6913)"><path
+               d="m 0,0 c 12.223,-11.924 -12.638,-19.168 -19.29,-7.01 0.137,-0.206 4.762,-6.999 13.682,-4.775 C 3.434,-9.53 3.35,-4.453 0,0"
+               style="fill:#008845;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path350-4"
+               inkscape:connector-curvature="0" /></g><g
+             id="g352-37"
+             transform="translate(617.6553,592.3096)"><path
+               d="m 0,0 c 0,0 2.26,2.019 -1.239,5.759 -3.499,3.741 -9.138,5.554 -12.757,3.282 -3.619,-2.273 -1.794,-6.134 0.847,-4.47 2.64,1.664 6.33,0.471 6.003,-1.055 -0.328,-1.526 -2.955,-2.645 -1.409,-4.925 1.545,-2.28 6.858,-1.284 7.706,0.063 z"
+               style="fill:#dae79c;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path354-3"
+               inkscape:connector-curvature="0" /></g><g
+             id="g356-24"
+             transform="translate(612.5725,599.311)"><path
+               d="M 0,0 C -1.751,1.832 -4.351,2.794 -6.25,1.862 -8.375,0.82 -7.594,-1.412 -6.043,-0.648 -5.346,-0.305 -1.88,-0.333 -2.95,-1.87 -2.298,-1.877 -0.388,-0.425 0,0"
+               style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path358-76"
+               inkscape:connector-curvature="0" /></g><g
+             id="g360-8"
+             transform="translate(616.0858,593.34)"><path
+               d="M 0,0 C 1.119,0.805 0.156,2.591 -0.388,3.292 -1.409,2.36 -3.343,0.919 -4.858,0.896 -5.88,-1.089 -1.544,-1.961 -0.545,-0.693 Z"
+               style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path362-8"
+               inkscape:connector-curvature="0" /></g><g
+             id="g364-0"
+             transform="translate(614.7651,587.9883)"><path
+               d="m 0,0 c 0,0 -3.414,-3.185 -8.948,0.409 -5.534,3.593 -5.9,5.948 -6.461,7.892 -0.561,1.945 -2.206,-0.811 0.479,-4.809 2.687,-3.999 5.606,-4.249 8.329,-5.089 C -3.878,-2.436 0,0 0,0"
+               style="fill:#53ba62;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path366-40"
+               inkscape:connector-curvature="0" /></g></g></g><g
+         id="g320-68"
+         transform="translate(-164.70285,-229.5775)"><g
+           id="g322-4"><g
+             id="g328-34"><g
+               id="g330-54"><path
+                 d="m 597.631,595.763 c 2.049,-21.116 42.65,-9.236 13.822,6.899 v 0 c -1.206,0.675 -2.77,1.009 -4.411,1.009 v 0 c -4.65,0 -9.919,-2.683 -9.411,-7.908"
+                 style="fill:url(#radialGradient336-74);stroke:none"
+                 id="path338-01"
+                 inkscape:connector-curvature="0" /></g></g></g></g><g
+         id="g340-4"
+         transform="translate(-164.70285,-229.5775)"><g
+           id="g342-36"
+           clip-path="url(#clipPath346-7)"><g
+             id="g348-1"
+             transform="translate(618.419,597.6913)"><path
+               d="m 0,0 c 12.223,-11.924 -12.638,-19.168 -19.29,-7.01 0.137,-0.206 4.762,-6.999 13.682,-4.775 C 3.434,-9.53 3.35,-4.453 0,0"
+               style="fill:#008845;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path350-8"
+               inkscape:connector-curvature="0" /></g><g
+             id="g352-70"
+             transform="translate(617.6553,592.3096)"><path
+               d="m 0,0 c 0,0 2.26,2.019 -1.239,5.759 -3.499,3.741 -9.138,5.554 -12.757,3.282 -3.619,-2.273 -1.794,-6.134 0.847,-4.47 2.64,1.664 6.33,0.471 6.003,-1.055 -0.328,-1.526 -2.955,-2.645 -1.409,-4.925 1.545,-2.28 6.858,-1.284 7.706,0.063 z"
+               style="fill:#dae79c;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path354-33"
+               inkscape:connector-curvature="0" /></g><g
+             id="g356-0"
+             transform="translate(612.5725,599.311)"><path
+               d="M 0,0 C -1.751,1.832 -4.351,2.794 -6.25,1.862 -8.375,0.82 -7.594,-1.412 -6.043,-0.648 -5.346,-0.305 -1.88,-0.333 -2.95,-1.87 -2.298,-1.877 -0.388,-0.425 0,0"
+               style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path358-2"
+               inkscape:connector-curvature="0" /></g><g
+             id="g360-2"
+             transform="translate(616.0858,593.34)"><path
+               d="M 0,0 C 1.119,0.805 0.156,2.591 -0.388,3.292 -1.409,2.36 -3.343,0.919 -4.858,0.896 -5.88,-1.089 -1.544,-1.961 -0.545,-0.693 Z"
+               style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path362-4"
+               inkscape:connector-curvature="0" /></g><g
+             id="g364-5"
+             transform="translate(614.7651,587.9883)"><path
+               d="m 0,0 c 0,0 -3.414,-3.185 -8.948,0.409 -5.534,3.593 -5.9,5.948 -6.461,7.892 -0.561,1.945 -2.206,-0.811 0.479,-4.809 2.687,-3.999 5.606,-4.249 8.329,-5.089 C -3.878,-2.436 0,0 0,0"
+               style="fill:#53ba62;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path366-2"
+               inkscape:connector-curvature="0" /></g></g></g><g
+         id="g320-1"
+         transform="translate(-158.71042,-265.53208)"><g
+           id="g322-81"><g
+             id="g328-2"><g
+               id="g330-4"><path
+                 d="m 597.631,595.763 c 2.049,-21.116 42.65,-9.236 13.822,6.899 v 0 c -1.206,0.675 -2.77,1.009 -4.411,1.009 v 0 c -4.65,0 -9.919,-2.683 -9.411,-7.908"
+                 style="fill:url(#radialGradient336-66);stroke:none"
+                 id="path338-33"
+                 inkscape:connector-curvature="0" /></g></g></g></g><g
+         id="g340-3"
+         transform="translate(-158.71042,-265.53208)"><g
+           id="g342-8"
+           clip-path="url(#clipPath346-00)"><g
+             id="g348-8"
+             transform="translate(618.419,597.6913)"><path
+               d="m 0,0 c 12.223,-11.924 -12.638,-19.168 -19.29,-7.01 0.137,-0.206 4.762,-6.999 13.682,-4.775 C 3.434,-9.53 3.35,-4.453 0,0"
+               style="fill:#008845;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path350-1"
+               inkscape:connector-curvature="0" /></g><g
+             id="g352-73"
+             transform="translate(617.6553,592.3096)"><path
+               d="m 0,0 c 0,0 2.26,2.019 -1.239,5.759 -3.499,3.741 -9.138,5.554 -12.757,3.282 -3.619,-2.273 -1.794,-6.134 0.847,-4.47 2.64,1.664 6.33,0.471 6.003,-1.055 -0.328,-1.526 -2.955,-2.645 -1.409,-4.925 1.545,-2.28 6.858,-1.284 7.706,0.063 z"
+               style="fill:#dae79c;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path354-4"
+               inkscape:connector-curvature="0" /></g><g
+             id="g356-03"
+             transform="translate(612.5725,599.311)"><path
+               d="M 0,0 C -1.751,1.832 -4.351,2.794 -6.25,1.862 -8.375,0.82 -7.594,-1.412 -6.043,-0.648 -5.346,-0.305 -1.88,-0.333 -2.95,-1.87 -2.298,-1.877 -0.388,-0.425 0,0"
+               style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path358-24"
+               inkscape:connector-curvature="0" /></g><g
+             id="g360-99"
+             transform="translate(616.0858,593.34)"><path
+               d="M 0,0 C 1.119,0.805 0.156,2.591 -0.388,3.292 -1.409,2.36 -3.343,0.919 -4.858,0.896 -5.88,-1.089 -1.544,-1.961 -0.545,-0.693 Z"
+               style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path362-0"
+               inkscape:connector-curvature="0" /></g><g
+             id="g364-3"
+             transform="translate(614.7651,587.9883)"><path
+               d="m 0,0 c 0,0 -3.414,-3.185 -8.948,0.409 -5.534,3.593 -5.9,5.948 -6.461,7.892 -0.561,1.945 -2.206,-0.811 0.479,-4.809 2.687,-3.999 5.606,-4.249 8.329,-5.089 C -3.878,-2.436 0,0 0,0"
+               style="fill:#53ba62;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path366-48"
+               inkscape:connector-curvature="0" /></g></g></g><g
+         id="g320-47"
+         transform="translate(-207.39891,-231.07561)"><g
+           id="g322-9"><g
+             id="g328-0"><g
+               id="g330-9"><path
+                 d="m 597.631,595.763 c 2.049,-21.116 42.65,-9.236 13.822,6.899 v 0 c -1.206,0.675 -2.77,1.009 -4.411,1.009 v 0 c -4.65,0 -9.919,-2.683 -9.411,-7.908"
+                 style="fill:url(#radialGradient336-4);stroke:none"
+                 id="path338-338"
+                 inkscape:connector-curvature="0" /></g></g></g></g><g
+         id="g340-46"
+         transform="translate(-207.39891,-231.07561)"><g
+           id="g342-4"
+           clip-path="url(#clipPath346-3)"><g
+             id="g348-51"
+             transform="translate(618.419,597.6913)"><path
+               d="m 0,0 c 12.223,-11.924 -12.638,-19.168 -19.29,-7.01 0.137,-0.206 4.762,-6.999 13.682,-4.775 C 3.434,-9.53 3.35,-4.453 0,0"
+               style="fill:#008845;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path350-6"
+               inkscape:connector-curvature="0" /></g><g
+             id="g352-79"
+             transform="translate(617.6553,592.3096)"><path
+               d="m 0,0 c 0,0 2.26,2.019 -1.239,5.759 -3.499,3.741 -9.138,5.554 -12.757,3.282 -3.619,-2.273 -1.794,-6.134 0.847,-4.47 2.64,1.664 6.33,0.471 6.003,-1.055 -0.328,-1.526 -2.955,-2.645 -1.409,-4.925 1.545,-2.28 6.858,-1.284 7.706,0.063 z"
+               style="fill:#dae79c;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path354-49"
+               inkscape:connector-curvature="0" /></g><g
+             id="g356-4"
+             transform="translate(612.5725,599.311)"><path
+               d="M 0,0 C -1.751,1.832 -4.351,2.794 -6.25,1.862 -8.375,0.82 -7.594,-1.412 -6.043,-0.648 -5.346,-0.305 -1.88,-0.333 -2.95,-1.87 -2.298,-1.877 -0.388,-0.425 0,0"
+               style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path358-9"
+               inkscape:connector-curvature="0" /></g><g
+             id="g360-29"
+             transform="translate(616.0858,593.34)"><path
+               d="M 0,0 C 1.119,0.805 0.156,2.591 -0.388,3.292 -1.409,2.36 -3.343,0.919 -4.858,0.896 -5.88,-1.089 -1.544,-1.961 -0.545,-0.693 Z"
+               style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path362-5"
+               inkscape:connector-curvature="0" /></g><g
+             id="g364-26"
+             transform="translate(614.7651,587.9883)"><path
+               d="m 0,0 c 0,0 -3.414,-3.185 -8.948,0.409 -5.534,3.593 -5.9,5.948 -6.461,7.892 -0.561,1.945 -2.206,-0.811 0.479,-4.809 2.687,-3.999 5.606,-4.249 8.329,-5.089 C -3.878,-2.436 0,0 0,0"
+               style="fill:#53ba62;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path366-1"
+               inkscape:connector-curvature="0" /></g></g></g><g
+         id="g320-04"
+         transform="translate(-206.64986,-187.63049)"><g
+           id="g322-88"><g
+             id="g328-307"><g
+               id="g330-56"><path
+                 d="m 597.631,595.763 c 2.049,-21.116 42.65,-9.236 13.822,6.899 v 0 c -1.206,0.675 -2.77,1.009 -4.411,1.009 v 0 c -4.65,0 -9.919,-2.683 -9.411,-7.908"
+                 style="fill:url(#radialGradient336-70);stroke:none"
+                 id="path338-6"
+                 inkscape:connector-curvature="0" /></g></g></g></g><g
+         id="g340-72"
+         transform="translate(-206.64986,-187.63049)"><g
+           id="g342-0"
+           clip-path="url(#clipPath346-6)"><g
+             id="g348-3"
+             transform="translate(618.419,597.6913)"><path
+               d="m 0,0 c 12.223,-11.924 -12.638,-19.168 -19.29,-7.01 0.137,-0.206 4.762,-6.999 13.682,-4.775 C 3.434,-9.53 3.35,-4.453 0,0"
+               style="fill:#008845;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path350-46"
+               inkscape:connector-curvature="0" /></g><g
+             id="g352-6"
+             transform="translate(617.6553,592.3096)"><path
+               d="m 0,0 c 0,0 2.26,2.019 -1.239,5.759 -3.499,3.741 -9.138,5.554 -12.757,3.282 -3.619,-2.273 -1.794,-6.134 0.847,-4.47 2.64,1.664 6.33,0.471 6.003,-1.055 -0.328,-1.526 -2.955,-2.645 -1.409,-4.925 1.545,-2.28 6.858,-1.284 7.706,0.063 z"
+               style="fill:#dae79c;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path354-8"
+               inkscape:connector-curvature="0" /></g><g
+             id="g356-1"
+             transform="translate(612.5725,599.311)"><path
+               d="M 0,0 C -1.751,1.832 -4.351,2.794 -6.25,1.862 -8.375,0.82 -7.594,-1.412 -6.043,-0.648 -5.346,-0.305 -1.88,-0.333 -2.95,-1.87 -2.298,-1.877 -0.388,-0.425 0,0"
+               style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path358-09"
+               inkscape:connector-curvature="0" /></g><g
+             id="g360-0"
+             transform="translate(616.0858,593.34)"><path
+               d="M 0,0 C 1.119,0.805 0.156,2.591 -0.388,3.292 -1.409,2.36 -3.343,0.919 -4.858,0.896 -5.88,-1.089 -1.544,-1.961 -0.545,-0.693 Z"
+               style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path362-69"
+               inkscape:connector-curvature="0" /></g><g
+             id="g364-23"
+             transform="translate(614.7651,587.9883)"><path
+               d="m 0,0 c 0,0 -3.414,-3.185 -8.948,0.409 -5.534,3.593 -5.9,5.948 -6.461,7.892 -0.561,1.945 -2.206,-0.811 0.479,-4.809 2.687,-3.999 5.606,-4.249 8.329,-5.089 C -3.878,-2.436 0,0 0,0"
+               style="fill:#53ba62;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path366-3"
+               inkscape:connector-curvature="0" /></g></g></g><g
+         id="g320-7"
+         transform="translate(-169.19717,-151.67591)"><g
+           id="g322-62"><g
+             id="g328-3079"><g
+               id="g330-75"><path
+                 d="m 597.631,595.763 c 2.049,-21.116 42.65,-9.236 13.822,6.899 v 0 c -1.206,0.675 -2.77,1.009 -4.411,1.009 v 0 c -4.65,0 -9.919,-2.683 -9.411,-7.908"
+                 style="fill:url(#radialGradient336-71);stroke:none"
+                 id="path338-1"
+                 inkscape:connector-curvature="0" /></g></g></g></g><g
+         id="g340-77"
+         transform="translate(-169.19717,-151.67591)"><g
+           id="g342-1"
+           clip-path="url(#clipPath346-55)"><g
+             id="g348-9"
+             transform="translate(618.419,597.6913)"><path
+               d="m 0,0 c 12.223,-11.924 -12.638,-19.168 -19.29,-7.01 0.137,-0.206 4.762,-6.999 13.682,-4.775 C 3.434,-9.53 3.35,-4.453 0,0"
+               style="fill:#008845;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path350-0"
+               inkscape:connector-curvature="0" /></g><g
+             id="g352-8"
+             transform="translate(617.6553,592.3096)"><path
+               d="m 0,0 c 0,0 2.26,2.019 -1.239,5.759 -3.499,3.741 -9.138,5.554 -12.757,3.282 -3.619,-2.273 -1.794,-6.134 0.847,-4.47 2.64,1.664 6.33,0.471 6.003,-1.055 -0.328,-1.526 -2.955,-2.645 -1.409,-4.925 1.545,-2.28 6.858,-1.284 7.706,0.063 z"
+               style="fill:#dae79c;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path354-0"
+               inkscape:connector-curvature="0" /></g><g
+             id="g356-33"
+             transform="translate(612.5725,599.311)"><path
+               d="M 0,0 C -1.751,1.832 -4.351,2.794 -6.25,1.862 -8.375,0.82 -7.594,-1.412 -6.043,-0.648 -5.346,-0.305 -1.88,-0.333 -2.95,-1.87 -2.298,-1.877 -0.388,-0.425 0,0"
+               style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path358-81"
+               inkscape:connector-curvature="0" /></g><g
+             id="g360-298"
+             transform="translate(616.0858,593.34)"><path
+               d="M 0,0 C 1.119,0.805 0.156,2.591 -0.388,3.292 -1.409,2.36 -3.343,0.919 -4.858,0.896 -5.88,-1.089 -1.544,-1.961 -0.545,-0.693 Z"
+               style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path362-81"
+               inkscape:connector-curvature="0" /></g><g
+             id="g364-7"
+             transform="translate(614.7651,587.9883)"><path
+               d="m 0,0 c 0,0 -3.414,-3.185 -8.948,0.409 -5.534,3.593 -5.9,5.948 -6.461,7.892 -0.561,1.945 -2.206,-0.811 0.479,-4.809 2.687,-3.999 5.606,-4.249 8.329,-5.089 C -3.878,-2.436 0,0 0,0"
+               style="fill:#53ba62;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path366-6"
+               inkscape:connector-curvature="0" /></g></g></g><g
+         id="g320-41"
+         transform="translate(-252.34214,-183.88522)"><g
+           id="g322-2"><g
+             id="g328-7"><g
+               id="g330-58"><path
+                 d="m 597.631,595.763 c 2.049,-21.116 42.65,-9.236 13.822,6.899 v 0 c -1.206,0.675 -2.77,1.009 -4.411,1.009 v 0 c -4.65,0 -9.919,-2.683 -9.411,-7.908"
+                 style="fill:url(#radialGradient336-2);stroke:none"
+                 id="path338-2"
+                 inkscape:connector-curvature="0" /></g></g></g></g><g
+         id="g340-6"
+         transform="translate(-252.34214,-183.88522)"><g
+           id="g342-9"
+           clip-path="url(#clipPath346-03)"><g
+             id="g348-30"
+             transform="translate(618.419,597.6913)"><path
+               d="m 0,0 c 12.223,-11.924 -12.638,-19.168 -19.29,-7.01 0.137,-0.206 4.762,-6.999 13.682,-4.775 C 3.434,-9.53 3.35,-4.453 0,0"
+               style="fill:#008845;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path350-11"
+               inkscape:connector-curvature="0" /></g><g
+             id="g352-2"
+             transform="translate(617.6553,592.3096)"><path
+               d="m 0,0 c 0,0 2.26,2.019 -1.239,5.759 -3.499,3.741 -9.138,5.554 -12.757,3.282 -3.619,-2.273 -1.794,-6.134 0.847,-4.47 2.64,1.664 6.33,0.471 6.003,-1.055 -0.328,-1.526 -2.955,-2.645 -1.409,-4.925 1.545,-2.28 6.858,-1.284 7.706,0.063 z"
+               style="fill:#dae79c;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path354-58"
+               inkscape:connector-curvature="0" /></g><g
+             id="g356-46"
+             transform="translate(612.5725,599.311)"><path
+               d="M 0,0 C -1.751,1.832 -4.351,2.794 -6.25,1.862 -8.375,0.82 -7.594,-1.412 -6.043,-0.648 -5.346,-0.305 -1.88,-0.333 -2.95,-1.87 -2.298,-1.877 -0.388,-0.425 0,0"
+               style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path358-4"
+               inkscape:connector-curvature="0" /></g><g
+             id="g360-90"
+             transform="translate(616.0858,593.34)"><path
+               d="M 0,0 C 1.119,0.805 0.156,2.591 -0.388,3.292 -1.409,2.36 -3.343,0.919 -4.858,0.896 -5.88,-1.089 -1.544,-1.961 -0.545,-0.693 Z"
+               style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path362-76"
+               inkscape:connector-curvature="0" /></g><g
+             id="g364-50"
+             transform="translate(614.7651,587.9883)"><path
+               d="m 0,0 c 0,0 -3.414,-3.185 -8.948,0.409 -5.534,3.593 -5.9,5.948 -6.461,7.892 -0.561,1.945 -2.206,-0.811 0.479,-4.809 2.687,-3.999 5.606,-4.249 8.329,-5.089 C -3.878,-2.436 0,0 0,0"
+               style="fill:#53ba62;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path366-86"
+               inkscape:connector-curvature="0" /></g></g></g><g
+         id="g320-3"
+         transform="translate(-221.63093,-139.69105)"><g
+           id="g322-86"><g
+             id="g328-38"><g
+               id="g330-542"><path
+                 d="m 597.631,595.763 c 2.049,-21.116 42.65,-9.236 13.822,6.899 v 0 c -1.206,0.675 -2.77,1.009 -4.411,1.009 v 0 c -4.65,0 -9.919,-2.683 -9.411,-7.908"
+                 style="fill:url(#radialGradient336-68);stroke:none"
+                 id="path338-013"
+                 inkscape:connector-curvature="0" /></g></g></g></g><g
+         id="g340-38"
+         transform="translate(-221.63093,-139.69105)"><g
+           id="g342-75"
+           clip-path="url(#clipPath346-57)"><g
+             id="g348-55"
+             transform="translate(618.419,597.6913)"><path
+               d="m 0,0 c 12.223,-11.924 -12.638,-19.168 -19.29,-7.01 0.137,-0.206 4.762,-6.999 13.682,-4.775 C 3.434,-9.53 3.35,-4.453 0,0"
+               style="fill:#008845;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path350-07"
+               inkscape:connector-curvature="0" /></g><g
+             id="g352-89"
+             transform="translate(617.6553,592.3096)"><path
+               d="m 0,0 c 0,0 2.26,2.019 -1.239,5.759 -3.499,3.741 -9.138,5.554 -12.757,3.282 -3.619,-2.273 -1.794,-6.134 0.847,-4.47 2.64,1.664 6.33,0.471 6.003,-1.055 -0.328,-1.526 -2.955,-2.645 -1.409,-4.925 1.545,-2.28 6.858,-1.284 7.706,0.063 z"
+               style="fill:#dae79c;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path354-589"
+               inkscape:connector-curvature="0" /></g><g
+             id="g356-9"
+             transform="translate(612.5725,599.311)"><path
+               d="M 0,0 C -1.751,1.832 -4.351,2.794 -6.25,1.862 -8.375,0.82 -7.594,-1.412 -6.043,-0.648 -5.346,-0.305 -1.88,-0.333 -2.95,-1.87 -2.298,-1.877 -0.388,-0.425 0,0"
+               style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path358-6"
+               inkscape:connector-curvature="0" /></g><g
+             id="g360-4"
+             transform="translate(616.0858,593.34)"><path
+               d="M 0,0 C 1.119,0.805 0.156,2.591 -0.388,3.292 -1.409,2.36 -3.343,0.919 -4.858,0.896 -5.88,-1.089 -1.544,-1.961 -0.545,-0.693 Z"
+               style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path362-41"
+               inkscape:connector-curvature="0" /></g><g
+             id="g364-4"
+             transform="translate(614.7651,587.9883)"><path
+               d="m 0,0 c 0,0 -3.414,-3.185 -8.948,0.409 -5.534,3.593 -5.9,5.948 -6.461,7.892 -0.561,1.945 -2.206,-0.811 0.479,-4.809 2.687,-3.999 5.606,-4.249 8.329,-5.089 C -3.878,-2.436 0,0 0,0"
+               style="fill:#53ba62;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path366-404"
+               inkscape:connector-curvature="0" /></g></g></g><g
+         id="g320-2"
+         transform="translate(-265.07605,-132.94957)"><g
+           id="g322-66"><g
+             id="g328-74"><g
+               id="g330-0"><path
+                 d="m 597.631,595.763 c 2.049,-21.116 42.65,-9.236 13.822,6.899 v 0 c -1.206,0.675 -2.77,1.009 -4.411,1.009 v 0 c -4.65,0 -9.919,-2.683 -9.411,-7.908"
+                 style="fill:url(#radialGradient336-84);stroke:none"
+                 id="path338-8"
+                 inkscape:connector-curvature="0" /></g></g></g></g><g
+         id="g340-2"
+         transform="translate(-265.07605,-132.94957)"><g
+           id="g342-752"
+           clip-path="url(#clipPath346-53)"><g
+             id="g348-09"
+             transform="translate(618.419,597.6913)"><path
+               d="m 0,0 c 12.223,-11.924 -12.638,-19.168 -19.29,-7.01 0.137,-0.206 4.762,-6.999 13.682,-4.775 C 3.434,-9.53 3.35,-4.453 0,0"
+               style="fill:#008845;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path350-7"
+               inkscape:connector-curvature="0" /></g><g
+             id="g352-796"
+             transform="translate(617.6553,592.3096)"><path
+               d="m 0,0 c 0,0 2.26,2.019 -1.239,5.759 -3.499,3.741 -9.138,5.554 -12.757,3.282 -3.619,-2.273 -1.794,-6.134 0.847,-4.47 2.64,1.664 6.33,0.471 6.003,-1.055 -0.328,-1.526 -2.955,-2.645 -1.409,-4.925 1.545,-2.28 6.858,-1.284 7.706,0.063 z"
+               style="fill:#dae79c;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path354-88"
+               inkscape:connector-curvature="0" /></g><g
+             id="g356-09"
+             transform="translate(612.5725,599.311)"><path
+               d="M 0,0 C -1.751,1.832 -4.351,2.794 -6.25,1.862 -8.375,0.82 -7.594,-1.412 -6.043,-0.648 -5.346,-0.305 -1.88,-0.333 -2.95,-1.87 -2.298,-1.877 -0.388,-0.425 0,0"
+               style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path358-99"
+               inkscape:connector-curvature="0" /></g><g
+             id="g360-3"
+             transform="translate(616.0858,593.34)"><path
+               d="M 0,0 C 1.119,0.805 0.156,2.591 -0.388,3.292 -1.409,2.36 -3.343,0.919 -4.858,0.896 -5.88,-1.089 -1.544,-1.961 -0.545,-0.693 Z"
+               style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path362-01"
+               inkscape:connector-curvature="0" /></g><g
+             id="g364-03"
+             transform="translate(614.7651,587.9883)"><path
+               d="m 0,0 c 0,0 -3.414,-3.185 -8.948,0.409 -5.534,3.593 -5.9,5.948 -6.461,7.892 -0.561,1.945 -2.206,-0.811 0.479,-4.809 2.687,-3.999 5.606,-4.249 8.329,-5.089 C -3.878,-2.436 0,0 0,0"
+               style="fill:#53ba62;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path366-7"
+               inkscape:connector-curvature="0" /></g></g></g><g
+         id="g320-39"
+         transform="translate(-259.08362,-87.257288)"><g
+           id="g322-3"><g
+             id="g328-63"><g
+               id="g330-92"><path
+                 d="m 597.631,595.763 c 2.049,-21.116 42.65,-9.236 13.822,6.899 v 0 c -1.206,0.675 -2.77,1.009 -4.411,1.009 v 0 c -4.65,0 -9.919,-2.683 -9.411,-7.908"
+                 style="fill:url(#radialGradient336-73);stroke:none"
+                 id="path338-37"
+                 inkscape:connector-curvature="0" /></g></g></g></g><g
+         id="g340-39"
+         transform="translate(-259.08362,-87.257288)"><g
+           id="g342-5"
+           clip-path="url(#clipPath346-4)"><g
+             id="g348-33"
+             transform="translate(618.419,597.6913)"><path
+               d="m 0,0 c 12.223,-11.924 -12.638,-19.168 -19.29,-7.01 0.137,-0.206 4.762,-6.999 13.682,-4.775 C 3.434,-9.53 3.35,-4.453 0,0"
+               style="fill:#008845;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path350-110"
+               inkscape:connector-curvature="0" /></g><g
+             id="g352-23"
+             transform="translate(617.6553,592.3096)"><path
+               d="m 0,0 c 0,0 2.26,2.019 -1.239,5.759 -3.499,3.741 -9.138,5.554 -12.757,3.282 -3.619,-2.273 -1.794,-6.134 0.847,-4.47 2.64,1.664 6.33,0.471 6.003,-1.055 -0.328,-1.526 -2.955,-2.645 -1.409,-4.925 1.545,-2.28 6.858,-1.284 7.706,0.063 z"
+               style="fill:#dae79c;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path354-2"
+               inkscape:connector-curvature="0" /></g><g
+             id="g356-84"
+             transform="translate(612.5725,599.311)"><path
+               d="M 0,0 C -1.751,1.832 -4.351,2.794 -6.25,1.862 -8.375,0.82 -7.594,-1.412 -6.043,-0.648 -5.346,-0.305 -1.88,-0.333 -2.95,-1.87 -2.298,-1.877 -0.388,-0.425 0,0"
+               style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path358-21"
+               inkscape:connector-curvature="0" /></g><g
+             id="g360-43"
+             transform="translate(616.0858,593.34)"><path
+               d="M 0,0 C 1.119,0.805 0.156,2.591 -0.388,3.292 -1.409,2.36 -3.343,0.919 -4.858,0.896 -5.88,-1.089 -1.544,-1.961 -0.545,-0.693 Z"
+               style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path362-2"
+               inkscape:connector-curvature="0" /></g><g
+             id="g364-46"
+             transform="translate(614.7651,587.9883)"><path
+               d="m 0,0 c 0,0 -3.414,-3.185 -8.948,0.409 -5.534,3.593 -5.9,5.948 -6.461,7.892 -0.561,1.945 -2.206,-0.811 0.479,-4.809 2.687,-3.999 5.606,-4.249 8.329,-5.089 C -3.878,-2.436 0,0 0,0"
+               style="fill:#53ba62;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path366-33"
+               inkscape:connector-curvature="0" /></g></g></g><g
+         id="g320-5"
+         transform="translate(-211.14418,-105.98363)"><g
+           id="g322-47"><g
+             id="g328-93"><g
+               id="g330-1"><path
+                 d="m 597.631,595.763 c 2.049,-21.116 42.65,-9.236 13.822,6.899 v 0 c -1.206,0.675 -2.77,1.009 -4.411,1.009 v 0 c -4.65,0 -9.919,-2.683 -9.411,-7.908"
+                 style="fill:url(#radialGradient336-89);stroke:none"
+                 id="path338-57"
+                 inkscape:connector-curvature="0" /></g></g></g></g><g
+         id="g340-65"
+         transform="translate(-211.14418,-105.98363)"><g
+           id="g342-74"
+           clip-path="url(#clipPath346-59)"><g
+             id="g348-47"
+             transform="translate(618.419,597.6913)"><path
+               d="m 0,0 c 12.223,-11.924 -12.638,-19.168 -19.29,-7.01 0.137,-0.206 4.762,-6.999 13.682,-4.775 C 3.434,-9.53 3.35,-4.453 0,0"
+               style="fill:#008845;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path350-03"
+               inkscape:connector-curvature="0" /></g><g
+             id="g352-33"
+             transform="translate(617.6553,592.3096)"><path
+               d="m 0,0 c 0,0 2.26,2.019 -1.239,5.759 -3.499,3.741 -9.138,5.554 -12.757,3.282 -3.619,-2.273 -1.794,-6.134 0.847,-4.47 2.64,1.664 6.33,0.471 6.003,-1.055 -0.328,-1.526 -2.955,-2.645 -1.409,-4.925 1.545,-2.28 6.858,-1.284 7.706,0.063 z"
+               style="fill:#dae79c;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path354-22"
+               inkscape:connector-curvature="0" /></g><g
+             id="g356-07"
+             transform="translate(612.5725,599.311)"><path
+               d="M 0,0 C -1.751,1.832 -4.351,2.794 -6.25,1.862 -8.375,0.82 -7.594,-1.412 -6.043,-0.648 -5.346,-0.305 -1.88,-0.333 -2.95,-1.87 -2.298,-1.877 -0.388,-0.425 0,0"
+               style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path358-811"
+               inkscape:connector-curvature="0" /></g><g
+             id="g360-23"
+             transform="translate(616.0858,593.34)"><path
+               d="M 0,0 C 1.119,0.805 0.156,2.591 -0.388,3.292 -1.409,2.36 -3.343,0.919 -4.858,0.896 -5.88,-1.089 -1.544,-1.961 -0.545,-0.693 Z"
+               style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path362-58"
+               inkscape:connector-curvature="0" /></g><g
+             id="g364-1"
+             transform="translate(614.7651,587.9883)"><path
+               d="m 0,0 c 0,0 -3.414,-3.185 -8.948,0.409 -5.534,3.593 -5.9,5.948 -6.461,7.892 -0.561,1.945 -2.206,-0.811 0.479,-4.809 2.687,-3.999 5.606,-4.249 8.329,-5.089 C -3.878,-2.436 0,0 0,0"
+               style="fill:#53ba62;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path366-0"
+               inkscape:connector-curvature="0" /></g></g></g><g
+         id="g320-01"
+         transform="translate(-259.83268,-40.815954)"><g
+           id="g322-70"><g
+             id="g328-79"><g
+               id="g330-78"><path
+                 d="m 597.631,595.763 c 2.049,-21.116 42.65,-9.236 13.822,6.899 v 0 c -1.206,0.675 -2.77,1.009 -4.411,1.009 v 0 c -4.65,0 -9.919,-2.683 -9.411,-7.908"
+                 style="fill:url(#radialGradient336-5);stroke:none"
+                 id="path338-72"
+                 inkscape:connector-curvature="0" /></g></g></g></g><g
+         id="g340-17"
+         transform="translate(-259.83268,-40.815954)"><g
+           id="g342-58"
+           clip-path="url(#clipPath346-8)"><g
+             id="g348-86"
+             transform="translate(618.419,597.6913)"><path
+               d="m 0,0 c 12.223,-11.924 -12.638,-19.168 -19.29,-7.01 0.137,-0.206 4.762,-6.999 13.682,-4.775 C 3.434,-9.53 3.35,-4.453 0,0"
+               style="fill:#008845;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path350-95"
+               inkscape:connector-curvature="0" /></g><g
+             id="g352-20"
+             transform="translate(617.6553,592.3096)"><path
+               d="m 0,0 c 0,0 2.26,2.019 -1.239,5.759 -3.499,3.741 -9.138,5.554 -12.757,3.282 -3.619,-2.273 -1.794,-6.134 0.847,-4.47 2.64,1.664 6.33,0.471 6.003,-1.055 -0.328,-1.526 -2.955,-2.645 -1.409,-4.925 1.545,-2.28 6.858,-1.284 7.706,0.063 z"
+               style="fill:#dae79c;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path354-6"
+               inkscape:connector-curvature="0" /></g><g
+             id="g356-36"
+             transform="translate(612.5725,599.311)"><path
+               d="M 0,0 C -1.751,1.832 -4.351,2.794 -6.25,1.862 -8.375,0.82 -7.594,-1.412 -6.043,-0.648 -5.346,-0.305 -1.88,-0.333 -2.95,-1.87 -2.298,-1.877 -0.388,-0.425 0,0"
+               style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path358-02"
+               inkscape:connector-curvature="0" /></g><g
+             id="g360-32"
+             transform="translate(616.0858,593.34)"><path
+               d="M 0,0 C 1.119,0.805 0.156,2.591 -0.388,3.292 -1.409,2.36 -3.343,0.919 -4.858,0.896 -5.88,-1.089 -1.544,-1.961 -0.545,-0.693 Z"
+               style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path362-9"
+               inkscape:connector-curvature="0" /></g><g
+             id="g364-8"
+             transform="translate(614.7651,587.9883)"><path
+               d="m 0,0 c 0,0 -3.414,-3.185 -8.948,0.409 -5.534,3.593 -5.9,5.948 -6.461,7.892 -0.561,1.945 -2.206,-0.811 0.479,-4.809 2.687,-3.999 5.606,-4.249 8.329,-5.089 C -3.878,-2.436 0,0 0,0"
+               style="fill:#53ba62;fill-opacity:1;fill-rule:evenodd;stroke:none"
+               id="path366-35"
+               inkscape:connector-curvature="0" /></g></g></g></g><g
+       aria-label="Cannlytics"
+       transform="scale(1,-1)"
+       id="text1335"
+       style="font-style:normal;font-weight:normal;font-size:205.142px;line-height:1.25;font-family:sans-serif;letter-spacing:-10.6845px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.06845"><path
+         d="m 546.3963,-674.7195 q -25.23246,0 -40.41297,-14.77023 -14.97537,-14.97536 -14.97537,-43.69524 v -32.41244 q 0,-28.71988 14.97537,-43.4901 15.18051,-14.97537 40.41297,-14.97537 25.02733,0 38.15641,14.1548 13.33423,13.94966 13.33423,38.15641 v 1.02571 h -13.12908 v -1.43599 q 0,-17.64221 -9.43654,-28.71988 -9.23139,-11.28281 -28.92502,-11.28281 -19.89877,0 -31.18158,12.10338 -11.07767,12.10337 -11.07767,34.05357 v 33.233 q 0,21.95019 11.07767,34.05357 11.28281,12.10338 31.18158,12.10338 19.69363,0 28.92502,-11.07767 9.43654,-11.28281 9.43654,-28.92502 v -3.07713 h 13.12908 v 2.66685 q 0,24.20675 -13.33423,38.36155 -13.12908,13.94966 -38.15641,13.94966 z"
+         style="font-style:normal;font-variant:normal;font-weight:300;font-stretch:normal;font-size:205.142px;font-family:'Space Grotesk';-inkscape-font-specification:'Space Grotesk Light';fill:#000000;fill-opacity:1;stroke-width:1.06845"
+         id="path1324" /><path
+         d="m 647.30882,-674.7195 q -10.46225,0 -19.07821,-3.48742 -8.41082,-3.69255 -13.33423,-10.66738 -4.92341,-6.97483 -4.92341,-16.82165 0,-10.05195 4.92341,-16.6165 4.92341,-6.56454 13.33423,-9.84681 8.41082,-3.28228 18.66792,-3.28228 h 36.51528 v -8.00053 q 0,-12.10338 -6.97483,-18.87307 -6.97483,-6.76968 -20.30906,-6.76968 -13.12909,0 -20.92448,6.56454 -7.59026,6.3594 -10.2571,17.23193 l -11.89824,-3.48742 q 2.46171,-8.8211 7.7954,-15.79593 5.33369,-7.17997 14.1548,-11.28281 8.8211,-4.30798 21.12962,-4.30798 18.87307,0 29.13017,9.84682 10.46224,9.84681 10.46224,27.48902 v 48.41351 q 0,6.15426 5.74397,6.15426 h 9.64168 v 10.66739 h -13.12909 q -6.3594,0 -10.05196,-3.69256 -3.69255,-3.8977 -3.69255,-10.46224 v -1.43599 h -2.05142 q -2.66685,4.51312 -6.97483,8.8211 -4.10284,4.30798 -10.87253,6.97483 -6.76968,2.66685 -17.02678,2.66685 z m 0.82056,-11.07767 q 15.79594,0 25.43761,-9.43653 9.84682,-9.64168 9.84682,-27.07875 v -2.4617 h -36.31014 q -10.66738,0 -17.84735,4.71826 -6.97483,4.51313 -6.97483,14.1548 0,9.64168 7.17997,14.97537 7.38511,5.12855 18.66792,5.12855 z"
+         style="font-style:normal;font-variant:normal;font-weight:300;font-stretch:normal;font-size:205.142px;font-family:'Space Grotesk';-inkscape-font-specification:'Space Grotesk Light';fill:#000000;fill-opacity:1;stroke-width:1.06845"
+         id="path1326" /><path
+         d="m 723.80955,-677.59149 v -99.69901 h 11.89823 v 18.46278 h 2.46171 q 3.69255,-8.00054 12.10337,-14.1548 8.41083,-6.3594 24.4119,-6.3594 10.87253,0 19.89877,4.51312 9.02625,4.51313 14.35994,13.74451 5.53884,9.02625 5.53884,22.77077 v 60.72203 h -12.30852 v -59.90147 q 0,-16.41136 -8.20568,-23.59133 -8.20568,-7.17996 -21.95019,-7.17996 -15.38565,0 -25.64275,10.25709 -10.2571,10.05196 -10.2571,30.56616 v 49.84951 z"
+         style="font-style:normal;font-variant:normal;font-weight:300;font-stretch:normal;font-size:205.142px;font-family:'Space Grotesk';-inkscape-font-specification:'Space Grotesk Light';fill:#000000;fill-opacity:1;stroke-width:1.06845"
+         id="path1328" /><path
+         d="m 838.67185,-677.59149 v -99.69901 h 11.89824 v 18.46278 h 2.4617 q 3.69256,-8.00054 12.10338,-14.1548 8.41082,-6.3594 24.4119,-6.3594 10.87252,0 19.89877,4.51312 9.02625,4.51313 14.35994,13.74451 5.53883,9.02625 5.53883,22.77077 v 60.72203 h -12.30852 v -59.90147 q 0,-16.41136 -8.20568,-23.59133 -8.20568,-7.17996 -21.95019,-7.17996 -15.38565,0 -25.64275,10.25709 -10.2571,10.05196 -10.2571,30.56616 v 49.84951 z"
+         style="font-style:normal;font-variant:normal;font-weight:300;font-stretch:normal;font-size:205.142px;font-family:'Space Grotesk';-inkscape-font-specification:'Space Grotesk Light';fill:#000000;fill-opacity:1;stroke-width:1.06845"
+         id="path1330" /><path
+         d="m 953.53415,-677.59149 v -143.5994 h 12.30852 v 143.5994 z"
+         style="font-style:normal;font-variant:normal;font-weight:300;font-stretch:normal;font-size:205.142px;font-family:'Space Grotesk';-inkscape-font-specification:'Space Grotesk Light';fill:#000000;fill-opacity:1;stroke-width:1.06845"
+         id="path1332" /><path
+         d="m 1003.7765,-636.56309 v -11.07767 h 58.2603 q 6.1542,0 6.1542,-6.15426 v -41.0284 h -2.4617 q -2.4617,4.92341 -6.9748,9.43653 -4.308,4.30798 -11.4879,7.17997 -6.9749,2.66685 -17.6423,2.66685 -10.6673,0 -19.8987,-4.51313 -9.0263,-4.51312 -14.56511,-13.53937 -5.33369,-9.02625 -5.33369,-22.77076 v -60.92717 h 12.3085 v 60.1066 q 0,16.20622 8.2057,23.38619 8.2057,7.17997 21.9502,7.17997 15.3856,0 25.6427,-10.05196 10.2571,-10.05196 10.2571,-30.56616 v -50.05464 h 12.3086 v 125.13662 q 0,7.38511 -4.1029,11.48795 -3.8977,4.10284 -11.8982,4.10284 z"
+         style="font-style:normal;font-variant:normal;font-weight:300;font-stretch:normal;font-size:205.142px;font-family:'Space Grotesk';-inkscape-font-specification:'Space Grotesk Light';fill:#000000;fill-opacity:1;stroke-width:1.06845"
+         id="path1334" /><path
+         d="m 1142.0246,-677.59149 q -8.0005,0 -12.1033,-4.10284 -3.8977,-4.10284 -3.8977,-11.48795 v -73.03055 h -32.0022 v -11.07767 h 32.0022 v -37.33585 h 12.3085 v 37.33585 h 34.8741 v 11.07767 h -34.8741 v 71.38941 q 0,6.15426 6.1542,6.15426 h 23.3862 v 11.07767 z"
+         style="font-style:normal;font-variant:normal;font-weight:300;font-stretch:normal;font-size:205.142px;font-family:'Space Grotesk';-inkscape-font-specification:'Space Grotesk Light';fill:#000000;fill-opacity:1;stroke-width:1.06845"
+         id="path1336" /><path
+         d="m 1190.0108,-677.59149 v -99.69901 h 12.3085 v 99.69901 z m 6.1542,-116.93094 q -4.5131,0 -7.5902,-3.07713 -3.0771,-3.07713 -3.0771,-7.59025 0,-4.71827 3.0771,-7.59026 3.0771,-3.07713 7.5902,-3.07713 4.7183,0 7.5903,3.07713 3.0771,2.87199 3.0771,7.59026 0,4.51312 -3.0771,7.59025 -2.872,3.07713 -7.5903,3.07713 z"
+         style="font-style:normal;font-variant:normal;font-weight:300;font-stretch:normal;font-size:205.142px;font-family:'Space Grotesk';-inkscape-font-specification:'Space Grotesk Light';fill:#000000;fill-opacity:1;stroke-width:1.06845"
+         id="path1338" /><path
+         d="m 1273.2809,-674.7195 q -14.565,0 -26.053,-6.15426 -11.2828,-6.35941 -17.8473,-18.0525 -6.5646,-11.69309 -6.5646,-27.48903 v -2.05142 q 0,-16.00107 6.5646,-27.48903 6.5645,-11.69309 17.8473,-17.84735 11.488,-6.3594 26.053,-6.3594 14.36,0 24.2068,5.53883 10.052,5.3337 15.5908,14.1548 5.744,8.61596 7.18,18.25764 l -12.1034,2.4617 q -1.0257,-8.00054 -5.1286,-14.56508 -4.1028,-6.76969 -11.4879,-10.66738 -7.3851,-4.10284 -18.2577,-4.10284 -10.8725,0 -19.4885,5.12855 -8.6159,4.9234 -13.7445,14.15479 -4.9234,9.02625 -4.9234,21.53991 v 1.64114 q 0,12.51366 4.9234,21.74505 5.1286,9.02625 13.7445,14.1548 8.616,4.92341 19.4885,4.92341 16.4114,0 25.0274,-8.41082 8.6159,-8.61597 10.6673,-20.92449 l 12.1034,2.46171 q -1.8463,9.64167 -7.7954,18.46278 -5.7439,8.61596 -15.7959,14.15479 -9.8468,5.3337 -24.2068,5.3337 z"
+         style="font-style:normal;font-variant:normal;font-weight:300;font-stretch:normal;font-size:205.142px;font-family:'Space Grotesk';-inkscape-font-specification:'Space Grotesk Light';fill:#000000;fill-opacity:1;stroke-width:1.06845"
+         id="path1340" /><path
+         d="m 1380.3477,-674.7195 q -18.0525,0 -30.9765,-8.61597 -12.7188,-8.61596 -15.5907,-28.51474 l 12.1033,-2.25656 q 1.6412,10.87253 6.5646,17.23193 5.1285,6.15426 12.3085,8.82111 7.3851,2.66684 15.5908,2.66684 12.3085,0 19.6936,-4.92341 7.5903,-5.12855 7.5903,-13.94965 0,-9.02625 -7.18,-12.51366 -6.9748,-3.48742 -18.6679,-5.53884 l -9.4365,-1.64113 q -9.4366,-1.64114 -17.0268,-4.92341 -7.5903,-3.48742 -12.1034,-9.23139 -4.308,-5.74398 -4.308,-14.35994 0,-12.92395 10.052,-20.30906 10.2571,-7.38511 27.0787,-7.38511 17.0268,0 27.2839,7.7954 10.4622,7.59025 13.1291,22.15533 l -11.488,2.4617 q -2.0514,-11.89823 -9.8468,-16.82164 -7.5902,-4.92341 -19.0782,-4.92341 -11.2828,0 -18.0525,4.30799 -6.7697,4.30798 -6.7697,12.7188 0,8.20568 6.1543,12.10338 6.1542,3.69255 16.8216,5.53883 l 9.4366,1.64114 q 10.2571,1.64113 18.4627,4.9234 8.2057,3.07713 12.924,8.82111 4.9234,5.74398 4.9234,15.18051 0,14.1548 -10.6674,21.95019 -10.6674,7.59026 -28.925,7.59026 z"
+         style="font-style:normal;font-variant:normal;font-weight:300;font-stretch:normal;font-size:205.142px;font-family:'Space Grotesk';-inkscape-font-specification:'Space Grotesk Light';fill:#000000;fill-opacity:1;stroke-width:1.06845"
+         id="path1342" /></g></g></svg>
diff --git a/console/templates/console/pages/account/base_sign_in.html b/console/templates/console/pages/account/base_sign_in.html
index 9ee3cc13..24b3b9e0 100644
--- a/console/templates/console/pages/account/base_sign_in.html
+++ b/console/templates/console/pages/account/base_sign_in.html
@@ -37,8 +37,8 @@
             
           <!-- Logo -->
           <div class="text-center mb-3 mb-sm-4">
-            <img class="light-only" src="{% static 'console/images/logos/cannlytics_logo_with_phrase.svg' %}" style="max-height:6rem; max-width:100%;"/>
-            <img class="dark-only" src="{% static 'console/images/logos/cannlytics_logo_with_phrase_dark.svg' %}" style="max-height:6rem; max-width:100%;"/>
+            <img class="light-only" src="{% static 'console/images/logos/cannlytics_logo_with_text.svg' %}" style="max-height:3rem; max-width:100%;"/>
+            <img class="dark-only" src="{% static 'console/images/logos/cannlytics_logo_with_text_dark.svg' %}" style="max-height:3rem; max-width:100%;"/>
           </div>
             
           <!-- Dynamic Form Fields -->
diff --git a/console/templates/console/pages/account/sign-in.html b/console/templates/console/pages/account/sign-in.html
index 839eafbc..346ff98f 100644
--- a/console/templates/console/pages/account/sign-in.html
+++ b/console/templates/console/pages/account/sign-in.html
@@ -112,14 +112,14 @@ <h2 class="fs-6 lh-base text-secondary mb-3">Sign into your user account.</h2>
   </div>
 
   <!-- Google Sign In -->
-  <div class="text-dark text-center my-3">
+  {% comment %} <div class="text-dark text-center my-3">
     &mdash;&nbsp;or&nbsp;&mdash;
   </div>
   <div class="d-grid gap-2 justify-content-center">
     <a id="google-signin" class="app-action" onclick="cannlytics.auth.signInWithGoogle()"></a>
   </div>
   <img style="display:none;" src="{% static 'console/images/logos/signin-assets/btn_google_signin_dark_focus_web@2x.png' %}"/>
-  <img style="display:none;" src="{% static 'console/images/logos/signin-assets/btn_google_signin_dark_pressed_web@2x.png' %}"/>
+  <img style="display:none;" src="{% static 'console/images/logos/signin-assets/btn_google_signin_dark_pressed_web@2x.png' %}"/> {% endcomment %}
 
   <!-- Sign up -->
   <div class="d-flex align-items-center justify-content-center">
diff --git a/console/webpack.config.js b/console/webpack.config.js
index 5461c1da..719a81eb 100644
--- a/console/webpack.config.js
+++ b/console/webpack.config.js
@@ -31,10 +31,6 @@ module.exports = env => {
     output: {
       path: path.resolve(__dirname, `./static/${appName}`), // Should be in STATICFILES_DIRS.
       filename: './bundles/[name].min.js',
-      // library: { // Turns JavaScript into a module.
-      //   name: 'cannlytics',
-      //   type: 'umd',
-      // },
       library: 'cannlytics',
       libraryTarget: 'var',
       hotUpdateChunkFilename: './bundles/hot/hot-update.js',
@@ -46,25 +42,34 @@ module.exports = env => {
         // Compiles SCSS to CSS.
         {
           test: /\.(scss)$/,
-          use: [{
-            loader: 'style-loader', // Inject CSS to page by creating `style` nodes from JS strings.
-          }, {
-            loader: 'css-loader', // Translate CSS into CommonJS modules.
-          }, {
-            loader: 'postcss-loader', // Run post compile actions.
-            options: {
-              postcssOptions: {
-                plugins: function () { // Use post css plugins, can be exported to postcss.config.js
-                  return [
-                    require('precss'),
-                    require('autoprefixer')
-                  ];
-                }
+          use: [
+            {
+              // Inject CSS to page by creating `style` nodes from JS strings.
+              loader: 'style-loader',
+            },
+            {
+              // Translate CSS into CommonJS modules.
+              loader: 'css-loader',
+            },
+            {
+              // Run post compile actions.
+              loader: 'postcss-loader',
+              options: {
+                postcssOptions: {
+                  plugins: function () {
+                    return [
+                      require('precss'),
+                      require('autoprefixer')
+                    ];
+                  }
+                },
               },
             },
-          }, {
-            loader: 'sass-loader' // Compile Sass to CSS.
-          }]
+            {
+              // Compile Sass to CSS.
+              loader: 'sass-loader',
+            }
+          ]
         },
 
         // Convert ES2015 to JavaScript.
diff --git a/docs/developers/dev/data.md b/docs/developers/dev/data.md
index b03af5c8..93c7de70 100644
--- a/docs/developers/dev/data.md
+++ b/docs/developers/dev/data.md
@@ -68,10 +68,51 @@ A dataset is composed of:
     git push
     ```
 
+## Making a Dataset Pull Request
+
+When you want to make a pull request to a specific dataset, first, create a pull request branch on [Hugging Face](huggingface.co), then checkout the branch:
+
+```bash
+git fetch origin refs/pr/2:pr/2
+git checkout pr/2
+```
+
+Next, do your modifications and track all of your changes, including any large data files, e.g. `.csv` files:
+
+```bash
+git lfs track *.csv
+git add *.csv
+git commit -m "Added `csv` files"
+git add --all
+git status
+git commit -m "Updated `cannabis_licenses` dataset."
+git push
+```
+
+Finally, make the pull request:
+
+```bash
+git push origin pr/2:refs/pr/2
+```
+
+🎉 Congratulations, your pull request is now ready to be reviewed and merged by the repository admin!
+
 ## Testing a Dataset <a name="testing"></a>
 
-You can create dummy data for supported datafiles with:
+You can create dummy data for te dataset with:
 
 ```
 datasets-cli dummy_data datasets/<your-dataset-folder> --auto_generate
 ```
+
+You can also load the dataset locally in Python, for example:
+
+```py
+from datasets import load_dataset
+
+# Load the dataset.
+dataset = load_dataset('cannabis_licenses.py', 'ca')
+data = dataset['data']
+assert len(data) > 0
+print('Read %i data points.' % len(data))
+```
diff --git a/docs/developers/sops/coa-parsing.md b/docs/developers/sops/coa-parsing.md
new file mode 100644
index 00000000..7527b7d9
--- /dev/null
+++ b/docs/developers/sops/coa-parsing.md
@@ -0,0 +1,82 @@
+<table>
+  <tr>
+    <td colspan="4">Cannlytics Standard Operating Procedure</td>
+  </tr>
+  <tr>
+    <td>Title</td>
+    <td colspan="3">Certificate of Analysis (COA) Parsing</td>
+  </tr>
+  <tr>
+    <td>ID</td>
+    <td>SOP-0002</td>
+    <td>Version</td>
+    <td>0.0.1</td>
+  </tr>
+  <tr>
+    <td>Created At</td>
+    <td>2022-09-22</td>
+    <td>Updated At</td>
+    <td>2022-09-22</td>
+  </tr>
+  <tr>
+    <td>Review Period</td>
+    <td>Annual</td>
+    <td>Last Review</td>
+    <td>2022-09-22</td>
+  </tr>
+  <tr>
+    <td>Author</td>
+    <td colspan="3">Keegan Skeate, Founder</td>
+  </tr>
+  <tr>
+    <td>Approved By</td>
+    <td colspan="3">Keegan Skeate, Founder</td>
+  </tr>
+  <tr>
+    <td>Status</td>
+    <td colspan="3">Active</td>
+  </tr>
+</table>
+
+## Introduction
+
+**Certificate of Analysis (COA) Parsing**, SOP-0002, defines how Cannlytics standard operating procedure (SOP) documents are best created, stored, accessed, and used. This document is reviewed and approved by the Cannlytics admin and should be read by anyone writing standard operating procedures (SOPs).
+
+## Purpose
+
+Cannlytics believes that open-source software provides major value to people generating cannabis data, such as laboratories, because:
+
+1. Methods, measurements, and data are verifiable and reproducible.
+2. Data generation and collection processes are traceable and transparent.
+3. Data generation mechanisms, data quality, quality control, statistical analysis, interpretation, and validation of data can be improved by anyone in the world.
+
+This **COA Parsing** is the first standard operating procedure (SOP), SOP-0001, to provide clear guidance and instructions to aid in the verifiability, reproducibility, and standardization of cannabis data generation and collection.
+
+## Procedure
+
+After copying this document, SOP-0001, as a template and editing appropriate fields, the following steps outline how to create a new standard operating procedure (SOP).
+
+1. Define the users and present the process description with appropriate detail.
+2. Describe the task in detail.
+      - Break the process into sections.
+      - Break the sections into specific steps.
+      - Number the steps or add bullet points for clarity.
+3. Specify any tools that are required or helpful for the task, such as examples, data, tools, and references.
+4. Specify reviewers and approvers and plan appropriate maintenance of the document.
+5. Publish the SOP, preferably with the Cannlytics documentation.
+
+Generally, SOPs should be written in markdown so that they can be rendered in HTML or LaTeX. It is best for general SOPs to be reviewed at least annually. When changes are made, versions should be [semantically](https://semver.org/) incremented.
+
+## Training
+
+It is essential for anyone performing a procedure to periodically read, and ideally train and test, the corresponding standard operating procedure. Adequate training should be demonstrated before performing a procedure if possible and reasonable.
+
+## History
+
+- 0.0.1 - Initial draft.
+
+## References
+
+1. Hollmann S, Frohme M, Endrullat C, Kremer A, D'Elia D, Regierer B, et al. (2020) Ten simple rules on how to write a standard operating procedure. PLoS Comput Biol 16(9): e1008095. <https://doi.org/10.1371/journal.pcbi.1008095>.
+
+2. Preston-Werner, Tom (2013). Semantic Versioning 2.0.0. Creative Commons. Retrieved from <https://semver.org/spec/v2.0.0.html>.
diff --git a/package.json b/package.json
index f3524555..58759bc1 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "cannlytics",
-  "version": "0.0.26",
+  "version": "0.0.27",
   "license": "MIT",
   "homepage": "https://cannlytics.com",
   "description": "Cannlytics includes a LIMS, a console, documentation, a website, and vast amount of tools for cannabis analytics.",
diff --git a/public/app/favicon.ico b/public/app/favicon.ico
new file mode 100644
index 00000000..1190d9b8
Binary files /dev/null and b/public/app/favicon.ico differ
diff --git a/setup.py b/setup.py
index 91e08fa6..044ca711 100644
--- a/setup.py
+++ b/setup.py
@@ -13,7 +13,7 @@
 # Define the package version.
 # Optional: Read from package.json? Pros and cons to tieing Python
 # package version to the Cannlytics project version.
-version = '0.0.12'
+version = '0.0.13'
 
 # Get the project description.
 README = ''
diff --git a/website/templates/website/pages/data/licenses.html b/website/templates/website/pages/data/licenses.html
new file mode 100644
index 00000000..7377b351
--- /dev/null
+++ b/website/templates/website/pages/data/licenses.html
@@ -0,0 +1,91 @@
+<!doctype html>
+<html>
+<!--
+  Cannabis Licenses Page | Cannlytics Website
+  Copyright (c) 2022 Cannlytics
+
+  Authors: Keegan Skeate <https://github.com/keeganskeate>
+  Created: 9/29/2022
+  Updated: 9/29/2022
+  License: MIT License <https://github.com/cannlytics/cannlytics/blob/main/LICENSE>
+-->
+{% extends "website/index.html" %}
+{% load static icon %}
+{% block title %}Cannabis Licenses | Cannlytics{% endblock %}
+{% block page_css %}
+
+<!-- TODO: Loading JS from the Cannlytics server preferred. -->
+{% comment %} <script src="{% static 'website/plugins/ag-grid/ag-grid-community.min.noStyle.js' %}"></script>
+<link href="{% static 'website/plugins/ag-grid/ag-grid.min.css' %}" rel="stylesheet" type="text/css"/>
+<link href="{% static 'website/plugins/ag-grid/ag-theme-alpine.min.css' %}" rel="stylesheet" type="text/css"/>
+<link href="{% static 'website/plugins/ag-grid/ag-theme-alpine-dark.min.css' %}" rel="stylesheet" type="text/css"/> {% endcomment %}
+ <script src="https://unpkg.com/ag-grid-community/dist/ag-grid-community.min.noStyle.js"></script>
+ <link rel="stylesheet" href="https://unpkg.com/ag-grid-community/styles/ag-grid.css"/>
+ <link rel="stylesheet" href="https://unpkg.com/ag-grid-community/styles/ag-theme-alpine.css"/>
+
+ <!-- TODO: Load alpine-dark without the entire community bundle. -->
+ <script src="https://unpkg.com/ag-grid-community/dist/ag-grid-community.min.js"></script>
+ {% comment %} <link rel="stylesheet" href="https://unpkg.com/ag-grid-community/styles/ag-theme-alpine-dark.min.css"/> {% endcomment %}
+{% endblock page_css %}
+{% block material %}
+
+<!-- Banner
+ - [ ] Add a background picture!
+-->
+<!-- Optional:  -->
+{% include "website/components/heros/hero_banner.html" with
+  title=''
+  description="Get cannabis license data while it's hot!"
+  image_url=''
+  image_size="200px"
+  only
+%}
+
+<!-- Breadcrumbs -->
+<section class="container mb-3">
+  <div class="d-flex mt-4">
+    <nav
+      style="--bs-breadcrumb-divider: '>';"
+      aria-label="breadcrumb"
+    >
+      <ol class="breadcrumb bg-transparent p-0 mb-0">
+        <li class="breadcrumb-item fs-6 lh-sm">
+          <a
+            class="text-dark serif action"
+            href="{% url 'page' page='data' %}"
+          >
+            Data
+          </a>
+        </li>
+        <li class="breadcrumb-item fs-6 lh-sm">
+          <a
+            class="text-dark serif action"
+            href="{% url 'section' page='data' section='licenses' %}"
+          >
+            Licenses
+          </a>
+        </li>
+      </ol>
+      <div>
+    </div>
+    </nav>
+  </div>
+</section>
+
+<!-- Cannabis Licenses -->
+<section class="container markdown">
+
+
+</section>
+
+{% include "website/components/art/vines.html" %}
+{% endblock material %}
+{% block page_js %}
+  <script>
+
+    // Initialize tooltips.
+    // cannlytics.ui.enableTooltips();
+  
+  </script>
+{% endblock %}
+</html>
diff --git a/website/templates/website/pages/data/results.html b/website/templates/website/pages/data/results.html
new file mode 100644
index 00000000..eff1aae7
--- /dev/null
+++ b/website/templates/website/pages/data/results.html
@@ -0,0 +1,91 @@
+<!doctype html>
+<html>
+<!--
+  Cannabis Lab Test Results Page | Cannlytics Website
+  Copyright (c) 2022 Cannlytics
+
+  Authors: Keegan Skeate <https://github.com/keeganskeate>
+  Created: 9/29/2022
+  Updated: 9/29/2022
+  License: MIT License <https://github.com/cannlytics/cannlytics/blob/main/LICENSE>
+-->
+{% extends "website/index.html" %}
+{% load static icon %}
+{% block title %}Cannabis Lab Test Results | Cannlytics{% endblock %}
+{% block page_css %}
+
+<!-- TODO: Loading JS from the Cannlytics server preferred. -->
+{% comment %} <script src="{% static 'website/plugins/ag-grid/ag-grid-community.min.noStyle.js' %}"></script>
+<link href="{% static 'website/plugins/ag-grid/ag-grid.min.css' %}" rel="stylesheet" type="text/css"/>
+<link href="{% static 'website/plugins/ag-grid/ag-theme-alpine.min.css' %}" rel="stylesheet" type="text/css"/>
+<link href="{% static 'website/plugins/ag-grid/ag-theme-alpine-dark.min.css' %}" rel="stylesheet" type="text/css"/> {% endcomment %}
+ <script src="https://unpkg.com/ag-grid-community/dist/ag-grid-community.min.noStyle.js"></script>
+ <link rel="stylesheet" href="https://unpkg.com/ag-grid-community/styles/ag-grid.css"/>
+ <link rel="stylesheet" href="https://unpkg.com/ag-grid-community/styles/ag-theme-alpine.css"/>
+
+ <!-- TODO: Load alpine-dark without the entire community bundle. -->
+ <script src="https://unpkg.com/ag-grid-community/dist/ag-grid-community.min.js"></script>
+ {% comment %} <link rel="stylesheet" href="https://unpkg.com/ag-grid-community/styles/ag-theme-alpine-dark.min.css"/> {% endcomment %}
+{% endblock page_css %}
+{% block material %}
+
+<!-- Banner
+ - [ ] Add a background picture!
+-->
+<!-- Optional:  -->
+{% include "website/components/heros/hero_banner.html" with
+  title=''
+  description="Get cannabis lab test results while they're hot!"
+  image_url=''
+  image_size="200px"
+  only
+%}
+
+<!-- Breadcrumbs -->
+<section class="container mb-3">
+  <div class="d-flex mt-4">
+    <nav
+      style="--bs-breadcrumb-divider: '>';"
+      aria-label="breadcrumb"
+    >
+      <ol class="breadcrumb bg-transparent p-0 mb-0">
+        <li class="breadcrumb-item fs-6 lh-sm">
+          <a
+            class="text-dark serif action"
+            href="{% url 'page' page='data' %}"
+          >
+            Data
+          </a>
+        </li>
+        <li class="breadcrumb-item fs-6 lh-sm">
+          <a
+            class="text-dark serif action"
+            href="{% url 'section' page='data' section='results' %}"
+          >
+            Test Results
+          </a>
+        </li>
+      </ol>
+      <div>
+    </div>
+    </nav>
+  </div>
+</section>
+
+<!-- Cannabis Lab Test Results -->
+<section class="container markdown">
+
+
+</section>
+
+{% include "website/components/art/vines.html" %}
+{% endblock material %}
+{% block page_js %}
+  <script>
+
+    // Initialize tooltips.
+    // cannlytics.ui.enableTooltips();
+  
+  </script>
+{% endblock %}
+</html>
diff --git a/website/views/email.py b/website/views/email.py
index 332f9705..2a797dc5 100644
--- a/website/views/email.py
+++ b/website/views/email.py
@@ -28,6 +28,8 @@ def send_message(request):
     """
 
     # Check if the user passed math input, to prevent spam.
+    # FIXME: Implement ReCaptcha:
+    # https://firebase.google.com/docs/app-check/web/recaptcha-provider
     try:
         request.POST['math_input'] == request.POST['math_total']
     except KeyError:
diff --git a/website/webpack.config.js b/website/webpack.config.js
index 3afac213..eb2e3183 100644
--- a/website/webpack.config.js
+++ b/website/webpack.config.js
@@ -8,6 +8,7 @@
  * License: MIT License <https://github.com/cannlytics/cannlytics-website/blob/main/LICENSE>
 */
 const appName = 'website';
+// const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
 const Dotenv = require('dotenv-webpack');
 const path = require('path');
 const TerserPlugin = require('terser-webpack-plugin');
@@ -31,6 +32,9 @@ module.exports = env => {
       './assets/js/index.js',
       // You can add additional JS here.
     ],
+    // entry: {
+    //   cannlytics: `./assets/js/index.js`,
+    // },
     output: {
       path: path.resolve(__dirname, `./static/${appName}`),
       filename: './js/bundles/cannlytics.min.js',
@@ -41,6 +45,8 @@ module.exports = env => {
     },
     module: {
       rules: [
+
+        // Compiles SCSS to CSS.
         {
           test: /\.s[ac]ss$/i,
           use: [
@@ -59,6 +65,7 @@ module.exports = env => {
                 postcssOptions: {
                   plugins: function () {
                     return [
+                      // require('precss'),
                       require('autoprefixer')
                     ];
                   }
@@ -71,8 +78,9 @@ module.exports = env => {
             },
           ],
         },
+
+        // Convert ES2015 to JavaScript.
         {
-          // Convert ES2015 to JavaScript.
           test: /\.js$/,
           exclude: '/node_modules/',
           loader: 'babel-loader',
@@ -80,19 +88,33 @@ module.exports = env => {
             compact: true,
           },
         },
+
       ],
     },
+
+    // Minimize JavaScript in production.
     optimization: {
-      minimize: env.production, // Minimize JavaScript in production.
-      minimizer: [new TerserPlugin({ parallel: true })],
+      minimize: env.production,
+      minimizer: [
+        new TerserPlugin({ parallel: true }),
+        // new CssMinimizerPlugin({}),
+      ],
     },
+
     // Define maximum optimal bundle-size.
     performance: {
       maxEntrypointSize: 512000 * 4,
       maxAssetSize: 512000 * 4,
     },
+
+    // Useful plugins.
     plugins: [
-      new Dotenv({ path: '../.env' }), // Make .env variables available in entry file.
+
+      // Make .env variables available in the entry file.
+      // WARNING: Any variables used in JavaScript will be compiled.
+      new Dotenv({ path: '../.env' }),
+
     ],
+
   }
 };


Test patch:
diff --git a/console/templates/console/pages/traceability/lab-tests/lab-test.html b/console/templates/console/pages/traceability/lab-tests/lab-test.html
index 72adc73c..a92f4c1d 100644
--- a/console/templates/console/pages/traceability/lab-tests/lab-test.html
+++ b/console/templates/console/pages/traceability/lab-tests/lab-test.html
@@ -165,7 +165,7 @@ <h2 class="fs-6 lh-base text-secondary mb-3">
     <div class="mt-3">
       <a
         class="btn btn-sm btn-secondary me-1"
-        href="/traceability/lab-tests"
+        href="/traceability/results"
       >
         Back
       </a>
diff --git a/console/templates/console/pages/traceability/lab-tests/lab-tests.html b/console/templates/console/pages/traceability/lab-tests/lab-tests.html
index 90672a72..64708dcc 100644
--- a/console/templates/console/pages/traceability/lab-tests/lab-tests.html
+++ b/console/templates/console/pages/traceability/lab-tests/lab-tests.html
@@ -27,7 +27,7 @@ <h2 class="h6 mb-3 text-dark">Lab Tests</h2>
           image='console/images/illustrations/science/lab_books.svg'
           message='No lab tests were found for your active license.'
           title='No Lab Tests Found'
-          url='/traceability/lab-tests/lab-test?new=true'
+          url='/traceability/results/lab-test?new=true'
           only
         %}
       </div>
@@ -35,7 +35,7 @@ <h2 class="h6 mb-3 text-dark">Lab Tests</h2>
       <!-- AG Grid Table -->
       <div id="data-table" class="d-none">
         <div
-          id="lab-tests-table"
+          id="results-table"
           class="ag-theme-alpine-dark"
           style="height: 350px; width:100%;"
         ></div>
@@ -53,7 +53,7 @@ <h2 class="h6 mb-3 text-dark">Lab Tests</h2>
 
   <script>
     cannlytics.traceability.getLabTests(
-      'lab-tests-table',
+      'results-table',
       '{{ organizations.0.uid }}',
       '{{ organizations.0.licenses.0.license_number }}',
       '{{ organizations.0.licenses.0.user_api_key_secret.version_id }}',
diff --git a/tests/assets/coas/Peanutbutter Breath.pdf b/tests/assets/coas/anresco-laboratories/Peanutbutter Breath.pdf
similarity index 100%
rename from tests/assets/coas/Peanutbutter Breath.pdf
rename to tests/assets/coas/anresco-laboratories/Peanutbutter Breath.pdf
diff --git a/tests/assets/coas/210000525-Citrus-Slurm-Diamonds.pdf b/tests/assets/coas/cannalysis/Citrus-Slurm-Diamonds.pdf
similarity index 100%
rename from tests/assets/coas/210000525-Citrus-Slurm-Diamonds.pdf
rename to tests/assets/coas/cannalysis/Citrus-Slurm-Diamonds.pdf
diff --git a/tests/assets/coas/210000068-Cloud-Cake-1g.pdf b/tests/assets/coas/cannalysis/Cloud-Cake-Concentrate.pdf
similarity index 100%
rename from tests/assets/coas/210000068-Cloud-Cake-1g.pdf
rename to tests/assets/coas/cannalysis/Cloud-Cake-Concentrate.pdf
diff --git a/tests/assets/coas/MothersMilk.pdf b/tests/assets/coas/cannalysis/Mothers-Milk-Flower.pdf
similarity index 100%
rename from tests/assets/coas/MothersMilk.pdf
rename to tests/assets/coas/cannalysis/Mothers-Milk-Flower.pdf
diff --git a/tests/assets/coas/G130921-Koi-CBD-Complete-Full-Spectrum-Gummies-Grape.pdf b/tests/assets/coas/cannasafe/Grape-CBD-Gummies.pdf
similarity index 100%
rename from tests/assets/coas/G130921-Koi-CBD-Complete-Full-Spectrum-Gummies-Grape.pdf
rename to tests/assets/coas/cannasafe/Grape-CBD-Gummies.pdf
diff --git a/tests/assets/coas/Classic Jack.pdf b/tests/assets/coas/confident-cannabis/Classic Jack.pdf
similarity index 100%
rename from tests/assets/coas/Classic Jack.pdf
rename to tests/assets/coas/confident-cannabis/Classic Jack.pdf
diff --git a/tests/assets/coas/confident-cannabis/Elyxr-Gummy.pdf b/tests/assets/coas/confident-cannabis/Elyxr-Gummy.pdf
new file mode 100644
index 00000000..8fbf92cb
Binary files /dev/null and b/tests/assets/coas/confident-cannabis/Elyxr-Gummy.pdf differ
diff --git a/tests/assets/coas/GDP.pdf b/tests/assets/coas/confident-cannabis/GDP.pdf
similarity index 100%
rename from tests/assets/coas/GDP.pdf
rename to tests/assets/coas/confident-cannabis/GDP.pdf
diff --git a/tests/assets/coas/HGN_SOUR .pdf b/tests/assets/coas/confident-cannabis/HGN_SOUR .pdf
similarity index 100%
rename from tests/assets/coas/HGN_SOUR .pdf
rename to tests/assets/coas/confident-cannabis/HGN_SOUR .pdf
diff --git a/tests/assets/coas/confident-cannabis/Purple-Punch.pdf b/tests/assets/coas/confident-cannabis/Purple-Punch.pdf
new file mode 100644
index 00000000..9a0ddf1d
Binary files /dev/null and b/tests/assets/coas/confident-cannabis/Purple-Punch.pdf differ
diff --git a/tests/assets/coas/MagicMelon.pdf b/tests/assets/coas/greenleaf-lab/MagicMelon.pdf
similarity index 100%
rename from tests/assets/coas/MagicMelon.pdf
rename to tests/assets/coas/greenleaf-lab/MagicMelon.pdf
diff --git a/tests/assets/coas/Raspberry Parfait.pdf b/tests/assets/coas/greenleaf-lab/Raspberry Parfait.pdf
similarity index 100%
rename from tests/assets/coas/Raspberry Parfait.pdf
rename to tests/assets/coas/greenleaf-lab/Raspberry Parfait.pdf
diff --git a/tests/assets/coas/kaycha-labs/Elyxer-Flower.pdf b/tests/assets/coas/kaycha-labs/Elyxer-Flower.pdf
new file mode 100644
index 00000000..3ff827b0
Binary files /dev/null and b/tests/assets/coas/kaycha-labs/Elyxer-Flower.pdf differ
diff --git a/tests/assets/coas/kca-labs/Elyxer-Awake-Gummies.pdf b/tests/assets/coas/kca-labs/Elyxer-Awake-Gummies.pdf
new file mode 100644
index 00000000..2dadc09d
Binary files /dev/null and b/tests/assets/coas/kca-labs/Elyxer-Awake-Gummies.pdf differ
diff --git a/tests/assets/coas/kca-labs/Elyxer-Gummies.pdf b/tests/assets/coas/kca-labs/Elyxer-Gummies.pdf
new file mode 100644
index 00000000..6e79dc72
Binary files /dev/null and b/tests/assets/coas/kca-labs/Elyxer-Gummies.pdf differ
diff --git a/tests/assets/coas/kca-labs/Tangie.pdf b/tests/assets/coas/kca-labs/Tangie.pdf
new file mode 100644
index 00000000..c80d385b
Binary files /dev/null and b/tests/assets/coas/kca-labs/Tangie.pdf differ
diff --git a/tests/assets/coas/mcr-labs/Bulk 100mg Milk Chocolate Drops.PDF b/tests/assets/coas/mcr-labs/Bulk 100mg Milk Chocolate Drops.PDF
new file mode 100644
index 00000000..7014deb5
Binary files /dev/null and b/tests/assets/coas/mcr-labs/Bulk 100mg Milk Chocolate Drops.PDF differ
diff --git a/tests/assets/coas/mcr-labs/Report S21-59483 Gummy - Lemon CL Classic 30mg-GUM.30.CCLM.210920.4.PDF b/tests/assets/coas/mcr-labs/Report S21-59483 Gummy - Lemon CL Classic 30mg-GUM.30.CCLM.210920.4.PDF
new file mode 100644
index 00000000..dcaa44b6
Binary files /dev/null and b/tests/assets/coas/mcr-labs/Report S21-59483 Gummy - Lemon CL Classic 30mg-GUM.30.CCLM.210920.4.PDF differ
diff --git a/tests/assets/coas/mcr-labs/Report S21-73903 100mg White Chocolate Drop-Party Cake (11532)-THC-PARTYWCD-112321 Rev.02.00 (1).PDF b/tests/assets/coas/mcr-labs/Report S21-73903 100mg White Chocolate Drop-Party Cake (11532)-THC-PARTYWCD-112321 Rev.02.00 (1).PDF
new file mode 100644
index 00000000..85ca0094
Binary files /dev/null and b/tests/assets/coas/mcr-labs/Report S21-73903 100mg White Chocolate Drop-Party Cake (11532)-THC-PARTYWCD-112321 Rev.02.00 (1).PDF differ
diff --git a/tests/assets/coas/mcr-labs/Report S21-77853 1906 Genius - GNSDMA21331.PDF b/tests/assets/coas/mcr-labs/Report S21-77853 1906 Genius - GNSDMA21331.PDF
new file mode 100644
index 00000000..e742e20d
Binary files /dev/null and b/tests/assets/coas/mcr-labs/Report S21-77853 1906 Genius - GNSDMA21331.PDF differ
diff --git a/tests/assets/coas/mcr-labs/Watermelon 30mg Gummy.PDF b/tests/assets/coas/mcr-labs/Watermelon 30mg Gummy.PDF
new file mode 100644
index 00000000..da797a0d
Binary files /dev/null and b/tests/assets/coas/mcr-labs/Watermelon 30mg Gummy.PDF differ
diff --git a/tests/assets/coas/mist.pdf b/tests/assets/coas/mist.pdf
deleted file mode 100644
index 95ff677d..00000000
Binary files a/tests/assets/coas/mist.pdf and /dev/null differ
diff --git a/tests/assets/coas/Cherry Punch.pdf b/tests/assets/coas/sc-labs/Cherry Punch.pdf
similarity index 100%
rename from tests/assets/coas/Cherry Punch.pdf
rename to tests/assets/coas/sc-labs/Cherry Punch.pdf
diff --git a/tests/assets/coas/Mattole Valley Jack H.pdf b/tests/assets/coas/sc-labs/Mattole Valley Jack H.pdf
similarity index 100%
rename from tests/assets/coas/Mattole Valley Jack H.pdf
rename to tests/assets/coas/sc-labs/Mattole Valley Jack H.pdf
diff --git a/tests/assets/coas/LemonTree.pdf b/tests/assets/coas/sonoma-labworks/LemonTree.pdf
similarity index 100%
rename from tests/assets/coas/LemonTree.pdf
rename to tests/assets/coas/sonoma-labworks/LemonTree.pdf
diff --git a/tests/assets/coas/steep-hill/111421GLCM17-1-PR.pdf b/tests/assets/coas/steep-hill/111421GLCM17-1-PR.pdf
new file mode 100644
index 00000000..a329002c
Binary files /dev/null and b/tests/assets/coas/steep-hill/111421GLCM17-1-PR.pdf differ
diff --git a/tests/assets/coas/steep-hill/210909CC.F8-1-D.pdf b/tests/assets/coas/steep-hill/210909CC.F8-1-D.pdf
new file mode 100644
index 00000000..39f6273c
Binary files /dev/null and b/tests/assets/coas/steep-hill/210909CC.F8-1-D.pdf differ
diff --git a/tests/assets/coas/steep-hill/211007OGG.F1-1-PR-D.pdf b/tests/assets/coas/steep-hill/211007OGG.F1-1-PR-D.pdf
new file mode 100644
index 00000000..59f84144
Binary files /dev/null and b/tests/assets/coas/steep-hill/211007OGG.F1-1-PR-D.pdf differ
diff --git a/tests/assets/coas/steep-hill/211014MLK.F21-4-D.pdf b/tests/assets/coas/steep-hill/211014MLK.F21-4-D.pdf
new file mode 100644
index 00000000..39bf91c0
Binary files /dev/null and b/tests/assets/coas/steep-hill/211014MLK.F21-4-D.pdf differ
diff --git a/tests/assets/coas/steep-hill/211230LL.F28-1-D.pdf b/tests/assets/coas/steep-hill/211230LL.F28-1-D.pdf
new file mode 100644
index 00000000..d0e55fc9
Binary files /dev/null and b/tests/assets/coas/steep-hill/211230LL.F28-1-D.pdf differ
diff --git a/tests/assets/coas/steep-hill/211230TB.F28-3-D.pdf b/tests/assets/coas/steep-hill/211230TB.F28-3-D.pdf
new file mode 100644
index 00000000..d7b52d90
Binary files /dev/null and b/tests/assets/coas/steep-hill/211230TB.F28-3-D.pdf differ
diff --git a/tests/assets/coas/steep-hill/220203HOG.F23-5-D.pdf b/tests/assets/coas/steep-hill/220203HOG.F23-5-D.pdf
new file mode 100644
index 00000000..46942674
Binary files /dev/null and b/tests/assets/coas/steep-hill/220203HOG.F23-5-D.pdf differ
diff --git a/tests/assets/coas/steep-hill/220217RRZ.F21-4-D.pdf b/tests/assets/coas/steep-hill/220217RRZ.F21-4-D.pdf
new file mode 100644
index 00000000..956696f5
Binary files /dev/null and b/tests/assets/coas/steep-hill/220217RRZ.F21-4-D.pdf differ
diff --git a/tests/assets/coas/steep-hill/220407SLU.F23-1-D.pdf b/tests/assets/coas/steep-hill/220407SLU.F23-1-D.pdf
new file mode 100644
index 00000000..f07a0f60
Binary files /dev/null and b/tests/assets/coas/steep-hill/220407SLU.F23-1-D.pdf differ
diff --git a/tests/assets/coas/steep-hill/220427JS.F4-1-D.pdf b/tests/assets/coas/steep-hill/220427JS.F4-1-D.pdf
new file mode 100644
index 00000000..18b98ac8
Binary files /dev/null and b/tests/assets/coas/steep-hill/220427JS.F4-1-D.pdf differ
diff --git a/tests/assets/coas/steep-hill/220519GR.F5-1-PR-D.pdf b/tests/assets/coas/steep-hill/220519GR.F5-1-PR-D.pdf
new file mode 100644
index 00000000..97e838ef
Binary files /dev/null and b/tests/assets/coas/steep-hill/220519GR.F5-1-PR-D.pdf differ
diff --git a/tests/assets/coas/steep-hill/Dosi-Woah.pdf b/tests/assets/coas/steep-hill/Dosi-Woah.pdf
new file mode 100644
index 00000000..2418d3eb
Binary files /dev/null and b/tests/assets/coas/steep-hill/Dosi-Woah.pdf differ
diff --git a/tests/assets/coas/steep-hill/Lambs-Bread.pdf b/tests/assets/coas/steep-hill/Lambs-Bread.pdf
new file mode 100644
index 00000000..c4387747
Binary files /dev/null and b/tests/assets/coas/steep-hill/Lambs-Bread.pdf differ
diff --git a/tests/assets/coas/steep-hill/Lemon-Afghani.pdf b/tests/assets/coas/steep-hill/Lemon-Afghani.pdf
new file mode 100644
index 00000000..aab313f4
Binary files /dev/null and b/tests/assets/coas/steep-hill/Lemon-Afghani.pdf differ
diff --git a/tests/assets/coas/steep-hill/Monkey-Mints.pdf b/tests/assets/coas/steep-hill/Monkey-Mints.pdf
new file mode 100644
index 00000000..5c110a75
Binary files /dev/null and b/tests/assets/coas/steep-hill/Monkey-Mints.pdf differ
diff --git a/tests/assets/coas/steep-hill/RTD.OMJ.220623.pdf b/tests/assets/coas/steep-hill/RTD.OMJ.220623.pdf
new file mode 100644
index 00000000..bd24c4ec
Binary files /dev/null and b/tests/assets/coas/steep-hill/RTD.OMJ.220623.pdf differ
diff --git a/tests/assets/coas/steep-hill/SEL.SL.220601.pdf b/tests/assets/coas/steep-hill/SEL.SL.220601.pdf
new file mode 100644
index 00000000..53b5ab77
Binary files /dev/null and b/tests/assets/coas/steep-hill/SEL.SL.220601.pdf differ
diff --git a/tests/assets/coas/Sunbeam.pdf b/tests/assets/coas/tagleaf/Sunbeam.pdf
similarity index 100%
rename from tests/assets/coas/Sunbeam.pdf
rename to tests/assets/coas/tagleaf/Sunbeam.pdf
diff --git a/tests/assets/coas/Veda Scientific Sample COA.pdf b/tests/assets/coas/veda-scientific/Veda Scientific Sample COA.pdf
similarity index 100%
rename from tests/assets/coas/Veda Scientific Sample COA.pdf
rename to tests/assets/coas/veda-scientific/Veda Scientific Sample COA.pdf
diff --git a/tests/assets/qr-codes/steep-hill-qr-code.jpg b/tests/assets/qr-codes/steep-hill-qr-code.jpg
new file mode 100644
index 00000000..c1f5a424
Binary files /dev/null and b/tests/assets/qr-codes/steep-hill-qr-code.jpg differ