Repository: callahantiff/PheKnowLator (language: python)

Issue:
Determine KG Baseline Format
### TASK  
**Task Type:** [`CODEBASE`](https://github.com/callahantiff/PheKnowLator/projects/8#card-51636107)  
 
Re-think what should be included in the base set of merged ontologies prior to adding edge data.  


#### TODO  
- [x] Determine how to handle merged ontologies (base KG)  
  - Process base KG to remove all node and edge metadata by removing it and storing it  
  - Remove all annotation properties and include only those triples needed for reasoning  
  - Remove all dead classes, some of this will be done in #73  
- [x] Update KG build code to match this plan  
Enhancement: Improve Networkx MultiDiGraph Metadata
### TASK  
**Task Type:** [`CODEBASE`](https://github.com/callahantiff/PheKnowLator/projects/8#card-51636107)

Improve the node and edge metadata when outputting the Networkx MultiDiGraph versions of each build. Thanks to @rkboyce, who suggested that we could make very small changes to the current Network graph and drastically improve the usability of the output structure. 


#### TODO   
**Impacted Scripts:** 
- `knowledge_graph.py`
- `converts_rdflib_to_networkx()` in `utils/kg_utils.py`
 
**Needed Functionality:** 
- [x] Add a helper function to `utils/kg_utils.py` that can be called by `converts_rdflib_to_networkx()`. The helper function will set graph attributes for edges: 
  - `key`: a unique value for each predicate with respect to the triple it appears in, could be a hash of the triple. Just need to ensure that it is unique  
  - `weight`: default to 0
 
___
 
@rkboyce, can you please verify that I have covered the needed changes that we discussed this week correctly above?
 
I will also be implementing a few changes to the `OWL-NETS` architecture (issue #56)  and will be storing the collapsed semantic information from the full graph as attributes of the transformed `OWL-NETS` graph, likely in the form of edge and and node dictionary entries.
Determine KG Baseline Format
### TASK  
**Task Type:** [`CODEBASE`](https://github.com/callahantiff/PheKnowLator/projects/8#card-51636107)  
 
Re-think what should be included in the base set of merged ontologies prior to adding edge data.  


#### TODO  
- [x] Determine how to handle merged ontologies (base KG)  
  - Process base KG to remove all node and edge metadata by removing it and storing it  
  - Remove all annotation properties and include only those triples needed for reasoning  
  - Remove all dead classes, some of this will be done in #73  
- [x] Update KG build code to match this plan  


Discussion / hints:

Hi @callahantiff - I agree with the summary for the most part. My suggestion is to make the key some identifier unique across the knowledge graph. Could be just an incremented integer unique to each relation with respect to the triple that it occurs in. I like to use 'predicate' for the URIRef that represents the edge relationship (which will likely be from an ontology e.g. RO and not unique), and weight should be 0.0 as you indicated. 
Thanks so much @rkboyce, that's exactly what I needed to know!


Fix patch:
diff --git a/.github/workflows/build-qa.yml b/.github/workflows/build-qa.yml
index 70799ef5c..723365029 100644
--- a/.github/workflows/build-qa.yml
+++ b/.github/workflows/build-qa.yml
@@ -49,12 +49,12 @@ jobs:
         env:
           CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }}
       - name: Coveralls
-        run: coveralls
+        run: coveralls --service=github
         env:
             GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
 
   publish_docker_container:
-    if: github.event_name == 'release'
+    if: github.event_name == 'release' && startsWith(github.ref, 'refs/tags')
     needs: build
     name: Push Docker Image to Docker Hub
     runs-on: ubuntu-latest
@@ -76,3 +76,17 @@ jobs:
             tags: callahantiff/pheknowlator:latest
       - name: Image Digest
         run: echo ${{ steps.docker_build.outputs.digest }}
+
+  publish_pypi_library:
+    if: github.event_name == 'release' && startsWith(github.ref, 'refs/tags')
+    needs: build
+    name: Publishes pkt_kg to PyPI
+    runs-on: ubuntu-latest
+    steps:
+      - name: Publish package
+        if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
+        uses: pypa/gh-action-pypi-publish@master
+        with:
+          user: __token__
+          password: ${{ secrets.PYPI_API_TOKEN }}
+          verbose: true
diff --git a/.gitignore b/.gitignore
index 525f89bb8..f86b9d166 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,6 @@
 # Created by .ignore support plugin (hsz.mobi)
 
-# Distribution/packaging/Environments
+#### Distribution/Packaging/Environments
 \.DS_Store
 .idea/
 .ipynb_checkpoints/*
@@ -9,7 +9,31 @@ __pycache__
 __pycache__/
 **/__pycache__/*
 
-# data
+#### Testing
+.single_run
+.coverage*
+coverage.xml
+.notipy.json
+\.vscode/
+/tests/archive/
+
+#### CI/CD Builds
+builds/temp/*
+
+
+#### API Keys/Project Passwords
+/resources/project_keys/*
+
+#### External Libraries
+pkt_kg/libs/deepwalk_c_master/*
+pkt_kg/libs/walking-rdf-and-owl-master/*
+
+#### Scripts
+pkt_kg/kg_embedding_visualizer.py
+pkt_kg/knowledge_graph_embedder.py
+scratch*.py
+
+#### Data and Directories
 /resources/*.json
 /resources/construction_approach/*
 /resources/edge_data/*
@@ -22,17 +46,11 @@ __pycache__/
 /resources/processed_data/*
 /resources/relations_data/*
 
-# scripts (in development or scratch)
-pkt_kg/kg_embedding_visualizer.py
-pkt_kg/knowledge_graph_embedder.py
-scratch*.py
-
-# ignoring resource preparation files and metadata information
+## Dependency Files
 !/resources/construction_approach/README.md
 !/resources/edge_data/edge_source_metadata.txt
 !/resources/edge_data/README.md
 !/resources/embeddings/README.md
-!/resources/knowledge_graphs/OWL_NETS_Property_Types.txt
 !/resources/knowledge_graphs/README.md
 !/resources/node_data/README.md
 !/resources/ontologies/ontology_source_metadata.txt
@@ -40,26 +58,5 @@ scratch*.py
 !/resources/owl_decoding/README.md
 !/resources/relations_data/README.md
 
-# releases
-/releases/*
-
-# api keys/passwords
-/resources/bioportal_api_key.txt
-
-# external libraries
-pkt_kg/libs/deepwalk_c_master/*
-pkt_kg/libs/walking-rdf-and-owl-master/*
-
-# testing files
-.single_run
-.coverage*
-coverage.xml
-.notipy.json
-\.vscode/
-/tests/archive/
-
-# project api keys
-/resources/project_keys/*
-
-# builds data
-builds/temp/*
\ No newline at end of file
+## Releases
+/releases/*
\ No newline at end of file
diff --git a/Data_Preparation.ipynb b/Data_Preparation.ipynb
index c8073fe9a..1d14032f2 100644
--- a/Data_Preparation.ipynb
+++ b/Data_Preparation.ipynb
@@ -2402,10 +2402,10 @@
     "    for k, v in tqdm(disease_dict.items()):\n",
     "        if any(x for x in v if x.startswith('MONDO')):\n",
     "            for idx in [x.replace(':', '_') for x in v if 'MONDO' in x]:\n",
-    "                outfile1.write(k.upper() + '\\t' + idx + '\\n')\n",
+    "                outfile1.write(k.upper().split(':')[-1] + '\\t' + idx + '\\n')\n",
     "        if any(x for x in v if x.startswith('HP')):\n",
     "            for idx in [x.replace(':', '_') for x in v if 'HP' in x]:\n",
-    "                outfile2.write(k.upper() + '\\t' + idx + '\\n')"
+    "                outfile2.write(k.upper().split(':')[-1]  + '\\t' + idx + '\\n')"
    ]
   },
   {
@@ -3856,9 +3856,26 @@
     "  - <u>Input Datasets</u>:  \n",
     "    - Genes ➞ `Homo_sapiens.gene_info`   \n",
     "    - RNA ➞ `ensembl_identifier_data_cleaned.txt` \n",
-    "    - Pathways ➞ [`reactome2py API`](https://github.com/reactome/reactome2py) ; `ReactomePathways.txt`    \n",
+    "    - Pathways ➞ [`reactome2py API`](https://github.com/reactome/reactome2py) ; `ReactomePathways.txt`; `gene_association.reactome.gz`; `ChEBI2Reactome_All_Levels.txt`; `kegg_reactome.csv`   \n",
     "    - Variants ➞ `variant_summary.txt`  \n",
     "    - Relations ➞ `ro_with_imports.owl`  \n",
+    "    \n",
+    "Example Metadata Dictionary Output:\n",
+    "\n",
+    "```python\n",
+    "{\n",
+    "    'nodes': {\n",
+    "        'http://www.ncbi.nlm.nih.gov/gene/1': {\n",
+    "            'Label': 'A1BG',\n",
+    "            'Description': \"A1BG has locus group protein-coding' and is located on chromosome 19 (19q13.43).\",\n",
+    "            'Synonym': 'HYST2477alpha-1B-glycoprotein|HEL-S-163pA|ABG|A1B|GAB'} ... },\n",
+    "    'relations': {\n",
+    "        'http://purl.obolibrary.org/obo/RO_0002533': {\n",
+    "            'Label': 'sequence atomic unit',\n",
+    "            'Description': 'Any individual unit of a collection of like units arranged in a linear order',\n",
+    "            'Synonym': 'None'} ... }\n",
+    "}\n",
+    "```\n",
     "\n",
     "<br>\n",
     "\n",
@@ -4086,7 +4103,7 @@
     "\n",
     "#### Pathway Metadata Dictionary <a class=\"anchor\" id=\"pathway-metadata\"></a>  \n",
     "\n",
-    "The nested dictionary of pathway metadata is created by looping over the human [Reactome Pathway Database](https://github.com/callahantiff/PheKnowLator/wiki/v2-Data-Sources#reactome-pathway-database) identifier data set (`ReactomePathways.txt`). The `keys` of the dictionary are `Reactome identifiers` and the `values` are dictionaries for each metadata type."
+    "The nested dictionary of pathway metadata is created by looping over the human [Reactome Pathway Database](https://github.com/callahantiff/PheKnowLator/wiki/v2-Data-Sources#reactome-pathway-database) identifier data set (`ReactomePathways.txt`); Reactome-Gene Association data (`gene_association.reactome.gz`), and Reactome-ChEBI data (`ChEBI2Reactome_All_Levels.txt`). The `keys` of the dictionary are `Reactome identifiers` and the `values` are dictionaries for each metadata type."
    ]
   },
   {
@@ -4095,14 +4112,31 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "# download data\n",
+    "# download reactome pathways data\n",
     "url = 'https://reactome.org/download/current/ReactomePathways.txt'\n",
     "if not os.path.exists(unprocessed_data_location + 'ReactomePathways.txt'):\n",
     "    data_downloader(url, unprocessed_data_location)\n",
-    "\n",
     "# load data\n",
     "reactome_pathways = pandas.read_csv(unprocessed_data_location + 'ReactomePathways.txt', header=None, delimiter='\\t', low_memory=False)\n",
-    "reactome_pathways = reactome_pathways.loc[reactome_pathways[2].apply(lambda x: x == 'Homo sapiens')] "
+    "reactome_pathways = reactome_pathways.loc[reactome_pathways[2].apply(lambda x: x == 'Homo sapiens')] \n",
+    "\n",
+    "# reactome gene association data\n",
+    "url = 'https://reactome.org/download/current/gene_association.reactome.gz'\n",
+    "if not os.path.exists(unprocessed_data_location + 'gene_association.reactome'):\n",
+    "    data_downloader(url, unprocessed_data_location)\n",
+    "# load data\n",
+    "reactome_pathways2 = pandas.read_csv(unprocessed_data_location + 'gene_association.reactome', header=None, delimiter='\\t', skiprows=1, low_memory=False)\n",
+    "reactome_pathways2 = reactome_pathways2.loc[reactome_pathways2[12].apply(lambda x: x == 'taxon:9606')]\n",
+    "reactome_pathways2[5].replace('REACTOME:','', inplace=True, regex=True) \n",
+    "\n",
+    "# reactome CHEBI data\n",
+    "url = 'https://reactome.org/download/current/ChEBI2Reactome_All_Levels.txt'\n",
+    "if not os.path.exists(unprocessed_data_location + 'ChEBI2Reactome_All_Levels.txt'):\n",
+    "    data_downloader(url, unprocessed_data_location)\n",
+    "# load data\n",
+    "reactome_pathways3 = pandas.read_csv(unprocessed_data_location + 'ChEBI2Reactome_All_Levels.txt', header=None, delimiter='\\t', low_memory=False)\n",
+    "# remove all non-human pathways and save as list\n",
+    "reactome_pathways3 = reactome_pathways3.loc[reactome_pathways3[5].apply(lambda x: x == 'Homo sapiens')] "
    ]
   },
   {
@@ -4112,7 +4146,7 @@
    "outputs": [],
    "source": [
     "# get metadata\n",
-    "nodes = set(list(reactome_pathways[0]))\n",
+    "nodes = set(list(reactome_pathways[0]) + list(reactome_pathways2[5]) + list(reactome_pathways3[1]))\n",
     "pathway_metadata_final = metadata_api_mapper(list(nodes))\n",
     "\n",
     "# update dictionary\n",
@@ -4200,11 +4234,11 @@
    "outputs": [],
    "source": [
     "# combine all metadata dictionaries\n",
-    "master_metadata_dictionary = {**gene_metadata_dict,\n",
-    "                              **rna_metadata_dict,\n",
-    "                              **var_metadata_dict,\n",
-    "                              **pathway_metadata_dict,\n",
-    "                              **relation_metadata_dict}\n",
+    "master_metadata_dictionary = {'nodes': {**gene_metadata_dict,\n",
+    "                                        **rna_metadata_dict,\n",
+    "                                        **var_metadata_dict,\n",
+    "                                        **pathway_metadata_dict},\n",
+    "                              'relations': relation_metadata_dict}\n",
     "\n",
     "# save dictionary locally\n",
     "pickle.dump(master_metadata_dictionary, open(node_data_location + 'node_metadata_dict.pkl', 'wb'), protocol=4)"
diff --git a/Main.py b/Main.py
index 6f07ecd65..1f7b7d382 100644
--- a/Main.py
+++ b/Main.py
@@ -43,6 +43,7 @@ def main():
     print('\n' + '=' * 33 + '\nDOWNLOADING DATA: ONTOLOGY DATA\n' + '=' * 33 + '\n')
     start = time.time()
     ont = OntData(data_path=args.onts, resource_data=args.res)
+    # ont = OntData(data_path='resources/ontology_source_list.txt', resource_data='resources/resource_info.txt')
     ont.downloads_data_from_url()
     end = time.time()
     timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
@@ -52,6 +53,7 @@ def main():
     print('\n' + '=' * 33 + '\nDOWNLOADING DATA: CLASS DATA\n' + '=' * 33 + '\n')
     start = time.time()
     ent = LinkedData(data_path=args.edg, resource_data=args.res)
+    # ent = LinkedData(data_path='resources/edge_source_list.txt', resource_data='resources/resource_info.txt')
     ent.downloads_data_from_url()
     end = time.time()
     timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
@@ -65,6 +67,7 @@ def main():
     start = time.time()
     combined_edges = dict(ent.data_files, **ont.data_files)
     master_edges = CreatesEdgeList(data_files=combined_edges, source_file=args.res)
+    # master_edges = CreatesEdgeList(data_files=combined_edges, source_file='resources/resource_info.txt')
     master_edges.creates_knowledge_graph_edges()
     end = time.time()
     timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
diff --git a/Ontology_Cleaning.ipynb b/Ontology_Cleaning.ipynb
index 0301f6cb0..9678555d2 100644
--- a/Ontology_Cleaning.ipynb
+++ b/Ontology_Cleaning.ipynb
@@ -23,19 +23,138 @@
     "## Assumptions and Dependencies  \n",
     "  \n",
     "**Assumptions:**   \n",
-    "- Directory of Imported Ontologies has been populated ➞ `./resources/ontologies`    \n",
-    "- Processed data write location established ➞ `./resources/ontologies`  \n",
+    "- Directory of Imported Ontologies has been populated ➞ `./resources/ontologies`     \n",
     "\n",
     "**Dependencies:**   \n",
     "- <u>Scripts</u>: This notebook utilizes several helper functions, which are stored in [utility scripts](https://github.com/callahantiff/PheKnowLator/blob/master/pkt_kg/utils) and the main code to perform the error checks in the  [`builds/temp/ontology_cleaning.py`](https://github.com/callahantiff/PheKnowLator/blob/master/builds/ontology_cleaning.py) script.\n",
     "- <u>Software</u>:[`OWLTools`](https://github.com/owlcollab/owltools)  \n",
-    "- <u>Data</u>: [`Merged_gene_rna_protein_identifiers.pkl`](https://storage.googleapis.com/pheknowlator/release_v2.0.0/build_31DEC2020/data/processed_data/Merged_gene_rna_protein_identifiers.pkl), which is automatically downloaded to the `./resources/ontologies` directory     \n",
+    "- <u>Data</u>: [`Merged_gene_rna_protein_identifiers.pkl`](https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/processed_data/Merged_gene_rna_protein_identifiers.pkl), which is automatically downloaded to the `./resources/ontologies` directory     \n",
     "\n",
     "<br>\n",
     "\n",
     "Details on the data utilized in this script can be found on the [Data Sources](https://github.com/callahantiff/PheKnowLator/wiki/v2-Data-Sources) Wiki. Data can be downloaded from [this](https://console.cloud.google.com/storage/browser/pheknowlator/release_v2.0.0?project=pheknowlator) dedicated Google Cloud Storage Bucket. Please note that all build data are freely available and organized by release and build date. "
    ]
   },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "<br>\n",
+    "\n",
+    "***\n",
+    "## CLEAN ONTOLOGIES <a class=\"anchor\" id=\"clean-ontologies\"></a>\n",
+    "***\n",
+    "\n",
+    "The ontology cleaning step includes the following error checks, each of which are explained below and each of which are applied to individual ontologies, the set of merged ontologies or both: (1) Value Errors, (2) Identifier Errors, (3) Duplicate and Obsolete Entities, (4) Punning Errors, and (5) Entity Normalization Errors.\n",
+    "\n",
+    "<br>\n",
+    "\n",
+    "### Value Errors  \n",
+    "*** \n",
+    "**Level:** `individual ontology`; `merged-ontology`    \n",
+    "\n",
+    "**Description:** This check utilizes the [`owlready2`](https://pypi.org/project/Owlready2/) library to read in each of the ontologies. This library is strict and will catch a wide variety of value errors. \n",
+    "\n",
+    "**Solution:** Parse the error message using the provided `ErrorType` and line number and repair it. For `ValueErrors` incorrectly typed input are re-typed.\n",
+    "\n",
+    "*Example Findings*  \n",
+    "The [Cell Line Ontology](http://www.clo-ontology.org/) yield the following error message:\n",
+    "\n",
+    "```python\n",
+    "ValueError: invalid literal for int() with base 10: '永生的乳腺衍生细胞系细胞'\n",
+    "...\n",
+    "OwlReadyOntologyParsingError: RDF/XML parsing error in file clo_with_imports.owl, line 10970, column 99.\n",
+    "```\n",
+    "\n",
+    "This tells us that we need to repair the triple containing the Literal '永生的乳腺衍生细胞系细胞' by removing it and redefining it as a `string`, rather than an `int` as it is currently defined as. This is currently noted as an issue in the [Cell Line Ontology's](http://www.clo-ontology.org/) GitHub repo ([issue #48](https://github.com/CLO-ontology/CLO/issues/48)). \n",
+    "\n",
+    "<br>\n",
+    "\n",
+    "### Identifier Errors  \n",
+    "*** \n",
+    "**Level:** `individual ontology`; `merged-ontology`  \n",
+    "\n",
+    "**Description:** This check verifies consistency of identifier prefixes. For example, we want to find identifiers that are incorrectly formatted like occurrences of `PRO_XXXXXXX` which should be `PR_XXXXXXX`.\n",
+    "\n",
+    "**Solution:** Incorrectly formatted class identifiers are updated. This is a tricky task to do in an automated manner and is something that should be updated if any new ontologies are added to the `PheKnowLator` build. Currently, the code below checks and logs any hits, but only fixes the following known errors: Vaccine Ontology: `PRO` which should be `PR`.\n",
+    "\n",
+    "*Example Findings*  \n",
+    "Running this check revealed mislabeling of `2` [pROtein Ontology](https://proconsortium.org/) identifiers in the [Vaccine Ontology](http://www.violinet.org/vaccineontology/) (see [this](https://github.com/vaccineontology/VO/issues/4) GitHub issue).\n",
+    "\n",
+    "<br>\n",
+    "\n",
+    "### Obsolete and/or Deprecated Entities\n",
+    "*** \n",
+    "**Level:** `individual ontology`; `merged-ontology`  \n",
+    "\n",
+    "**Description:** Verify that the ontology only contains current content.\n",
+    "\n",
+    "**Solution:** All obsolete classes and any triples that they participate in are removed from an ontologies.\n",
+    "\n",
+    "<br>\n",
+    "\n",
+    "### Normalization Errors  \n",
+    "*** \n",
+    "**Level:** `merged-ontology`\n",
+    "\n",
+    "These checks are performed at the individual- and merged-ontology levels. There are two types of checks that are performed:  \n",
+    "\n",
+    "<u>Normalize Existing Ontology Classes</u>  \n",
+    "  - **Description:** Checks for inconsistencies in ontology classes that overlap with non-ontology entity identifiers (e.g. if HP includes `HGNC` identifiers, but PheKnowLator utilizes `Entrez` identifiers). \n",
+    "\n",
+    "  - **Solution:** While there are other types of identifiers, we currently focus primarily on resolving errors involving the genomic identifiers, since we have a master dictionary we can use([`Merged_gene_rna_protein_identifiers.pkl`](https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/processed_data/Merged_gene_rna_protein_identifiers.pkl)). This check can be updated in future iterations to include other types of identifiers, but given our detailed examination of the `v2.0.0` ontologies, these were the identifier types that needed repair.\n",
+    "\n",
+    "<u>Normalize Duplicate Ontology Concepts</u>  \n",
+    "  - **Description:** Make sure that all classes that represent the same entity are connected to each other. For example, consider the following: the [Sequence Ontology](http://www.sequenceontology.org/), [ChEBI](https://www.ebi.ac.uk/chebi), and [PRotein Ontology](https://proconsortium.org/) all include terms for protein, but none of these classes are connected to each other.\n",
+    "\n",
+    "  - **Solution:** Choose a primary concept for all duplicate scenarios and make duplicate concepts an `RDFS:subClassOf` the primary concept. In the future, this check could be improved by leveraging [KBOOM](https://www.biorxiv.org/content/10.1101/048843v3).\n",
+    "\n",
+    "*Example Findings*  \n",
+    "The follow classes occur in all of the ontologies used in the current build and have to be normalized so that there are not multiple versions of the same concept:  \n",
+    "\n",
+    "- Gene: [VO](http://purl.obolibrary.org/obo/OGG_0000000002)  \n",
+    "  - <u>Solution</u>: Make the `VO` imported `OGG` class a subclass of the `SO` gene term  \n",
+    "\n",
+    "- Protein: [SO](http://purl.obolibrary.org/obo/SO_0000104), [PRO](http://purl.obolibrary.org/obo/PR_000000001), [ChEBI](http://purl.obolibrary.org/obo/CHEBI_36080) \n",
+    "  - <u>Solution</u>: Make the `CHEBI` and `PRO` classes a subclass of the `SO` protein term  \n",
+    "  \n",
+    "- Disorder: [VO](http://purl.obolibrary.org/obo/OGMS_0000045)  \n",
+    "  - <u>Solution</u>: Make the `VO` imported `OGMS` class a subclass of the `MONDO` disease term  \n",
+    "\n",
+    "- Antigen: [VO](http://purl.obolibrary.org/obo/OBI_1110034)  \n",
+    "  - <u>Solution</u>: Make the `VO` imported OBI class a subclass of the `CHEBI` antigen term  \n",
+    "\n",
+    "- Gelatin: [VO]('http://purl.obolibrary.org/obo/VO_0003030') \n",
+    "  - <u>Solution</u>: Make the `VO` class a subclass of the `CHEBI` gelatin term \n",
+    "\n",
+    "- Hormone: [VO](http://purl.obolibrary.org/obo/FMA_12278) \n",
+    "  - <u>Solution</u>: Make the `VO` imported `FMA` class a subclass of the `CHEBI` hormone term\n",
+    "\n",
+    "<br>\n",
+    "\n",
+    "### Punning Errors \n",
+    "*** \n",
+    "**Level:** `individual ontology`; `merged-ontology`\n",
+    "\n",
+    "**Description:** [Punning](https://www.w3.org/2007/OWL/wiki/Punning) or redeclaration errors occur for a few different reasons, but the primary or most prevalent cause observed in the ontologies used in `PheKnowLator` is due to an `owl:ObjectProperty` being incorrectly redeclared as an `owl:AnnotationProperty` or an `owl:Class` also being defined as an `OWL:ObjectProperty`. \n",
+    "\n",
+    "**Solution:** Consistent with the solution described [here](https://github.com/oborel/obo-relations/issues/130), for `owl:ObjectProperty` redeclarations we remove all `owl:AnnotationProperty` declarations. For all `owl:Class` redeclarations, we remove all `owl:ObjectProperty` redeclarations. \n",
+    "\n",
+    "*Example Findings* \n",
+    "The [Cell Line Ontology](http://www.clo-ontology.org/) had 7 object properties that were illegally redeclared and triggered punning errors. More details regarding these errors are shown below. \n",
+    "\n",
+    "```bash\n",
+    "2020-12-03 20:57:15,616 ERROR (OWLOntologyManagerImpl:1138) Illegal redeclarations of entities: reuse of entity http://purl.obolibrary.org/obo/RO_0002091 in punning not allowed [Declaration(AnnotationProperty(<http://purl.obolibrary.org/obo/RO_0002091>)), Declaration(ObjectProperty(<http://purl.obolibrary.org/obo/RO_0002091>))]\n",
+    "2020-12-03 20:57:15,619 ERROR (OWLOntologyManagerImpl:1138) Illegal redeclarations of entities: reuse of entity http://purl.obolibrary.org/obo/BFO_0000062 in punning not allowed [Declaration(AnnotationProperty(<http://purl.obolibrary.org/obo/BFO_0000062>)), Declaration(ObjectProperty(<http://purl.obolibrary.org/obo/BFO_0000062>))]\n",
+    "2020-12-03 20:57:15,620 ERROR (OWLOntologyManagerImpl:1138) Illegal redeclarations of entities: reuse of entity http://purl.obolibrary.org/obo/BFO_0000063 in punning not allowed [Declaration(ObjectProperty(<http://purl.obolibrary.org/obo/BFO_0000063>)), Declaration(AnnotationProperty(<http://purl.obolibrary.org/obo/BFO_0000063>))]\n",
+    "2020-12-03 20:57:15,620 ERROR (OWLOntologyManagerImpl:1138) Illegal redeclarations of entities: reuse of entity http://purl.obolibrary.org/obo/RO_0002222 in punning not allowed [Declaration(AnnotationProperty(<http://purl.obolibrary.org/obo/RO_0002222>)), Declaration(ObjectProperty(<http://purl.obolibrary.org/obo/RO_0002222>))]\n",
+    "2020-12-03 20:57:15,620 ERROR (OWLOntologyManagerImpl:1138) Illegal redeclarations of entities: reuse of entity http://purl.obolibrary.org/obo/RO_0000087 in punning not allowed [Declaration(ObjectProperty(<http://purl.obolibrary.org/obo/RO_0000087>)), Declaration(AnnotationProperty(<http://purl.obolibrary.org/obo/RO_0000087>))]\n",
+    "2020-12-03 20:57:15,620 ERROR (OWLOntologyManagerImpl:1138) Illegal redeclarations of entities: reuse of entity http://purl.obolibrary.org/obo/RO_0002161 in punning not allowed [Declaration(ObjectProperty(<http://purl.obolibrary.org/obo/RO_0002161>)), Declaration(AnnotationProperty(<http://purl.obolibrary.org/obo/RO_0002161>))]\n",
+    "```\n",
+    "\n",
+    "From this message, we can see that we need to remove the following `owl:ObjectProperty` redeclared to `owl:AnnotationProperty`: `RO_0002091`, `BFO_0000062`, `BFO_0000063`, `RO_0002222`, `RO_0000087`, `RO_0002161`. There were also 2 classes (i.e. `CLO_0054407` and `CLO_0054409`) defined as being a `owl:Class` and an `owl:ObjectProperty`. This is currently noted as an issue in the Cell Line Ontology's GitHub repo [issue #43](https://github.com/CLO-ontology/CLO/issues/43))."
+   ]
+  },
   {
    "cell_type": "markdown",
    "metadata": {},
@@ -58,6 +177,13 @@
     "# !{sys.executable} -m pip install <<MODULE NAME>>"
    ]
   },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "#### Load Needed Modules"
+   ]
+  },
   {
    "cell_type": "code",
    "execution_count": null,
@@ -77,6 +203,13 @@
     "from builds.ontology_cleaning import *"
    ]
   },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "#### Set Global Variables"
+   ]
+  },
   {
    "cell_type": "code",
    "execution_count": null,
@@ -94,6 +227,13 @@
     "oboinowl = Namespace('http://www.geneontology.org/formats/oboInOwl#')"
    ]
   },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "#### Helper Functions"
+   ]
+  },
   {
    "cell_type": "code",
    "execution_count": null,
@@ -138,97 +278,15 @@
     "<br>\n",
     "\n",
     "***\n",
-    "## CLEAN ONTOLOGIES <a class=\"anchor\" id=\"clean-ontologies\"></a>\n",
+    "## INDIVIDUAL ONTOLOGIES <a class=\"anchor\" id=\"individual-ontologies\"></a>\n",
     "***\n",
     "\n",
-    "The ontology cleaning step includes the following error checks, each of which are explained below: (1) Value Errors, (2) Identifier Errors, (3) Duplicate and Obsolete Entities, (4) Punning Errors, and (5) Entity Normalization Errors.\n",
-    "\n",
-    "<br>\n",
-    "\n",
-    "**Value Errors**  \n",
-    "*** \n",
-    "This check is performed at the individual- and merged-ontology level. This check utilizes the [`owlready2`](https://pypi.org/project/Owlready2/) library to read in each of the ontologies. This library is strict and will catch a wide variety of value errors. Should any of these errors arise, we retype the edge correctly. \n",
-    "\n",
-    "*Example Findings*  \n",
-    "The [Cell Line Ontology](http://www.clo-ontology.org/) yield the following error message:\n",
-    "\n",
-    "```python\n",
-    "ValueError: invalid literal for int() with base 10: '永生的乳腺衍生细胞系细胞'\n",
-    "...\n",
-    "OwlReadyOntologyParsingError: RDF/XML parsing error in file clo_with_imports.owl, line 10970, column 99.\n",
-    "```\n",
-    "\n",
-    "This tells us that we need to repair the triple containing the Literal '永生的乳腺衍生细胞系细胞' by removing it and redefining it as a `string`, rather than an `int` as it is currently defined as. This is currently noted as an issue in the [Cell Line Ontology's](http://www.clo-ontology.org/) GitHub repo ([issue #48](https://github.com/CLO-ontology/CLO/issues/48)). \n",
+    "**Purpose:** This section focuses on cleaning the individual ontologies which consists of fixing: (1) Parsing Errors; (2) Identifier Errors; (3) Deprecated and Obsolete Classes; and (4) Punning Errors.\n",
     "\n",
-    "<br>\n",
     "\n",
-    "**Identifier Errors**  \n",
-    "*** \n",
-    "This check is performed at the individual- and merged-ontology level. Check class identifiers to ensure consistency in identifier prefixes. For example, we want to identifiers that are incorrectly formatted like occurrences of `PRO_XXXXXXX` which should be `PR_XXXXXXX`. For all detected errors, we reformat the incorrectly formatted class identifiers. This is a tricky task to do in an automated manner and is something that should be updated if any new ontologies are added to the `PheKnowLator` build. Currently, the code below checks and logs any hits, but only fixes the following known errors: Vaccine Ontology: `PRO` which should be `PR`.\n",
-    "\n",
-    "*Example Findings*  \n",
-    "Running this check revealed mislabeling of `2` [pROtein Ontology](https://proconsortium.org/) identifiers in the [Vaccine Ontology](http://www.violinet.org/vaccineontology/) (see [this](https://github.com/vaccineontology/VO/issues/4) GitHub issue).\n",
-    "\n",
-    "<br>\n",
-    "\n",
-    "**Obsolete and/or Deprecated Entities**  \n",
-    "*** \n",
-    "This check is only performed at the individual-ontology level. To make sure that the ontology only contains current information, all obsolete classes and any triples that they participate in are removed from the ontologies. In addition to running the code below, it may also be necessary to check for classes that are a sub-class of `oboInOwl:ObsoleteClass`, as well as any obsolete or deprecated annotations, individuals, or `owl:ObjectProperty`.\n",
-    "\n",
-    "<br>\n",
+    "**Inputs:** A directory (`write_location`) containing ontology files (`.owl`)\n",
     "\n",
-    "**Normalization Errors**   \n",
-    "*** \n",
-    "These checks are performed at the individual- and merged-ontology levels. There are two types of checks that are performed:  \n",
-    "\n",
-    "*Normalize Duplicate Ontology Concepts*  \n",
-    "we want to make sure that all classes that represent the same entity are connected to each other. For example, consider the following: the [Sequence Ontology](http://www.sequenceontology.org/), [ChEBI](https://www.ebi.ac.uk/chebi), and [PRotein Ontology](https://proconsortium.org/) all include terms for protein, but none of these classes are connected to each other. The solution to fixing these errors is to choose a primary concept for all duplicate scenarios and make duplicate concepts an `RDFS:subClassOf` the primary concept.\n",
-    "\n",
-    "*Normalize Existing Ontology Classes*  \n",
-    "Checks for inconsistencies in ontology classes that overlap with non-ontology entity identifiers (e.g. if HP includes `HGNC` identifiers, but PheKnowLator utilizes `Entrez` identifiers). While there are other types of identifiers, we focus primarily on resolving the genomic types, since we have a master dictionary we can used to help with this ([`Merged_gene_rna_protein_identifiers.pkl`](https://storage.googleapis.com/pheknowlator/release_v2.0.0/build_31DEC2020/data/processed_data/Merged_gene_rna_protein_identifiers.pkl)). This check can be updated in future iterations to include other types of identifiers, but given our detailed examination of the `v2.0.0` ontologies, these were the identifier types that needed repair.\n",
-    "\n",
-    "*Example Findings*  \n",
-    "The follow classes occur in all of the ontologies used in the current build and have to be normalizesd so that there are not multiple versions of the same concept:  \n",
-    "\n",
-    "- Gene: [VO](http://purl.obolibrary.org/obo/OGG_0000000002)  \n",
-    "  - <u>Solution</u>: Make the `VO` imported `OGG` class a subclass of the `SO` gene term  \n",
-    "\n",
-    "- Protein: [SO](http://purl.obolibrary.org/obo/SO_0000104), [PRO](http://purl.obolibrary.org/obo/PR_000000001), [ChEBI](http://purl.obolibrary.org/obo/CHEBI_36080) \n",
-    "  - <u>Solution</u>: Make the `CHEBI` and `PRO` classes a subclass of the `SO` protein term  \n",
-    "  \n",
-    "- Disorder: [VO](http://purl.obolibrary.org/obo/OGMS_0000045)  \n",
-    "  - <u>Solution</u>: Make the `VO` imported `OGMS` class a subclass of the `MONDO` disease term  \n",
-    "\n",
-    "- Antigen: [VO](http://purl.obolibrary.org/obo/OBI_1110034)  \n",
-    "  - <u>Solution</u>: Make the `VO` imported OBI class a subclass of the `CHEBI` antigen term  \n",
-    "\n",
-    "- Gelatin: [VO]('http://purl.obolibrary.org/obo/VO_0003030') \n",
-    "  - <u>Solution</u>: Make the `VO` class a subclass of the `CHEBI` gelatin term \n",
-    "\n",
-    "- Hormone: [VO](http://purl.obolibrary.org/obo/FMA_12278) \n",
-    "  - <u>Solution</u>: Make the `VO` imported `FMA` class a subclass of the `CHEBI` hormone term\n",
-    "\n",
-    "<br>\n",
-    "\n",
-    "**Punning Errors** \n",
-    "*** \n",
-    "This check is performed at the individual- and merged-ontology level. [Punning](https://www.w3.org/2007/OWL/wiki/Punning) or redeclaration errors occur for a few different reasons, but the primary or most prevalent cause observed in the ontologies used in `PheKnowLator` is due to an `owl:ObjectProperty` being incorrectly redeclared as an `owl:AnnotationProperty` or an `owl:Class` also being defined as an `OWL:ObjectProperty`. Consistent with the solution described [here](https://github.com/oborel/obo-relations/issues/130), for `owl:ObjectProperty` redeclarations we remove all `owl:AnnotationProperty` declarations. For all `owl:Class` redeclarations, we remove all `owl:ObjectProperty` redeclarations. We also handle all classes that are directly types as an `owl:Class` and an `owl:NamedIndividual` by before the `owl:NamedIndividual` assertion.\n",
-    "\n",
-    "*Example Findings* \n",
-    "The [Cell Line Ontology](http://www.clo-ontology.org/) had 7 object properties that were illegally redeclared and triggered punning errors. More details regarding these errors are shown below. \n",
-    "\n",
-    "```bash\n",
-    "2020-12-03 20:57:15,616 ERROR (OWLOntologyManagerImpl:1138) Illegal redeclarations of entities: reuse of entity http://purl.obolibrary.org/obo/RO_0002091 in punning not allowed [Declaration(AnnotationProperty(<http://purl.obolibrary.org/obo/RO_0002091>)), Declaration(ObjectProperty(<http://purl.obolibrary.org/obo/RO_0002091>))]\n",
-    "2020-12-03 20:57:15,619 ERROR (OWLOntologyManagerImpl:1138) Illegal redeclarations of entities: reuse of entity http://purl.obolibrary.org/obo/BFO_0000062 in punning not allowed [Declaration(AnnotationProperty(<http://purl.obolibrary.org/obo/BFO_0000062>)), Declaration(ObjectProperty(<http://purl.obolibrary.org/obo/BFO_0000062>))]\n",
-    "2020-12-03 20:57:15,620 ERROR (OWLOntologyManagerImpl:1138) Illegal redeclarations of entities: reuse of entity http://purl.obolibrary.org/obo/BFO_0000063 in punning not allowed [Declaration(ObjectProperty(<http://purl.obolibrary.org/obo/BFO_0000063>)), Declaration(AnnotationProperty(<http://purl.obolibrary.org/obo/BFO_0000063>))]\n",
-    "2020-12-03 20:57:15,620 ERROR (OWLOntologyManagerImpl:1138) Illegal redeclarations of entities: reuse of entity http://purl.obolibrary.org/obo/RO_0002222 in punning not allowed [Declaration(AnnotationProperty(<http://purl.obolibrary.org/obo/RO_0002222>)), Declaration(ObjectProperty(<http://purl.obolibrary.org/obo/RO_0002222>))]\n",
-    "2020-12-03 20:57:15,620 ERROR (OWLOntologyManagerImpl:1138) Illegal redeclarations of entities: reuse of entity http://purl.obolibrary.org/obo/RO_0000087 in punning not allowed [Declaration(ObjectProperty(<http://purl.obolibrary.org/obo/RO_0000087>)), Declaration(AnnotationProperty(<http://purl.obolibrary.org/obo/RO_0000087>))]\n",
-    "2020-12-03 20:57:15,620 ERROR (OWLOntologyManagerImpl:1138) Illegal redeclarations of entities: reuse of entity http://purl.obolibrary.org/obo/RO_0002161 in punning not allowed [Declaration(ObjectProperty(<http://purl.obolibrary.org/obo/RO_0002161>)), Declaration(AnnotationProperty(<http://purl.obolibrary.org/obo/RO_0002161>))]\n",
-    "```\n",
-    "\n",
-    "From this message, we can see that we need to remove the following `owl:ObjectProperty` redeclared to `owl:AnnotationProperty`: `RO_0002091`, `BFO_0000062`, `BFO_0000063`, `RO_0002222`, `RO_0000087`, `RO_0002161`. There were also 2 classes (i.e. `CLO_0054407` and `CLO_0054409`) defined as being a `owl:Class` and an `owl:ObjectProperty`. This is currently noted as an issue in the Cell Line Ontology's GitHub repo [issue #43](https://github.com/CLO-ontology/CLO/issues/43)).\n",
-    "\n",
-    "NOTE. Even though a run will yield a clean a result, reading back in the ontology using any OWL API-based tools (e.g. OWL Tools) will result in a single punning error for `RO_0002161`."
+    "**Outputs:** A directory (`write_location`) containing cleaned ontology files (`.owl`)  "
    ]
   },
   {
@@ -244,13 +302,6 @@
     "ont_data.ontology_info = {k.split('/')[-1]: {} for k, v in ont_data.ontology_info.items()}"
    ]
   },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "### Individual-Ontology Cleaning"
-   ]
-  },
   {
    "cell_type": "code",
    "execution_count": null,
@@ -269,11 +320,6 @@
     "    # clean ontologies\n",
     "    ont_data.fixes_ontology_parsing_errors()\n",
     "    ont_data.fixes_identifier_errors()\n",
-    "    \n",
-    "    \n",
-    "    \n",
-    "    \n",
-    "    .000\n",
     "    ont_data.removes_deprecated_obsolete_entities()\n",
     "    ont_data.fixes_punning_errors()\n",
     "    \n",
@@ -281,7 +327,10 @@
     "    ont_data.updates_ontology_reporter()\n",
     "    \n",
     "    # run cleaned ontology through the elk reasoner\n",
-    "    logically_verifies_cleaned_ontologies(ont_data.ont_graph, ont_data.temp_dir, ont_data.ont_file_location, ont_data.owltools_location)"
+    "    logically_verifies_cleaned_ontologies(ont_data.ont_graph,\n",
+    "                                          ont_data.temp_dir,\n",
+    "                                          ont_data.ont_file_location,\n",
+    "                                          ont_data.owltools_location)"
    ]
   },
   {
@@ -291,10 +340,10 @@
     "<br>\n",
     "\n",
     "***\n",
-    "## MERGE ONTOLOGIES <a class=\"anchor\" id=\"merge-ontologies\"></a>\n",
+    "## MERGED ONTOLOGIES <a class=\"anchor\" id=\"merge-ontologies\"></a>\n",
     "***\n",
     "\n",
-    "**Purpose:** In this step, the [`OWLTools`](https://github.com/owlcollab/owltools) library is designed to merge a directory of ontology files into a single ontology file. This merged ontology file is required as input to the knowledge graph build algorithm.  \n",
+    "**Purpose:** In this step, the [`OWLTools`](https://github.com/owlcollab/owltools) library is used to merge the directory of cleaned ontology files into a single ontology file. Then, the following cleaning steps are performed: (1) Identifier Errors; (2) Duplicate Classes; (3) Duplicate Class Concepts; and (4) Punning Errors.  \n",
     "\n",
     "**Inputs:** A directory of ontology files (`.owl`)\n",
     "\n",
@@ -377,6 +426,15 @@
     "ontology_file_formatter(knowledge_graphs_location, '/' + ont_data.ont_file_location, ont_data.owltools_location)"
    ]
   },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "ontology_file_formatter(knowledge_graphs_location, '/' + ont_data.ont_file_location, ont_data.owltools_location)"
+   ]
+  },
   {
    "cell_type": "markdown",
    "metadata": {},
diff --git a/README.rst b/README.rst
index 9eff3108a..f398798db 100644
--- a/README.rst
+++ b/README.rst
@@ -1,5 +1,6 @@
-pkt_kg
-=========================================================================================
+##############################################
+Phenotype Knowledge Translator (PheKnowLator) 
+##############################################
 
 |github_action| |mypy|  
 
@@ -9,70 +10,68 @@ pkt_kg
 
 |ABRA|
 
-
+***********************
 What is PheKnowLator?
-######################
+***********************
 
 PheKnowLator (Phenotype Knowledge Translator) or ``pkt_kg`` is the first fully customizable KG construction framework enabling users to build complex knowledge graphs that are Semantic Web compliant and amenable to automatic Web Ontology Language (OWL) reasoning, conform to contemporary property graph standards, and are importable by today’s popular graph toolkits. PheKnowLator offers multiple build types, can automatically include inverse edges, provides OWL-decoded transformations to support automated deductive reasoning, and outputs knowledge graphs in several formats. By providing flexibility in the way knowledge graphs are modeled, PheKnowLator enables the use of cutting edge graph-based learning and sophisticated network inference algorithms.
 
 What Does This Repository Provide?
-**********************************
-
-Through this repository we provide the following:
-
+===================================
 1. **A Knowledge Graph Sharing Hub:** Prebuilt knowledge graphs and associated metadata. Each knowledge graph is provided as triple edge lists (``.nt``, ``.txt``), OWL API-formatted ``RDF/XML`` and NetworkX graph-pickled MultiDiGraphs. We also make text files available that contain node and relation metadata. Finally, starting with build `V2.0.0 <https://github.com/callahantiff/PheKnowLator/wiki/v2.0.0>`__, we make available different types of graph embeddings for each knowledge graph build.
+2. **A Knowledge Graph Building Framework:** A fully automated ``Python 3`` library explicitly designed for optimized construction of semantically-rich, large-scale biomedical KGs from complex heterogeneous data. Our framework provides detailed Jupyter Notebooks to greatly simplify the process of constructing knowledge graphs.  
 
+How do I Learn More?
+===================================
+- Join an existing or start a new `Discussion`_
+- The Project `Wiki`_ for more details on available knowledge graphs, the knowledge graph construction process, algorithm, and information on the data sources used to build each knowledge graph
+- Preliminary results from our 2020 ISMB presentation are available `here <https://doi.org/10.1101/2020.04.30.071407>`__
 
-2. **A Knowledge Graph Building Framework:** A fully automated ``Python 3`` library explicitly designed for optimized construction of semantically-rich, large-scale biomedical KGs from complex heterogeneous data. The PheKnowLator framework provides detailed Jupyter Notebooks and scripts which greatly simplify knowledge graph construction, assisting even non-technical users through all steps of the build process.
-
+|
 
-How do I Learn More?
-**********************
+--------------------------------------------
 
-- Join an existing or start a new `Discussion`_
+************************************
+Important Updates and Notifications
+************************************
 
-- The Project `Wiki`_ for more details on available knowledge graphs, the knowledge graph construction process,
-algorithm, and information on the data sources used to build each knowledge graph
+- *12/21/2020:*  We are hard at work on release ``v2.0.0`` and will announce here when the release is ready for public consumption!
+- *January 2021:*
 
-- Preliminary results from our 2020 ISMB presentation are available `here <https://doi.org/10.1101/2020.04.30.071407>`__
+  - Access build data via a dedicated Google Cloud Storage bucket (see details under ``Releases``)
 
-|
+  - New Public Endpoints:
 
-Releases
-########
+    - SPARQL Endpoint (``OWL-NETS`` ``instance``-based with inverse_relations and no metadata (e.g. labels)): `http://sparql.pheknowlator.com/ <http://sparql.pheknowlator.com/>`__
 
-All data and output for each release are free to download from our dedicated Google Cloud Storage Bucket (GCS). All data can be downloaded from the `PheKnowLator GCS Bucket
-<https://console.cloud.google.com/storage/browser/pheknowlator?project=pheknowlator>`__, which is organized by release and build..
+    - Neo4J Endpoint: `http://neo4j.pheknowlator.com/ <http://neo4j.pheknowlator.com/>`__  --  *COMING SOON*
 
-**Current Release:**
+Releases
+=========
+All data and output for each release are free to download from our dedicated Google Cloud Storage Bucket (GCS). All data can be downloaded from the `PheKnowLator GCS Bucket <https://console.cloud.google.com/storage/browser/pheknowlator?project=pheknowlator>`__, which is organized by release and build..
 
+Current Release
+----------------
 - ``v2.0.0``
 
   - `Build Documentation <https://github.com/callahantiff/PheKnowLator/wiki/v2.0.0>`__
   - `Data Access <https://console.cloud.google.com/storage/browser/pheknowlator/release_v2.0.0?project=pheknowlator>`__
 
-**Prior Releases:**
-
+Prior Releases
+-----------------
 - ``v1.0.0``
 
   - `Build Documentation <https://github.com/callahantiff/PheKnowLator/wiki/v1.0.0>`__
   - `Data Access <https://console.cloud.google.com/storage/browser/pheknowlator/release_v1.0.0?project=pheknowlator>`__
-
-|
-
-Important Updates and Notifications
-########
-
-- *12/21/2020:*  We are hard at work on release ``v2.0.0`` and will announce here when the release is ready for public consumption!  
-
 |
+----------------------------------
 
+************************
 Getting Started
-###############
+************************
 
 Install Library
-****************
-
+================
 This program requires Python version 3.6. To install the library from PyPI, run:
 
 .. code:: shell
@@ -98,8 +97,7 @@ You can also clone the repository directly from GitHub by running:
 |
 
 Set-Up Environment
-**********************
-
+===================
 The ``pkt_kg`` library requires a specific project directory structure.
 
 - If you plan to run the code from a cloned version of this repository, then no additional steps are needed.
@@ -125,16 +123,12 @@ The ``pkt_kg`` library requires a specific project directory structure.
         |         |
         |     relations_data/
 
-|
-|
-
 Dependencies
-^^^^^^^^^^^^
-
+-------------
 Several input documents must be created before the ``pkt_kg`` library can be utilized. Each of the input documents are listed below by knowledge graph build step:
 
-*DOWNLOAD DATA*
-
+*Download Data*
+^^^^^^^^^^^^^^^^
 This code requires three documents within the ``resources`` directory to run successfully. For more information on these documents, see `Document Dependencies`_:
 
 * `resources/resource_info.txt`_
@@ -147,32 +141,34 @@ For assistance in creating these documents, please run the following from the ro
 
     python3 generates_dependency_documents.py
 
-Prior to running this step, make sure that all mapping and filtering data referenced in `resources/resource_info.txt`_ have been created or downloaded for an existing build from the `PheKnowLator GCS Bucket<https://console.cloud.google.com/storage/browser/pheknowlator?project=pheknowlator>`__. To generate these data yourself, please see the `Data_Preparation.ipynb`_ Jupyter Notebook for detailed examples
+Prior to running this step, make sure that all mapping and filtering data referenced in `resources/resource_info.txt`_ have been created or downloaded for an existing build from the `PheKnowLator GCS Bucket <https://console.cloud.google.com/storage/browser/pheknowlator?project=pheknowlator>`__. To generate these data yourself, please see the `Data_Preparation.ipynb`_ Jupyter Notebook for detailed examples
 of the steps used to build the `v2.0.0 knowledge graph <https://github.com/callahantiff/PheKnowLator/wiki/v2.0.0>`__.
 
 *Note.* To ensure reproducibility, after downloading data, a metadata file is output for the ontologies (`ontology_source_metadata.txt`_) and edge data sources (`edge_source_metadata.txt`_).
 
-|
-
-*CONSTRUCT KNOWLEDGE GRAPH*
-
+*Construct Knowledge Graph*
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 The `KG Construction`_ Wiki page provides a detailed description of the knowledge construction process (please see the knowledge graph `README`_ for more information). Please make sure you have created the documents listed below prior to constructing a knowledge graph. Click on each document for additional information.
 
 * `resources/construction_approach/subclass_construction_map.pkl`_
 * `resources/Master_Edge_List_Dict.json`_ ➞ *automatically created after edge list construction*
-* `resources/node_data/*.txt`_ ➞ *if adding metadata for new edges to the knowledge graph*
+* `resources/node_data/node_metadata_dict.pkl <https://github.com/callahantiff/PheKnowLator/blob/master/resources/node_data/README.md>`__ ➞ *if adding metadata for new edges to the knowledge graph*  
 * `resources/knowledge_graphs/PheKnowLator_MergedOntologies*.owl`_ ➞ *see* `ontology README`_ *for information*
-* `resources/owl_decoding/OWL_NETS_Property_Types.txt`_
 * `resources/relations_data/RELATIONS_LABELS.txt`_
 * `resources/relations_data/INVERSE_RELATIONS.txt`_ ➞ *if including inverse relations*
 
 |
-|
+----------------------------------
 
+************************
 Running the pkt Library
-**********************
+************************
 
-There are several ways to run ``pkt_kg``. An example workflow is provided below.
+``pkt_kg`` can be run via the provided `Main.py`_ script or using the `main.ipynb`_ Jupyter Notebook or using a Docker container.
+
+Main Script or Jupyter Notebook
+==========================================
+The program can be run locally using the `Main.py`_ script or using the `main.ipynb`_ Jupyter Notebook. An example of the workflow used in both of these approaches is shown below.
 
 .. code:: python
 
@@ -207,12 +203,9 @@ There are several ways to run ``pkt_kg``. An example workflow is provided below.
 
  kg.construct_knowledge_graph()
 
-|
-|
-
-This repo provides 3 different of ways to run ``pkt_kg``:
-
-*COMMAND LINE* ➞ `Main.py`_
+``Main.py``
+-----------
+The example below provides the details needed to run ``pkt_kg`` using ``./Main.py``.
 
 .. code:: bash
 
@@ -230,107 +223,96 @@ This repo provides 3 different of ways to run ``pkt_kg``:
     -t RES,  --res RES    name/path to text file containing resource_info
     -b KG,   --kg KG      the build, can be "partial", "full", or "post-closure"
     -o OUT,  --out OUT    name/path to directory where to write knowledge graph
-    -n NDE,  --nde NDE    yes/no - adding node metadata to knowledge graph
     -r REL,  --rel REL    yes/no - adding inverse relations to knowledge graph
     -s OWL,  --owl OWL    yes/no - removing OWL Semantics from knowledge graph
-    -m KGM,  --kgm KGM    yes/no - adding node metadata to knowledge graph
 
-|
-|
+``main.ipynb``
+---------------
+The ``./main.ipynb`` Jupyter notebook provides detailed instructions for how to run the ``pkt_kg`` algorithm and build a knowledge graph from scratch.  
 
-*JUPYTER NOTEBOOK* ➞ `main.ipynb`_
-
-|
 |
 
-*DOCKER*
-
-**Pre-Built Containers:** `DockerHub <https://hub.docker.com/repository/docker/callahantiff/pheknowlator/general>`__
-
-``pkt_kg`` can be run using a Docker instance. In order to utilize the Dockerized version of the code, please make sure that you have downloaded the newest version of `Docker <https://docs.docker.com/get-docker/>`__.
+Docker Container
+=================
+``pkt_kg`` can be run using a Docker instance. In order to utilize the Dockerized version of the code, please make sure that you have downloaded the newest version of `Docker <https://docs.docker.com/get-docker/>`__. There are two ways to utilize Docker with this repository:
 
-There are two ways to utilize Docker with this repository:
+- Obtain Pre-Built Container from `DockerHub <https://hub.docker.com/repository/docker/callahantiff/pheknowlator>`__
+- Build the Container (see details below) 
 
-- Obtain the pre-built Docker container from `DockerHub <https://hub.docker.com/repository/docker/callahantiff/pheknowlator>`__
-- Build the Container
+Obtaining a Container
+----------------------
+*Obtain Pre-Built Containiner:* A pre-built containers can be obtained directly from `DockerHub <https://hub.docker.com/repository/docker/callahantiff/pheknowlator/general>`__.  
 
-|
-
-*Build the Container*
-
-To build the ``pkt_kg`` Docker container:
-
-- Download a stable release of this repository or clone this repository to get the most up-to-date version
-- Unpack the repository downloaded (if necessary), then execute the following commands to build the container:
+*Build Container:* To build the ``pkt_kg`` download a stable release of this repository (or fork/clone it repository). Once downloaded, you will have everything needed to build the conatiner, including the ```./Dockerfile` and ``./dockerignore``. The code shown below builds the container. Make sure to relace ``[VERSION]`` with the current ``pkt_kg`` version before running the code.  
 
 .. code:: bash
 
     cd /path/to/PheKnowLator (Note, this is the directory containing the Dockerfile file)
     docker build -t pkt:[VERSION] .
 
-*NOTES:* When building a container using new data sources, the only files that you should have to update are the
-``pkt_kg`` input dependency documents (i.e. ``PheKnowLator/resources/resource_info.txt``,
-``PheKnowLator/resources/edge_source_list.txt``, and ``PheKnowLator/resources/ontology_source_list.txt``) and the ``PheKnowLatpr/.dockerignore`` (i.e. updating the sources listed under the ``## DATA NEEDED TO BUILD KNOWLEDGE GRAPH ##`` comment, to make sure they match the file paths for all datasets used to map indentifiers listed in the ``PheKnowLator/resources/resource_info.txt`` document).
-
-|
-
-*Run the Container*
+Notes:
+^^^^^^
+- Update ``PheKnowLator/resources/resource_info.txt``, ``PheKnowLator/resources/edge_source_list.txt``, and ``PheKnowLator/resources/ontology_source_list.txt``  
+- Verify ``PheKnowLatpr/.dockerignore`` is accurate (i.e. updating the sources listed under the ``## DATA NEEDED TO BUILD KNOWLEDGE GRAPH ##`` comment, to make sure they match the file paths for all datasets used to map identifiers listed in the ``PheKnowLator/resources/resource_info.txt`` document)  
 
+Running a Container
+--------------------
 The following code can be used to run ``pkt_kg`` from outside of the container (after obtaining a prebuilt container or after building the container locally). In:
 
 .. code:: bash
 
     docker run --name [DOCKER CONTAINER NAME] -it pkt:[VERSION] --app subclass --kg full --nde yes --rel yes --owl no --kgm yes
 
-|
-
-*NOTES*:
-
-- The example shown above builds a full version of the knowledge graph using the subclass construction approach with node metadata, inverse relations, and decoding of OWL classes. See the **Running the pkt Library** section for more information on the parameters that can be passed to ``pkt_kg``
+Notes:
+^^^^^^
+- The example shown above builds a full version of the knowledge graph using the subclass construction approach with node metadata, inverse relations, and decoding of OWL classes. See the **Running the pkt Library** section for more information on the parameters that can be passed to ``pkt_kg`` 
 - The Docker container cannot write to an encrypted filesystem, however, so please make sure ``/local/path/to/PheKnowLator/resources/knowledge_graphs`` references a directory that is not encrypted
 
-|
-
-**Finding Data Inside Docker Container**
-
+Finding Data Inside a Container
+------------------------------------
 In order to enable persistent data, a volume is mounted within the ``Dockerfile``. By default, Docker names volumes using a hash. In order to find the correctly mounted volume, you can run the following:
 
-*Command 1:* Obtains the volume hash:
+**Command 1:** Obtains the volume hash:
 
 .. code:: bash
 
     docker inspect --format='{{json .Mounts}}' [DOCKER CONTAINER NAME] | python -m json.tool
 
 
-*Command 2:* View data written to the volume:
+**Command 2:** View data written to the volume:
 
 .. code:: bash
 
     sudo ls /var/lib/docker/volumes/[VOLUME HASH]/_data
 
-
---------------
-
---------------
-
 |
+---------------------------------
 
-Contributing
-########
+******************************
+Get In Touch or Get Involved
+******************************
 
+Contribution
+=============
 Please read `CONTRIBUTING.md`_ for details on our code of conduct, and the process for submitting pull requests to us.
 
+Contact Us
+===========
+We’d love to hear from you! To get in touch with us, please join or start a new `Discussion`_, `create an issue`_
+or `send us an email`_ 💌
+
 |
 
-License
-########
+*************
+Attribution
+*************
 
+Licensing
+==========
 This project is licensed under Apache License 2.0 - see the `LICENSE.md`_ file for details.
 
-|
-
 Citing this Work
-########
+=================
 
 ..
 
@@ -343,13 +325,6 @@ Citing this Work
      url          = {https://doi.org/10.5281/zenodo.3401437}
    }
 
-|
-
-Contact
-########
-
-We’d love to hear from you! To get in touch with us, please join or start a new `Discussion`_, `create an issue`_
-or `send us an email`_ 💌
 
 
 .. |ABRA| image:: https://img.shields.io/badge/ReproducibleResearch-AbraCollaboratory-magenta.svg
@@ -427,7 +402,7 @@ or `send us an email`_ 💌
 
 .. _`resources/Master_Edge_List_Dict.json`: https://www.dropbox.com/s/t8sgzd847t1rof4/Master_Edge_List_Dict.json?dl=1
 
-.. _`resources/node_data/*.txt`: https://github.com/callahantiff/PheKnowLator/blob/master/resources/node_data/README.md
+.. _`resources/node_data/node_metadata_dict.pkl`: https://github.com/callahantiff/PheKnowLator/blob/master/resources/node_data/README.md
 
 .. _`resources/knowledge_graphs/PheKnowLator_MergedOntologies*.owl`: https://www.dropbox.com/s/75lkod7vzpgjdaq/PheKnowLator_MergedOntologiesGeneID_Normalized_Cleaned.owl?dl=1
 
diff --git a/builds/build_phase_2.py b/builds/build_phase_2.py
index 1654a2550..2ecce9ed0 100644
--- a/builds/build_phase_2.py
+++ b/builds/build_phase_2.py
@@ -121,7 +121,7 @@ def updates_dependency_documents(gcs_url, file_url, bucket, temp_directory):
 def main():
     print('#' * 35 + '\nBUILD PHASE 2: DATA PRE-PROCESSING\n' + '#' * 35)
 
-    temp_dir = 'builds/temp'
+    temp_dir = 'temp'
     if not os.path.exists(temp_dir): os.mkdir(temp_dir)
 
     ###############################################
@@ -130,10 +130,10 @@ def main():
     bucket = storage_client.get_bucket('pheknowlator')
     # define write path to Google Cloud Storage bucket
     release = 'release_v' + __version__
-    bucket_files = [file.name.split('/')[1] for file in bucket.list_blobs(prefix=release)]
+    bucket_files = [file.name.split('/')[2] for file in bucket.list_blobs(prefix='{}/archived_builds'.format(release))]
     build = 'build_' + sorted([x[0] for x in [re.findall(r'(?<=_)\d.*', x) for x in bucket_files] if len(x) > 0])[-1]
-    gcs_original_data = '{}/{}/data/{}'.format(release, build, 'original_data/')
-    gcs_processed_data = '{}/{}/data/{}'.format(release, build, 'processed_data/')
+    gcs_original_data = '{}/archived_builds/{}/data/{}'.format(release, build, 'original_data/')
+    gcs_processed_data = '{}/archived_builds/{}/data/{}'.format(release, build, 'processed_data/')
     gcs_url = 'https://storage.googleapis.com/pheknowlator/{}/{}/data/'.format(release, build)
 
     ###############################################
@@ -150,7 +150,7 @@ def main():
     # STEP 4 - GENERATE METADATA DOCUMENTATION
     metadata, processed_data = [], glob.glob(temp_dir + '/*')
     for data_file in tqdm(processed_data):
-        url = gcs_url + '/original_data/' + data_file.replace(temp_dir + '/', '')
+        url = gcs_url + 'original_data/' + data_file.replace(temp_dir + '/', '')
         metadata += [get_file_metadata(url, data_file, gcs_url + 'processed_data/')]
     writes_metadata(metadata, temp_dir)
 
diff --git a/builds/data_preprocessing.py b/builds/data_preprocessing.py
index 9039ce4ae..710f3467a 100644
--- a/builds/data_preprocessing.py
+++ b/builds/data_preprocessing.py
@@ -716,10 +716,10 @@ def creates_disease_identifier_mappings(self) -> None:
             for k, v in tqdm({**disease_dict, **mondo_dict, **hp_dict}.items()):
                 if any(x for x in v if x.startswith('MONDO')):
                     for idx in [x.replace(':', '_') for x in v if 'MONDO' in x]:
-                        out1.write(k.upper() + '\t' + idx + '\n')
+                        out1.write(k.upper().split(':')[-1] + '\t' + idx + '\n')
                 if any(x for x in v if x.startswith('HP')):
                     for idx in [x.replace(':', '_') for x in v if 'HP' in x]:
-                        out2.write(k.upper() + '\t' + idx + '\n')
+                        out2.write(k.upper().split(':')[-1] + '\t' + idx + '\n')
         self.uploads_data_to_gcs_bucket(file1)
         self.uploads_data_to_gcs_bucket(file2)
 
@@ -1481,7 +1481,9 @@ def _creates_variant_metadata_dict(self) -> Dict:
 
     def _creates_pathway_metadata_dict(self) -> Dict:
         """Creates a dictionary to store labels, synonyms, and a description for each Reactome Pathway identifier
-        present in the input data file.
+        present in the human Reactome Pathway Database identifier data set (ReactomePathways.txt); Reactome-Gene
+        Association data (gene_association.reactome.gz), and Reactome-ChEBI data (ChEBI2Reactome_All_Levels.txt). The
+        keys of the dictionary are Reactome identifiers and the values are dictionaries for each metadata type.
 
         Returns:
             pathway_metadata_dict: A dict containing metadata that's keyed by Reactome Pathway identifier and whose
@@ -1494,11 +1496,22 @@ def _creates_pathway_metadata_dict(self) -> Dict:
 
         print('\t- Generating Metadata for Pathway Identifiers')
 
-        # get metadata then convert it to a dictionary
+        # reactome pathways
         f_name = self.temp_dir + '/' + 'ReactomePathways.txt'
         data = pandas.read_csv(f_name, header=None, delimiter='\t', low_memory=False)
         data = data.loc[data[2].apply(lambda x: x == 'Homo sapiens')]
-        nodes = set(list(data[0]))
+        # reactome gene association data
+        f_name1 = self.temp_dir + '/' + 'gene_association.reactome'
+        data1 = pandas.read_csv(f_name1, header=None, delimiter='\t', skiprows=1, low_memory=False)
+        data1 = data1.loc[data1[12].apply(lambda x: x == 'taxon:9606')]
+        data1[5].replace('REACTOME:', '', inplace=True, regex=True)
+        # reactome CHEBI data
+        f_name2 = self.temp_dir + '/' + 'ChEBI2Reactome_All_Levels.txt'
+        data2 = pandas.read_csv(f_name2, header=None, delimiter='\t', low_memory=False)
+        data2 = data2.loc[data2[5].apply(lambda x: x == 'Homo sapiens')]
+
+        # set unique node list
+        nodes = set(list(data[0]) + list(data1[5]) + list(data2[1]))
         metadata = metadata_api_mapper(list(nodes))
         metadata['ID'] = metadata['ID'].map('https://reactome.org/content/detail/{}'.format)
         metadata.set_index('ID', inplace=True)
@@ -1541,9 +1554,20 @@ def _creates_relations_metadata_dict(self) -> Dict:
         return relation_metadata_dict
 
     def creates_non_ontology_class_metadata_dict(self) -> None:
-        """Combines the gene metadata, transcript metadata, variant metadata, pathway metadata, and relations metadata
-        dictionaries into a single large metadata dictionary. See the specific functions used below for examples of
-        the output.
+        """Combines the gene metadata, transcript metadata, variant metadata, pathway metadata, and relations
+        metadata dictionaries into a single large metadata dictionary. See example output below:
+        {
+            'nodes': {
+                'http://www.ncbi.nlm.nih.gov/gene/1': {
+                    'Label': 'A1BG',
+                    'Description': "A1BG has locus group protein-coding' and is located on chromosome 19 (19q13.43).",
+                    'Synonym': 'HYST2477alpha-1B-glycoprotein|HEL-S-163pA|ABG|A1B|GAB'} ... },
+            'relations': {
+                'http://purl.obolibrary.org/obo/RO_0002533': {
+                    'Label': 'sequence atomic unit',
+                    'Description': 'Any individual unit of a collection of like units arranged in a linear order',
+                    'Synonym': 'None'} ... }
+        }
 
         Returns:
             None.
@@ -1552,11 +1576,11 @@ def creates_non_ontology_class_metadata_dict(self) -> None:
         print('Creating Master Metadata Dictionary for Non-Ontology Entities')
 
         # create single dictionary of
-        master_metadata_dictionary = {**self._creates_gene_metadata_dict(),
-                                      **self._creates_transcript_metadata_dict(),
-                                      **self._creates_variant_metadata_dict(),
-                                      **self._creates_pathway_metadata_dict(),
-                                      **self._creates_relations_metadata_dict()}
+        master_metadata_dictionary = {'nodes': {**self._creates_gene_metadata_dict(),
+                                                **self._creates_transcript_metadata_dict(),
+                                                **self._creates_variant_metadata_dict(),
+                                                **self._creates_pathway_metadata_dict()},
+                                      'relations': self._creates_relations_metadata_dict()}
 
         # save data and push to gcs bucket
         filename = 'node_metadata_dict.pkl'
diff --git a/builds/ontology_cleaning.py b/builds/ontology_cleaning.py
index 69558b8f6..9c8a54d59 100644
--- a/builds/ontology_cleaning.py
+++ b/builds/ontology_cleaning.py
@@ -10,7 +10,7 @@
 import re
 
 from google.cloud import storage  # type: ignore
-from owlready2 import *  # type: ignore
+from owlready2 import get_ontology, OwlReadyOntologyParsingError  # type: ignore
 from rdflib import BNode, Graph, Literal, Namespace, URIRef  # type: ignore
 from rdflib.namespace import OWL, RDF, RDFS  # type: ignore
 from tqdm import tqdm  # type: ignore
@@ -65,15 +65,19 @@ def __init__(self, gcs_bucket: Union[storage.bucket.Bucket, str], org_data: str,
             'http://www.genenames.org/cgi-bin/gene_symbol_report?hgnc_id=18372': ['653067', '653220'],
             'http://www.genenames.org/cgi-bin/gene_symbol_report?hgnc_id=26418': ['132332'],
             'http://www.genenames.org/cgi-bin/gene_symbol_report?hgnc_id=30679': ['653067', '653220'],
-            'http://www.genenames.org/cgi-bin/gene_symbol_report?hgnc_id=26844': ['5414']
+            'http://www.genenames.org/cgi-bin/gene_symbol_report?hgnc_id=26844': ['5414'],
+            'http://www.genenames.org/cgi-bin/gene_symbol_report?hgnc_id=31447': ['10529'],
+            'http://www.genenames.org/cgi-bin/gene_symbol_report?hgnc_id=33870': ['114112'],
+            'http://www.genenames.org/cgi-bin/gene_symbol_report?hgnc_id=8103': ['10896'],
+            'http://www.genenames.org/cgi-bin/gene_symbol_report?hgnc_id=26619': ['5058'],
+            'http://www.genenames.org/cgi-bin/gene_symbol_report?hgnc_id=31424': ['101362076']
         }
         f_data = self.temp_dir + '/Merged_gene_rna_protein_identifiers.pkl'
         if not os.path.exists(f_data):
-            url = 'https://storage.googleapis.com/pheknowlator/release_v2.0.0/build_31DEC2020/data/processed_data/'
+            url = 'https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/processed_data'
             data_downloader(url + f_data.split('/')[-1], self.temp_dir + '/')
-            self.gene_ids = pickle.load(open(f_data, 'rb'), encoding='bytes')
-        else:
-            self.gene_ids = pickle.load(open(f_data, 'rb'), encoding='bytes')
+        with open(f_data, 'rb') as out:
+            self.gene_ids = pickle.load(out, encoding='bytes')
 
     def uploads_data_to_gcs_bucket(self, file_loc: str) -> None:
         """Takes a file name and pushes the corresponding data referenced by the filename object from a local
@@ -115,9 +119,9 @@ def downloads_data_from_gcs_bucket(self, filename: str) -> Optional[str]:
                     self.bucket.blob(match_file).download_to_filename(self.temp_dir + '/' + match_file.split('/')[-1])
             except IndexError:
                 raise ValueError('{} Not in the GCS original_data Directory of the Current Build'.format(filename))
-
             return data_file
-        else: return None
+        else:
+            return None
 
     def reads_gcs_bucket_data_to_graph(self, file_location: str) -> Graph:
         """Reads data corresponding to the input file_location variable into a Pandas DataFrame.
@@ -166,14 +170,21 @@ def updates_ontology_reporter(self) -> None:
 
         print('Obtaining Ontology Statistics')
 
-        gcs_org_url = 'https://storage.googleapis.com/pheknowlator/{}'.format(self.original_data)
-        gcs_proc_url = 'https://storage.googleapis.com/pheknowlator/{}'.format(self.processed_data)
+        if isinstance(self.bucket, str):
+            gcs_org_url = '{}/{}'.format(self.temp_dir, self.original_data)
+            gcs_proc_url = gcs_org_url
+        else:
+            gcs_org_url = 'https://storage.googleapis.com/pheknowlator/{}'.format(self.original_data)
+            gcs_proc_url = 'https://storage.googleapis.com/pheknowlator/{}'.format(self.processed_data)
+
+        # write out statistics
         if 'Starting Statistics' not in self.ontology_info[self.ont_file_location].keys():
             self.ontology_info[self.ont_file_location] = {}
             self.ontology_info[self.ont_file_location]['Original GCS URL'] = gcs_org_url + self.ont_file_location
             self.ontology_info[self.ont_file_location]['Processed GCS URL'] = gcs_proc_url + self.ont_file_location
             key = 'Starting Statistics'
-        else: key = 'Final Statistics'
+        else:
+            key = 'Final Statistics'
         classes, obj_props = len(gets_ontology_classes(self.ont_graph)), len(gets_object_properties(self.ont_graph))
         triples, indv = len(self.ont_graph), len(list(self.ont_graph.triples((None, RDF.type, OWL.NamedIndividual))))
         stats = '{} Classes; {} Object Properties; {} Triples; {} Individuals'.format(classes, obj_props, triples, indv)
@@ -189,10 +200,14 @@ def _finds_ontology_errors(self) -> Dict:
         """
 
         errors = {}
-        try: _ = get_ontology(self.temp_dir + '/' + self.ont_file_location).load()  # type: ignore
-        except OwlReadyOntologyParsingError as o: errors['OwlReadyOntologyParsingError'] = str(o)  # type: ignore
-        except KeyError as k: errors['KeyError'] = str(k)
-        except TypeError as p: errors['PunningError'] = str(p)
+        try:
+            _ = get_ontology(self.temp_dir + '/' + self.ont_file_location).load()  # type: ignore
+        except OwlReadyOntologyParsingError as o:
+            errors['OwlReadyOntologyParsingError'] = str(o)  # type: ignore
+        except KeyError as k:
+            errors['KeyError'] = str(k)
+        except TypeError as p:
+            errors['PunningError'] = str(p)
 
         return errors
 
@@ -283,8 +298,8 @@ def removes_deprecated_obsolete_entities(self) -> None:
         for node in list(deprecated_classes) + list(obsolete_classes):
             self.ont_graph.remove((node, None, None))
 
-        self.ontology_info[key]['Deprecated'] = len(deprecated_classes) if len(deprecated_classes) > 0 else 'None'
-        self.ontology_info[key]['Obsolete'] = len(obsolete_classes) if len(obsolete_classes) > 0 else 'None'
+        self.ontology_info[key]['Deprecated'] = deprecated_classes if len(deprecated_classes) > 0 else 'None'
+        self.ontology_info[key]['Obsolete'] = obsolete_classes if len(obsolete_classes) > 0 else 'None'
 
         return None
 
@@ -293,10 +308,7 @@ def fixes_punning_errors(self) -> None:
             (1) Entities typed as an owl:Class and owl:ObjectProperty --> removes owl:ObjectProperty
             (2) Entities typed as an owl:Class and owl:NamedIndividual --> removes owl:NamedIndividual
             (3) Entities typed as an OWL:ObjectProperty and owl:AnnotationProperty --> removes owl:AnnotationProperty
-            (4) owl:ObjectProperties that are rdfs:subProperty of an owl:AnnotationProperty --> remove rdfs:subProperty
-
-        NOTE. Even though a run will yield a clean a result, reading back in the ontology using any OWL API-based
-        tools (e.g. OWL Tools) will result in a single punning error for RO_0002161.
+              - Any owl:ObjectProperty redeclared as an owl:AnnotationProperty never used in other triples are removed
 
         Returns:
              None.
@@ -304,40 +316,39 @@ def fixes_punning_errors(self) -> None:
 
         print('Resolving Punning Errors')
 
-        key, bad_cls, bad_obj, bad_oth = self.ont_file_location, set(), set(), []
+        key, bad_cls, bad_obj = self.ont_file_location, set(), set()
         onts_entities = set([x[0] for x in tqdm(self.ont_graph)])
         for x in tqdm(onts_entities):
             ent_types = list(self.ont_graph.triples((x, RDF.type, None)))
-            subpro = list(self.ont_graph.triples((x, RDFS.subPropertyOf, None)))
             if len(ent_types) > 1:
                 if not any([x for x in ent_types if 'owl' not in str(x[2])]):
                     # class + object properties --> remove object properties
                     class_prop, obj_prop = (x, RDF.type, OWL.Class), (x, RDF.type, OWL.ObjectProperty)
-                    if (class_prop in ent_types and obj_prop in ent_types) and str(x) not in bad_cls:
+                    if class_prop in ent_types and obj_prop in ent_types and str(x) not in bad_cls:
                         self.ont_graph.remove(obj_prop)
                         bad_cls.add(str(x))
                     # class + individual --> remove individual
                     class_prop, ind_prop = (x, RDF.type, OWL.Class), (x, RDF.type, OWL.NamedIndividual)
-                    if (class_prop in ent_types and ind_prop in ent_types) and str(x) not in bad_cls:
+                    if class_prop in ent_types and ind_prop in ent_types and str(x) not in bad_cls:
                         self.ont_graph.remove(ind_prop)
                         bad_cls.add(str(x))
                     # object property + annotation property --> remove annotation property
                     obj_prop, a_prop = (x, RDF.type, OWL.ObjectProperty), (x, RDF.type, OWL.AnnotationProperty)
-                    if (obj_prop in ent_types and a_prop in ent_types) and str(x) not in bad_obj:
+                    if obj_prop in ent_types and a_prop in ent_types and str(x) not in bad_obj:
                         self.ont_graph.remove(a_prop)
                         bad_obj.add(str(x))
-                        # object properties that are subproperty of something that is not an object property
-                        if len(subpro) > 0 and any([x for x in ent_types if OWL.ObjectProperty == x[2]]):
-                            for s in subpro:
-                                for sp in list(self.ont_graph.triples((s[2], RDF.type, None))):
-                                    if OWL.ObjectProperty != sp[2]:
-                                        self.ont_graph.remove(sp)
-                                        bad_obj.add(str(x))
-                    else: bad_oth += [str(ent_types[0]) + '-' + '/'.join([str(x[2]) for x in ent_types])]
+                        # check if the object property is used beyond definition -- if not, delete it
+                        out_edges = list(self.ont_graph.triples((x, None, None)))
+                        in_edges = list(self.ont_graph.triples((None, None, x)))
+                        val_nodes = [i for i in out_edges + in_edges if len(
+                            [j[2] for j in list(self.ont_graph.triples((i, RDF.type, OWL.Class)))] +
+                            [j[2] for j in list(self.ont_graph.triples((i, RDF.type, OWL.NamedIndividual)))]) > 0]
+                        if len(val_nodes) == 0:
+                            self.ont_graph.remove((x, None, None))
+                            bad_obj.add(str(x))
 
         self.ontology_info[key]['PunningErrors - Classes'] = ', '.join(bad_cls) if len(bad_cls) > 0 else 'None'
         self.ontology_info[key]['PunningErrors - ObjectProperty'] = ', '.join(bad_obj) if len(bad_obj) > 0 else 'None'
-        self.ontology_info[key]['PunningErrors - Other'] = ', '.join(bad_oth) if len(bad_oth) > 0 else 'None'
 
         return None
 
@@ -408,7 +419,7 @@ def normalizes_existing_classes(self) -> None:
                         self.ont_graph.add((edge[0], edge[1], i))
                         self.ont_graph.remove(edge)
 
-        no_ont = len(non_ont)-len(hgnc)
+        no_ont = len(non_ont) - len(hgnc)
         self.ontology_info[self.ont_file_location]['Normalized - NonOnt'] = no_ont if no_ont != 0 else 'None'
         self.ontology_info[self.ont_file_location]['Normalized - Gene IDs'] = len(hgnc) if len(hgnc) > 0 else 'None'
 
@@ -436,11 +447,19 @@ def generates_ontology_report(self) -> None:
                 if 'Final Statistics' in x.keys(): o.write('\t\t- After Cleaning: {}\n'.format(x['Final Statistics']))
                 if 'ValueErrors' in x.keys(): o.write('\t- Value Errors: {}\n'.format(x['ValueErrors']))
                 if 'IdentifierErrors' in x.keys(): o.write('\t- Identifier Errors: {}\n'.format(x['IdentifierErrors']))
-                if 'Deprecated' in x.keys(): o.write('\t- Deprecated Classes: {}\n'.format(x['Deprecated']))
-                if 'Obsolete' in x.keys(): o.write('\t- Obsolete Classes: {}\n'.format(x['Obsolete']))
+                if 'PheKnowLator_MergedOntologies' not in key:
+                    if x['Deprecated'] != 'None':
+                        o.write('\t- Deprecated Classes:\n')
+                        for i in x['Deprecated']: o.write('\t\t- {}\n'.format(str(i)))
+                    else: o.write('\t\t\t- {}\n'.format(x['Deprecated']))
+                    if x['Obsolete'] != 'None':
+                        o.write('\t- Obsolete Classes:\n')
+                        for i in x['Obsolete']: o.write('\t\t- {}\n'.format(str(i)))
+                    else: o.write('\t\t\t- {}\n'.format(x['Obsolete']))
                 o.write('\t- Punning Error:\n\t\t- Classes:\n')
                 if x['PunningErrors - Classes'] != 'None':
-                    for i in x['PunningErrors - Classes'].split(', '): o.write('\t\t\t- {}\n'.format(i))
+                    for i in x['PunningErrors - Classes'].split(', '):
+                        o.write('\t\t\t- {}\n'.format(i))
                 else: o.write('\t\t\t- {}\n'.format(x['PunningErrors - Classes']))
                 o.write('\t\t- ObjectProperties:\n')
                 if x['PunningErrors - ObjectProperty'] != 'None':
@@ -470,7 +489,7 @@ def cleans_ontology_data(self) -> None:
             None.
         """
 
-        print('*** CLEANING ONTOLOGY DATA SOURCES ***')
+        print('*** CLEANING INDIVIDUAL ONTOLOGY DATA SOURCES ***')
 
         for ont in self.ontology_info.keys():
             if ont != self.merged_ontology_filename:
@@ -484,7 +503,7 @@ def cleans_ontology_data(self) -> None:
                 self.updates_ontology_reporter()  # get finishing statistics
                 self._logically_verifies_cleaned_ontologies()
 
-        print('\n\n*** CLEANING ONTOLOGY DATA SOURCES ***')
+        print('\n\n*** CLEANING MERGED ONTOLOGY DATA ***')
         self.ont_file_location = self.merged_ontology_filename
         onts = [self.temp_dir + '/' + x for x in list(self.ontology_info.keys())
                 if x != self.merged_ontology_filename]
diff --git a/pkt_kg/construction_approaches.py b/pkt_kg/construction_approaches.py
index e2a3814d6..23471fa45 100644
--- a/pkt_kg/construction_approaches.py
+++ b/pkt_kg/construction_approaches.py
@@ -20,11 +20,6 @@
 pkt = Namespace('https://github.com/callahantiff/PheKnowLator/pkt/')
 
 
-# TODO: extend functionality to improve KR for:
-#  (1) instance-based builds that includes connections between 2 instance nodes
-#  (2) the ability to combine instance and subclass-based methods
-
-
 class KGConstructionApproach(object):
     """Class stores different methods that can be used to construct knowledge graph edges.
 
@@ -66,8 +61,7 @@ def __init__(self, edge_dict: Dict, write_location: str) -> None:
 
         # LOADING SUBCLASS DICTIONARY
         file_name = self.write_location + '/construction_*/*.pkl'
-        if len(glob.glob(file_name)) == 0:
-            raise OSError('{} does not exist!'.format('subclass_construction_map.pkl'))
+        if len(glob.glob(file_name)) == 0: raise OSError('{} does not exist!'.format('subclass_construction_map.pkl'))
         elif os.stat(glob.glob(file_name)[0]).st_size == 0:
             raise TypeError('The input file: {} is empty'.format(glob.glob(file_name)[0]))
         else:
@@ -95,28 +89,26 @@ def maps_node_to_class(self, edge_type: str, entity: str, edge: List) -> Optiona
             non-class entity node is returned.
         """
 
+        e_type = edge_type
         if entity not in self.subclass_dict.keys():
-            if self.subclass_error and edge_type in self.subclass_error.keys():
-                self.subclass_error[edge_type] += [entity]
-            else:
-                self.subclass_error[edge_type] = [entity]
+            if self.subclass_error and e_type in self.subclass_error.keys(): self.subclass_error[e_type] += [entity]
+            else: self.subclass_error[e_type] = [entity]
             # remove the edge from the edge_dict
-            self.edge_dict[edge_type]['edge_list'].pop(self.edge_dict[edge_type]['edge_list'].index(edge))
+            self.edge_dict[e_type]['edge_list'].pop(self.edge_dict[e_type]['edge_list'].index(edge))
             subclass_map = None
-        else:
-            subclass_map = self.subclass_dict[entity]
+        else: subclass_map = self.subclass_dict[entity]
 
         return subclass_map
 
     @staticmethod
-    def edge_constructor(node1: Union[BNode, URIRef], node2: Union[BNode, URIRef], relation: URIRef,
-                         inverse_relation: Optional[URIRef]) -> Tuple:
-        """Constructs a single edge between to ontology classes as well as verifies if the user wants an inverse edge
-        created and if so, then this edge is also added to the knowledge graph.
+    def subclass_edge_constructor(node1: Union[BNode, URIRef], node2: Union[BNode, URIRef], relation: URIRef,
+                                  inverse_relation: Optional[URIRef]) -> Tuple:
+        """Core subclass-based edge construction method. Constructs a single edge between to ontology classes as well as
+        verifies if the user wants an inverse edge created and if so, then this edge is also added to the knowledge
+        graph.
 
-        Note. We explicitly type each node as a owl:Class and each relation/inverse relation as a owl:ObjectProperty.
-        This may seem redundant, but it is needed in order to ensure consistency between the data after applying the
-        OWL API to reformat the data.
+        Note. We explicitly type each node and each relation/inverse relation. This may seem redundant, but it is
+        needed in order to ensure consistency between the data after applying the OWL API to reformat the data.
 
         Args:
             node1: A URIRef or BNode object containing a subject node.
@@ -177,7 +169,7 @@ def subclass_constructor(self, edge_info: Dict, edge_type: str) -> Tuple[Dict, L
         edges: List = []
 
         if res['cls1'] and res['cls2']:  # class-class edges
-            edges = list(self.edge_constructor(URIRef(res['cls1']), URIRef(res['cls2']), rel, irel))
+            edges = list(self.subclass_edge_constructor(URIRef(res['cls1']), URIRef(res['cls2']), rel, irel))
         elif res['cls1'] and res['ent1']:  # subclass-class/class-subclass edges
             x = res['ent1'].replace(uri2, '') if edge_info['n1'] == 'class' else res['ent1'].replace(uri1, '')
             mapped_node = self.maps_node_to_class(edge_type, x, edge_info['edges'])
@@ -186,9 +178,9 @@ def subclass_constructor(self, edge_info: Dict, edge_type: str) -> Tuple[Dict, L
                                      ((URIRef(obo + x), RDF.type, OWL.Class),) for x in mapped_node] for x in y]
                 # add edge with relation/inverse relation (if it exists)
                 if edge_info['n1'] == 'class':  # determine node order
-                    edges += self.edge_constructor(URIRef(res['cls1']), URIRef(res['ent1']), rel, irel)
+                    edges += self.subclass_edge_constructor(URIRef(res['cls1']), URIRef(res['ent1']), rel, irel)
                 else:
-                    edges += self.edge_constructor(URIRef(res['ent1']), URIRef(res['cls1']), rel, irel)
+                    edges += self.subclass_edge_constructor(URIRef(res['ent1']), URIRef(res['cls1']), rel, irel)
         else:  # subclass-subclass edges
             mapped_node1 = self.maps_node_to_class(edge_type, res['ent1'].replace(uri1, ''), edge_info['edges'])
             mapped_node2 = self.maps_node_to_class(edge_type, res['ent2'].replace(uri2, ''), edge_info['edges'])
@@ -198,10 +190,51 @@ def subclass_constructor(self, edge_info: Dict, edge_type: str) -> Tuple[Dict, L
                 edges += [x for y in [((URIRef(res['ent2']), RDFS.subClassOf, URIRef(obo + x)),) +
                                       ((URIRef(obo + x), RDF.type, OWL.Class),) for x in mapped_node2] for x in y]
                 # add edge with relation/inverse relation (if it exists)
-                edges += self.edge_constructor(URIRef(res['ent1']), URIRef(res['ent2']), rel, irel)
+                edges += self.subclass_edge_constructor(URIRef(res['ent1']), URIRef(res['ent2']), rel, irel)
 
         return self.edge_dict, edges
 
+    @staticmethod
+    def instance_edge_constructor(node1: Union[BNode, URIRef], node2: Union[BNode, URIRef], relation: URIRef,
+                                  inverse_relation: Optional[URIRef]) -> Tuple:
+        """Core instance-based edge construction method. Constructs a single edge between to ontology classes as well as
+        verifies if the user wants an inverse edge created and if so, then this edge is also added to the knowledge
+        graph.
+
+        Note. We explicitly type each node and each relation/inverse relation. This may seem redundant,
+        but it is needed in order to ensure consistency between the data after applying the OWL API to reformat the
+        data.
+
+        Args:
+            node1: A URIRef or BNode object containing a subject node.
+            node2: A URIRef or BNode object containing a object node.
+            relation: A URIRef object containing an owl:ObjectProperty.
+            inverse_relation: A string containing the identifier for an inverse relation (i.e. "RO_0002200") or None
+                (i.e. indicator of no inverse relation).
+
+        Returns:
+            A list of tuples representing new edges to add to the knowledge graph.
+        """
+
+        new_edge_inverse_rel: Tuple = tuple()
+        node1_sha = URIRef(pkt + 'N' + hashlib.md5(node1.encode()).hexdigest())
+        node2_sha = URIRef(pkt + 'N' + hashlib.md5(node2.encode()).hexdigest())
+
+        # creates a restriction in order to connect two nodes using the input relation
+        new_edge_rel_only = ((node1, RDF.type, OWL.Class),
+                             (node2, RDF.type, OWL.Class),
+                             (node1_sha, RDF.type, node1),
+                             (node1_sha, RDF.type, OWL.NamedIndividual),
+                             (node2_sha, RDF.type, node2),
+                             (node2_sha, RDF.type, OWL.NamedIndividual),
+                             (node1_sha, relation, node2_sha),
+                             (relation, RDF.type, OWL.ObjectProperty))
+        if inverse_relation:
+            new_edge_inverse_rel = ((inverse_relation, RDF.type, OWL.ObjectProperty),
+                                    (node2_sha, inverse_relation, node1_sha))
+
+        return new_edge_rel_only + new_edge_inverse_rel
+
     def instance_constructor(self, edge_info: Dict, edge_type: str) -> Tuple[Dict, List]:
         """Adds edges for the instance construction approach.
 
@@ -226,7 +259,7 @@ def instance_constructor(self, edge_info: Dict, edge_type: str) -> Tuple[Dict, L
         edges: List = []
 
         if res['cls1'] and res['cls2']:  # class-class edges
-            edges = list(self.edge_constructor(URIRef(res['cls1']), URIRef(res['cls2']), rel, irel))
+            edges = list(self.instance_edge_constructor(URIRef(res['cls1']), URIRef(res['cls2']), rel, irel))
         elif res['cls1'] and res['ent1']:  # class-instance/instance-class edges
             sha_uid = URIRef(pkt + 'N' + hashlib.md5(res['cls1'].encode()).hexdigest())
             x = res['ent1'].replace(uri2, '') if edge_info['n1'] == 'class' else res['ent1'].replace(uri1, '')
@@ -247,10 +280,9 @@ def instance_constructor(self, edge_info: Dict, edge_type: str) -> Tuple[Dict, L
                     edges += [(URIRef(res['ent1']), rel, sha_uid), (rel, RDF.type, OWL.ObjectProperty)]
                     if irel: edges += [(sha_uid, irel, URIRef(res['ent1'])), (irel, RDF.type, OWL.ObjectProperty)]
         else:  # instance-instance edges
-            mapped_node1 = self.maps_node_to_class(edge_type, res['ent1'].replace(uri1, ''), edge_info['edges'])
-            mapped_node2 = self.maps_node_to_class(edge_type, res['ent2'].replace(uri2, ''), edge_info['edges'])
-            if mapped_node1 and mapped_node2:
-                # type non-class nodes
+            map_node1 = self.maps_node_to_class(edge_type, res['ent1'].replace(uri1, ''), edge_info['edges'])
+            map_node2 = self.maps_node_to_class(edge_type, res['ent2'].replace(uri2, ''), edge_info['edges'])
+            if map_node1 and map_node2:
                 n1, n2 = res['ent1'], res['ent2']
                 edges = [(URIRef(x), RDF.type, OWL.NamedIndividual) for x in [n1, n2]]
                 # add edge with relation/inverse relation (if it exists)
@@ -258,8 +290,8 @@ def instance_constructor(self, edge_info: Dict, edge_type: str) -> Tuple[Dict, L
                 if irel: edges += [(URIRef(n2), irel, URIRef(n1)), (irel, RDF.type, OWL.ObjectProperty)]
                 # get non-class node mappings to ontology classes
                 edges += [x for y in [((URIRef(n1), RDF.type, URIRef(obo + x)),) +
-                                      ((URIRef(obo + x), RDF.type, OWL.Class),) for x in mapped_node1] for x in y]
+                                      ((URIRef(obo + x), RDF.type, OWL.Class),) for x in map_node1] for x in y]
                 edges += [x for y in [((URIRef(n2), RDF.type, URIRef(obo + x)),) +
-                                      ((URIRef(obo + x), RDF.type, OWL.Class),) for x in mapped_node2] for x in y]
+                                      ((URIRef(obo + x), RDF.type, OWL.Class),) for x in map_node2] for x in y]
 
         return self.edge_dict, edges
diff --git a/pkt_kg/edge_list.py b/pkt_kg/edge_list.py
index 6cadda8bf..1ccf0edd3 100755
--- a/pkt_kg/edge_list.py
+++ b/pkt_kg/edge_list.py
@@ -79,7 +79,6 @@ def identify_header(file_path: str, delimiter: str, skip_rows: List[int]) -> Opt
                                               '|'.join([str(x) for x in list(df_without_header)])).ratio()
         # determine if header should be used
         if abs(with_header_test-without_header_test) < 0.5: return 0
-        elif with_header_test >= without_header_test: return None
         else: return None
 
     def data_reader(self, file_path: str, delimiter: str = 't') -> pd.DataFrame:
@@ -120,8 +119,7 @@ def data_reader(self, file_path: str, delimiter: str = 't') -> pd.DataFrame:
         edge_data = pd.read_csv(file_path, header=header, delimiter=splitter, low_memory=False, skiprows=skip)
         # clean environment and return verified data
         del data, skip
-        if len(list(edge_data)) >= 2 and len(edge_data) > 10: return edge_data.fillna('None', inplace=False)
-        else: raise ValueError('ERROR: Data could not be properly read in')
+        return edge_data.fillna('None', inplace=False)
 
     @staticmethod
     def filter_fixer(criteria):
@@ -187,9 +185,8 @@ def filter_data(self, edge_data: pd.DataFrame, filter_criteria: str, evidence_cr
                         else:
                             exp = '{} {} "{}"'.format('x', crit.split(';')[1], crit.split(';')[2].replace("'", ''))
                     edge_data = edge_data.loc[edge_data[col].apply(lambda x: eval(exp))]
-            # verify and return data
-            if len(list(edge_data)) >= 2 and len(edge_data) >= 1: return edge_data
-            else: raise Exception('ERROR: Data could not be properly read in')
+
+            return edge_data
 
     @staticmethod
     def data_reducer(cols: str, edge_data: pd.DataFrame) -> pd.DataFrame:
@@ -318,6 +315,30 @@ def process_mapping_data(self, mapping_data: str, edge_data: pd.DataFrame) -> Tu
 
             return tuple(zip(list(merged_data[maps[0][0]]), list(merged_data[maps[1][0]])))
 
+    def gets_entity_namespaces(self) -> None:
+        """Identifies namespaces for all non-ontology entities. This is achieved by adding an entity_namespace key to
+        the source_info dictionary, which contains a sub-dictionary keyed by edge entity with it's associated URL as
+        the value. For example:
+            source_info['entity_namespaces']: {'gene': 'http://www.ncbi.nlm.nih.gov/gene/',
+                                               'pathway': 'https://reactome.org/content/detail/',
+                                               'rna': 'https://uswest.ensembl.org/Homo_sapiens/Transcript/Summary?t=',
+                                               'variant': 'https://www.ncbi.nlm.nih.gov/snp/'}
+
+        Returns:
+            None.
+        """
+
+        self.source_info['entity_namespaces'] = {}
+        for key in [x for x in self.source_info.keys() if x != 'entity_namespaces']:
+            data_type, uri = self.source_info[key]['data_type'].split('-'), self.source_info[key]['uri']
+            if data_type != ['class', 'class']:
+                entities = [key.split('-')[data_type.index(x)] for x in data_type if x == 'entity']
+                namespaces = [uri[data_type.index(x)] for x in data_type if x == 'entity']
+                for x in zip(entities, namespaces):
+                    self.source_info['entity_namespaces'][x[0]] = x[1]
+
+        return None
+
     def creates_knowledge_graph_edges(self) -> None:
         """Generates edge lists for each edge type in an input dictionary. In order to generate the edge list,
         the function performs six steps: (1) read in data; (2) apply filtering and evidence criteria; (3) reduce data
@@ -336,12 +357,12 @@ def creates_knowledge_graph_edges(self) -> None:
         for edge_type in tqdm(self.source_info.keys()):
             print('\n\n### Processing Edge: {}'.format(edge_type))
 
-            # STEP 1: read in data
+            # STEP 1: Read Data
             print('*** Reading Edge Data ***')
-            edge_data = self.data_reader(self.data_files[edge_type],
-                                         self.source_info[edge_type]['delimiter'])
+            edge_data = self.data_reader(self.data_files[edge_type], self.source_info[edge_type][
+                'delimiter'])
 
-            # STEP 2: apply filtering and evidence criteria
+            # STEP 2: Apply Filtering and Evidence Criteria
             print('*** Applying Filtering and/or Mapping Criteria to Edge Data ***')
             edge_data = self.filter_data(edge_data,
                                          self.source_info[edge_type]['filter_criteria'],
@@ -350,28 +371,30 @@ def creates_knowledge_graph_edges(self) -> None:
             # STEP 3: reduce data to specific columns, remove duplicates, and ensure proper formatting of column data
             edge_data = self.data_reducer(self.source_info[edge_type]['column_idx'], edge_data)
 
-            # STEP 4: update node column values
+            # STEP 4: Update Node Column Values
             print('*** Reformatting Node Values ***')
             edge_data = self.label_formatter(edge_data, self.source_info[edge_type]['source_labels'])
 
-            # STEP 5: rename nodes
+            # STEP 5: Rename Nodes
             edge_data.rename(columns={list(edge_data)[0]: str(list(edge_data)[0]) + '-' + edge_type.split('-')[0],
                                       list(edge_data)[1]: str(list(edge_data)[1]) + '-' + edge_type.split('-')[1]},
                              inplace=True)
 
-            # STEP 6: map identifiers
+            # STEP 6: Map Identifiers
             print('*** Performing Identifier Mapping ***')
-            mapped_data = self.process_mapping_data(self.source_info[edge_type]['identifier_maps'],
-                                                    edge_data)
-
+            mapped_data = self.process_mapping_data(self.source_info[edge_type]['identifier_maps'], edge_data)
             self.source_info[edge_type]['edge_list'] = [edge for edge in mapped_data if 'None' not in edge]
 
-            # print edge statistics
+            # print Edge Statistics
             unique_edges = [list(y) for y in set([tuple(x) for x in self.source_info[edge_type]['edge_list']])]
             print('\nPROCESSED EDGE: {}'.format(edge_type))
-            print('Total Unique Edge Count: {}'.format(len(unique_edges)))
             print('{}: Unique Node Count = {}'.format(edge_type.split('-')[0], len(set([x[0] for x in unique_edges]))))
             print('{}: Unique Node Count = {}'.format(edge_type.split('-')[1], len(set([x[1] for x in unique_edges]))))
+            print('Total Unique Edge Count: {}'.format(len(unique_edges)))
+            print('Total Unique Edge Count + Inverses: {}'.format(len(unique_edges)*2))
+
+        # add source entity namespaces
+        self.gets_entity_namespaces()
 
         # save a copy of the final master edge list
         with open('/'.join(self.source_file.split('/')[:-1]) + '/Master_Edge_List_Dict.json', 'w') as filepath:
diff --git a/pkt_kg/knowledge_graph.py b/pkt_kg/knowledge_graph.py
index a9c10d55b..be0eaf767 100644
--- a/pkt_kg/knowledge_graph.py
+++ b/pkt_kg/knowledge_graph.py
@@ -40,9 +40,10 @@ class KGBuilder(object):
 
     Attributes:
         construction: A string that indicates what type of construction approach to use (i.e. instance or subclass).
-        node_data: A filepath to a directory called 'node_data' containing a file for each instance node.
-        inverse_relations: A filepath to a directory called 'relations_data' containing the relations data.
-        decode_owl: A string indicating whether edges containing owl semantics should be removed.
+        node_data: A string containing "yes" or "no" indicating whether or not to add node data to the knowledge graph.
+        inverse_relations: A string containing "yes" or "no" indicating whether or not to add inverse relations to the
+            knowledge graph.
+        decode_owl: A string containing "yes" or "no" indicating whether owl semantics should be removed.
         write_location: An optional string passed to specify the primary directory to write to.
 
     Raises:
@@ -61,24 +62,18 @@ class KGBuilder(object):
     def __init__(self, construction: str, node_data: str, inverse_relations: str, decode_owl: str,
                  write_location: str = os.path.abspath('./resources/knowledge_graphs')) -> None:
 
-        self.kg_version = 'v' + __version__
         self.build: str = self.gets_build_type().lower().split()[0]
-        self.decode_owl: Optional[str] = None
-        self.edge_dict: Dict = dict()
         self.graph: Graph = Graph()
-        self.inverse_relations: Optional[List] = None
-        self.inverse_relations_dict: Optional[Dict] = None
-        self.node_data: Optional[List] = None
-        self.node_dict: Optional[Dict] = None
-        self.ont_classes: Set = set()
+        self.kg_version: str = 'v' + __version__
         self.obj_properties: Set = set()
-        self.owl_tools = './pkt_kg/libs/owltools'
+        self.ont_classes: Set = set()
+        self.owl_tools: str = './pkt_kg/libs/owltools'
         self.relations_dict: Dict = dict()
-        self.write_location = write_location
-        self.res_dir = os.path.abspath('/'.join(self.write_location.split('/')[:-1]))
+        self.write_location: str = write_location
+        self.res_dir: str = os.path.abspath('/'.join(self.write_location.split('/')[:-1]))
         self.merged_ont_kg: str = self.write_location + '/PheKnowLator_MergedOntologies.owl'
 
-        # FINDING ONTOLOGIES DATA DIRECTORY
+        # ONTOLOGIES DATA DIRECTORY
         ontologies = glob.glob(self.res_dir + '/ontologies/*.owl')
         if not os.path.exists(self.res_dir + '/ontologies'): raise OSError("Can't find 'ontologies/' directory")
         elif len(ontologies) == 0: raise TypeError('The ontologies directory is empty')
@@ -94,26 +89,33 @@ def __init__(self, construction: str, node_data: str, inverse_relations: str, de
         if not os.path.exists(edge_data): raise OSError('The {} file does not exist!'.format(edge_data))
         elif os.stat(edge_data).st_size == 0: raise TypeError('The input file: {} is empty'.format(edge_data))
         else:
-            with(open(edge_data, 'r')) as _file: self.edge_dict = json.load(_file)
+            with(open(edge_data, 'r')) as _file: self.edge_dict: Dict = json.load(_file)
 
         # RELATIONS DATA
         inv, rel_dir = str(inverse_relations).lower(), glob.glob(self.res_dir + '/relations_data/*.txt')
         if inv not in ['yes', 'no']: raise ValueError('inverse_relations not "no" or "yes"')
         elif len(rel_dir) == 0: raise TypeError('relations_data directory is empty')
-        elif inv == 'yes': self.inverse_relations, self.inverse_relations_dict, rel = rel_dir, {}, '_inverseRelations'
+        elif inv == 'yes':
+            self.inverse_relations: Optional[List] = rel_dir
+            self.inverse_relations_dict: Optional[Dict] = {}
+            rel = '_inverseRelations'
         else: self.inverse_relations, self.inverse_relations_dict, rel = None, None, '_relationsOnly'
 
         # NODE METADATA
-        node_data, node_dir = str(node_data).lower(), glob.glob(self.res_dir + '/node_data/*.txt')
+        node_data, node_dir = str(node_data).lower(), glob.glob(self.res_dir + '/node_data/*.pkl')
         if node_data not in ['yes', 'no']: raise ValueError('node_data not "no" or "yes"')
         elif node_data == 'yes' and len(node_dir) == 0: raise TypeError('node_data directory is empty')
-        elif node_data == 'yes' and len(node_dir) != 0: self.node_data, self.node_dict = node_dir, dict()
+        elif node_data == 'yes' and len(node_dir) != 0:
+            self.node_data: Optional[List] = node_dir
+            self.node_dict: Optional[Dict] = dict()
         else: self.node_data, self.node_dict = None, None
 
         # OWL SEMANTICS
         decode_owl = str(decode_owl).lower()
         if decode_owl not in ['yes', 'no']: raise ValueError('decode_semantics not "no" or "yes"')
-        elif decode_owl == 'yes': self.decode_owl, owl_kg = decode_owl, '_noOWL'
+        elif decode_owl == 'yes':
+            self.decode_owl: Optional[str] = decode_owl
+            owl_kg = '_noOWL'
         else: self.decode_owl, owl_kg = None, '_OWL'
 
         # KG FILE NAME
@@ -180,13 +182,11 @@ class nodes are also present in the current list of classes from the merged onto
             False - if the edge contains at least 1 ontology class that is not present in the knowledge graph.
         """
 
-        if edge_info['n1'] != 'class' and edge_info['n2'] != 'class':
-            class_found = True
+        if edge_info['n1'] != 'class' and edge_info['n2'] != 'class': class_found = True
         elif edge_info['n1'] == 'class' and edge_info['n2'] == 'class':
             n1, n2 = URIRef(obo + edge_info['edges'][0]), URIRef(obo + edge_info['edges'][1])
             class_found = n1 in self.ont_classes and n2 in self.ont_classes
-        else:
-            class_found = URIRef(finds_node_type(edge_info)['cls1']) in self.ont_classes
+        else: class_found = URIRef(finds_node_type(edge_info)['cls1']) in self.ont_classes
 
         return class_found
 
@@ -223,6 +223,27 @@ def checks_for_inverse_relations(self, relation: str, edge_list: List[List[str]]
 
         return inverse_relation
 
+    def gets_edge_statistics(self, edge_type: str, invrel: Optional[str], results: List) -> None:
+        """Calculates the number of nodes and edges created from the build process.
+
+        Args:
+            edge_type: A string point to a specific edge type (e.g. 'chemical-disease).
+            invrel: A string if there are inverse edges or None.
+            results: A list of tuples representing the complete set of triples generated from the construction process.
+
+        Returns:
+            None
+        """
+
+        n1, n2, edges = edge_type.split('-')[0], edge_type.split('-')[1], self.edge_dict[edge_type]['edge_list']
+
+        print('Total OWL Edges: {}'.format(len(set(results))))
+        print('Unique Non-OWL Edges: {}'.format(len(edges) * 2 if invrel else len(edges)))
+        print('Unique {}: {}'.format(n1, len(set([x[0] for x in self.edge_dict[edge_type]['edge_list']]))))
+        print('Unique {}: {}'.format(n2, len(set([x[1] for x in self.edge_dict[edge_type]['edge_list']]))))
+
+        return None
+
     def creates_knowledge_graph_edges(self, node_metadata_func: Callable, ontology_annotator_func: Callable) -> None:
         """Takes a nested dictionary of edge lists and adds them to an existing knowledge graph by their edge_type (
         e.g. chemical-gene). Once the knowledge graph is complete, it is written out as an `.owl` file to the
@@ -237,48 +258,42 @@ def creates_knowledge_graph_edges(self, node_metadata_func: Callable, ontology_a
         """
 
         edge_builder = KGConstructionApproach(self.edge_dict, self.res_dir)  # initialize construction approaches
-
-        for edge_type in self.edge_dict.keys():
+        for edge_type in [x for x in self.edge_dict.keys() if x != 'entity_namespaces']:
             n1_type, n2_type = self.edge_dict[edge_type]['data_type'].split('-')
             uri, edge_list = self.edge_dict[edge_type]['uri'], copy.deepcopy(self.edge_dict[edge_type]['edge_list'])
             edge_results: List = []
-            # identify relations
             rel = self.edge_dict[edge_type]['edge_relation']
             self.verifies_object_property(URIRef(obo + rel))  # verify object property in knowledge graph
             invrel = self.checks_for_inverse_relations(rel, edge_list) if self.inverse_relations else None
-
             print('\nCreating {} ({}-{}) Edges ***'.format(edge_type.upper(), n1_type, n2_type))
             for edge in tqdm(edge_list):
                 edge_info = {'n1': n1_type, 'n2': n2_type, 'rel': rel, 'inv_rel': invrel, 'uri': uri, 'edges': edge}
-                if self.check_ontology_class_nodes(edge_info):  # verify edges - make sure ont class nodes are in KG
+                meta = node_metadata_func(ent=[''.join(x) for x in list(zip(uri, edge))], e_type=[n1_type, n2_type])
+                metadata_logic = [True if (self.node_data is None and meta is None)
+                                  or [n1_type, n2_type] == ['class', 'class']
+                                  or (self.node_data is not None and meta is not None) else False][0]
+                if self.check_ontology_class_nodes(edge_info) and metadata_logic:  # make sure ont class nodes are in KG
+                    if n1_type != 'class': self.graph = updates_graph_namespace(n1_type, self.graph, uri[0] + edge[0])
+                    if n2_type != 'class': self.graph = updates_graph_namespace(n2_type, self.graph, uri[1] + edge[1])
                     if self.construct_approach == 'subclass':
                         self.edge_dict, new_edges = edge_builder.subclass_constructor(edge_info, edge_type)
-                        self.graph = adds_edges_to_graph(self.graph, new_edges)  # add new edges to graph
-                        edge_results += new_edges  # update list of added edges
+                        edge_results += new_edges
+                        self.graph = adds_edges_to_graph(self.graph, new_edges + meta if meta else new_edges)
                     else:
                         self.edge_dict, new_edges = edge_builder.instance_constructor(edge_info, edge_type)
-                        self.graph = adds_edges_to_graph(self.graph, new_edges)  # add new edges to graph
-                        edge_results += new_edges  # update list of added edges
-                else:
-                    self.edge_dict[edge_type]['edge_list'].pop(self.edge_dict[edge_type]['edge_list'].index(edge))
-            n1, n2, edges = edge_type.split('-')[0], edge_type.split('-')[1], self.edge_dict[edge_type]['edge_list']
-            print('Total OWL Edges: {}'.format(len(set(edge_results))))
-            print('Unique Non-OWL Edges: {}'.format(len(edges) * 2 if invrel else len(edges)))
-            print('Unique {}: {}'.format(n1, len(set([x[0] for x in self.edge_dict[edge_type]['edge_list']]))))
-            print('Unique {}: {}'.format(n2, len(set([x[1] for x in self.edge_dict[edge_type]['edge_list']]))))
-
-        # output error logs
-        if len(edge_builder.subclass_error.keys()) > 0:
+                        edge_results += new_edges
+                        self.graph = adds_edges_to_graph(self.graph, new_edges + meta if meta else new_edges)
+                else: self.edge_dict[edge_type]['edge_list'].pop(self.edge_dict[edge_type]['edge_list'].index(edge))
+            self.gets_edge_statistics(edge_type, invrel, edge_results)
+        if len(edge_builder.subclass_error.keys()) > 0:  # output error logs
             log_file = glob.glob(self.res_dir + '/construction*')[0] + '/subclass_map_missing_node_log.json'
-            print('\nSome edge lists nodes were missing from subclass_dict, see log: {}'.format(log_file))
+            print('\nSome edge lists nodes were missing from the subclass_dict, see log: {}'.format(log_file))
             outputs_dictionary_data(edge_builder.subclass_error, log_file)
-        # add ontology metadata and annotations, serialize graph, and apply OWL API formatting to output
-        # if self.kg_metadata == 'yes': node_metadata_func(self.graph, self.edge_dict)
-        node_metadata_func(self.graph, self.edge_dict)
         self.graph = ontology_annotator_func(self.full_kg.split('/')[-1], self.graph)
         print('\nSerializing Knowledge Graph')
-        self.graph.serialize(destination=self.write_location + self.full_kg, format='xml')
-        ontology_file_formatter(self.write_location, self.full_kg, self.owl_tools)
+        full_kg_owl = self.full_kg.replace('noOWL', 'OWL') if self.decode_owl == 'yes' else self.full_kg
+        self.graph.serialize(destination=self.write_location + full_kg_owl, format='xml')
+        ontology_file_formatter(self.write_location, full_kg_owl, self.owl_tools)
 
         return None
 
@@ -312,8 +327,8 @@ def gets_build_type(self) -> str:
     def construct_knowledge_graph(self) -> None:
         """Builds a partial knowledge graph. A partial knowledge graph build is recommended when one intends to build a
         knowledge graph and intends to run a reasoner over it. The partial build includes the following steps: (1)
-        Process relation/inverse relation data; (2) Process node metadata; (3) Merge ontologies; and (4) Add master
-        edge list to merged ontologies.
+        Process relation/inverse relations; (2) Merge ontologies; (3) Process node metadata; and (4) Add master edge
+        list to merged ontologies.
 
         Returns:
             None.
@@ -328,34 +343,39 @@ def construct_knowledge_graph(self) -> None:
         print('*** Loading Relations Data ***')
         self.reverse_relation_processor()
 
-        # STEP 2: PROCESS NODE METADATA
-        print('*** Loading Node Metadata Data ***')
-        metadata = Metadata(self.kg_version, self.write_location, self.full_kg, self.node_data, self.node_dict)
-        if self.node_data: metadata.node_metadata_processor()
-
-        # STEP 3: MERGE ONTOLOGIES
+        # STEP 2: MERGE ONTOLOGIES
         if self.merged_ont_kg in glob.glob(self.write_location + '/*.owl'):
             print('*** Loading Merged Ontologies ***')
-            self.graph.parse(self.merged_ont_kg, format='xml')
+            self.graph = Graph().parse(self.merged_ont_kg, format='xml')
         else:
             print('*** Merging Ontology Data ***')
             merged_ontology_location = self.merged_ont_kg.split('/')[-1]
             merges_ontologies(self.ontologies, merged_ontology_location, self.owl_tools)
             self.graph.parse(self.merged_ont_kg, format='xml')  # load the merged ontology
-        # print merged ontology statistics
+        # print statistics on the merged ontologies
         gets_ontology_statistics(self.merged_ont_kg, self.owl_tools)
         print('The Merged Core Ontology Knowledge Graph Contains: {} Triples'.format(len(self.graph)))
 
-        # STEP 4: ADD MASTER EDGE DATA TO KNOWLEDGE GRAPH
-        # build knowledge graph
-        print('*** Building Knowledge Graph Edges ***')
+        # STEP 3: PROCESS NODE METADATA
+        print('*** Loading Node Metadata Data ***')
+        metadata = Metadata(self.kg_version, self.write_location, self.full_kg, self.node_data, self.node_dict)
+        if self.node_data:
+            metadata.node_metadata_processor()
+            metadata.extracts_class_metadata(self.graph)
+            self.node_dict = metadata.node_dict
+
+        # STEP 4: ADD EDGE DATA TO KNOWLEDGE GRAPH DATA
+        print('\n*** Building Knowledge Graph Edges ***')
         self.ont_classes = gets_ontology_classes(self.graph)
         self.obj_properties = gets_object_properties(self.graph)
-        self.creates_knowledge_graph_edges(metadata.adds_node_metadata, metadata.adds_ontology_annotations)
-        # print statistics
-        gets_ontology_statistics(self.write_location + self.full_kg, self.owl_tools)
+        self.creates_knowledge_graph_edges(metadata.creates_node_metadata, metadata.adds_ontology_annotations)
+        full_kg_owl = self.full_kg.replace('noOWL', 'OWL') if self.decode_owl == 'yes' else self.full_kg
+        gets_ontology_statistics(self.write_location + full_kg_owl, self.owl_tools)
         print('The Knowledge Graph Contains: {} Triples'.format(len(self.graph)))
 
+        # clean environment
+        del metadata, self.edge_dict, self.graph, self.inverse_relations_dict, self.node_dict, self.relations_dict
+
         return None
 
 
@@ -372,8 +392,8 @@ def construct_knowledge_graph(self) -> None:
         built knowledge graph and completes the build process.
 
         The post-closure build utilizes the following steps: (1) Process relation and inverse relation data; (2)
-        Process node metadata; (3) Load closed knowledge graph; (4) Extract and write node metadata; (5) Decode
-        OWL-encoded classes; and (6) Output knowledge graph files and create edge lists.
+        Load closed knowledge graph; (3) Process node metadata; (4) Decode OWL-encoded classes; (5) Output knowledge
+        graph files and create edge lists; and (6) Extract and write node metadata.
 
         Returns:
             None.
@@ -389,12 +409,7 @@ def construct_knowledge_graph(self) -> None:
         print('*** Loading Relations Data ***')
         self.reverse_relation_processor()
 
-        # STEP 2: PROCESS NODE METADATA
-        print('*** Loading Node Metadata Data ***')
-        metadata = Metadata(self.kg_version, self.write_location, self.full_kg, self.node_data, self.node_dict)
-        if self.node_data: metadata.node_metadata_processor()
-
-        # STEP 3: LOAD CLOSED KNOWLEDGE GRAPH
+        # STEP 2: LOAD CLOSED KNOWLEDGE GRAPH
         closed_kg_location = glob.glob(self.write_location + '/*.owl')
         if len(closed_kg_location) == 0:
             raise OSError('The closed KG file does not exist!')
@@ -403,30 +418,44 @@ def construct_knowledge_graph(self) -> None:
         else:
             print('*** Loading Closed Knowledge Graph ***')
             os.rename(closed_kg_location[0], self.write_location + self.full_kg)  # rename closed kg file
-            self.graph = Graph()
-            self.graph.parse(self.write_location + self.full_kg, format='xml')
+            self.graph = Graph().parse(self.write_location + self.full_kg, format='xml')
             # print statistics
             gets_ontology_statistics(self.write_location + self.full_kg, self.owl_tools)
             print('The Knowledge Graph Contains: {} Triples'.format(len(self.graph)))
 
-        # STEP 4: EXTRACT AND WRITE NODE METADATA
-        print('\n*** Processing Knowledge Graph Metadata ***')
-        if self.node_data: metadata.output_knowledge_graph_metadata(self.graph)
-        del metadata, self.edge_dict, self.node_dict, self.relations_dict, self.inverse_relations_dict  # clean env
+        # STEP 3: PROCESS NODE METADATA
+        print('*** Loading Node Metadata Data ***')
+        metadata = Metadata(self.kg_version, self.write_location, self.full_kg, self.node_data, self.node_dict)
+        if self.node_data:
+            metadata.node_metadata_processor()
+            metadata.extracts_class_metadata(self.graph)
+            self.node_dict = metadata.node_dict
 
-        # STEP 5: DECODE OWL SEMANTICS
+        # STEP 4: DECODE OWL SEMANTICS
+        results: Tuple = tuple([self.graph, None])
         if self.decode_owl:
-            print('*** Running OWL-NETS - Decoding OWL-Encoded Classes and Removing OWL Semantics ***')
-            owl_nets = OwlNets(self.construct_approach, self.graph, self.write_location, self.full_kg, self.owl_tools)
-            self.graph = owl_nets.run_owl_nets()
+            print('\n*** Running OWL-NETS - Decoding OWL-Encoded Classes and Removing OWL Semantics ***')
+            owl_nets = OwlNets(self.graph, self.write_location, self.full_kg, self.construct_approach, self.owl_tools)
+            results = owl_nets.run_owl_nets()
         else:
             converts_rdflib_to_networkx(self.write_location, self.full_kg[:-4], self.graph)
 
-        # STEP 6: WRITE OUT KNOWLEDGE GRAPH DATA AND CREATE EDGE LISTS
-        print('*** Writing Knowledge Graph Edge Lists ***')
-        maps_node_ids_to_integers(self.graph, self.write_location,
-                                  self.full_kg[:-4] + '_Triples_Integers.txt',
-                                  self.full_kg[:-4] + '_Triples_Integer_Identifier_Map.json')
+        # STEP 5: WRITE OUT KNOWLEDGE GRAPH DATA AND CREATE EDGE LISTS
+        print('\n*** Writing Knowledge Graph Edge Lists ***')
+        f_prefix = ('', '_' + self.construct_approach.upper() + '_purified') if len(results) == 2 else ('', '')
+        for graph in results:
+            if isinstance(graph, Graph):
+                self.graph = graph
+                triple_list_file = self.full_kg[:-4] + f_prefix[results.index(graph)] + '_Triples_Integers.txt'
+                triple_map = self.full_kg[:-4] + f_prefix[results.index(graph)] + '_Triples_Integer_Identifier_Map.json'
+                node_int_map = maps_node_ids_to_integers(self.graph, self.write_location, triple_list_file, triple_map)
+
+                # STEP 6: EXTRACT AND WRITE NODE METADATA
+                print('\n*** Processing Knowledge Graph Metadata ***')
+                if self.node_data: metadata.output_knowledge_graph_metadata(node_int_map)
+
+        # clean environment
+        del metadata, self.edge_dict, self.graph, self.inverse_relations_dict, self.node_dict, self.relations_dict
 
         return None
 
@@ -441,9 +470,9 @@ def gets_build_type(self) -> str:
     def construct_knowledge_graph(self) -> None:
         """Builds a full knowledge graph. Please note that the process to build this version of the knowledge graph
         does not include running a reasoner. The full build includes the following steps: (1) Process relation/inverse
-        relations; (2) Process node metadata; (3) Merge ontologies; and (4) Add master edge list to merged ontologies;
-        (5) Extract and write node metadata; (6) Decode OWL-encoded classes; and (7) Output knowledge graphs and
-        create edge lists.
+        relations; (2) Merge ontologies; (3) Process node metadata; (4) Add master edge list to merged ontologies; (5)
+        Decode OWL-encoded classes; (6) Output knowledge graphs and create edge lists and (7) Extract and write node
+        metadata.
 
         Returns:
             None.
@@ -458,15 +487,10 @@ def construct_knowledge_graph(self) -> None:
         print('*** Loading Relations Data ***')
         self.reverse_relation_processor()
 
-        # STEP 2: PROCESS NODE METADATA
-        print('*** Loading Node Metadata Data ***')
-        metadata = Metadata(self.kg_version, self.write_location, self.full_kg, self.node_data, self.node_dict)
-        if self.node_data: metadata.node_metadata_processor()
-
-        # STEP 3: MERGE ONTOLOGIES
+        # STEP 2: MERGE ONTOLOGIES
         if self.merged_ont_kg in glob.glob(self.write_location + '/*.owl'):
             print('*** Loading Merged Ontologies ***')
-            self.graph.parse(self.merged_ont_kg, format='xml')
+            self.graph = Graph().parse(self.merged_ont_kg, format='xml')
         else:
             print('*** Merging Ontology Data ***')
             merged_ontology_location = self.merged_ont_kg.split('/')[-1]
@@ -476,32 +500,46 @@ def construct_knowledge_graph(self) -> None:
         gets_ontology_statistics(self.merged_ont_kg, self.owl_tools)
         print('The Merged Core Ontology Knowledge Graph Contains: {} Triples'.format(len(self.graph)))
 
+        # STEP 3: PROCESS NODE METADATA
+        print('*** Loading Node Metadata Data ***')
+        metadata = Metadata(self.kg_version, self.write_location, self.full_kg, self.node_data, self.node_dict)
+        if self.node_data:
+            metadata.node_metadata_processor()
+            metadata.extracts_class_metadata(self.graph)
+            self.node_dict = metadata.node_dict
+
         # STEP 4: ADD EDGE DATA TO KNOWLEDGE GRAPH DATA
         print('\n*** Building Knowledge Graph Edges ***')
         self.ont_classes = gets_ontology_classes(self.graph)
         self.obj_properties = gets_object_properties(self.graph)
-        self.creates_knowledge_graph_edges(metadata.adds_node_metadata, metadata.adds_ontology_annotations)
-        gets_ontology_statistics(self.write_location + self.full_kg, self.owl_tools)
+        self.creates_knowledge_graph_edges(metadata.creates_node_metadata, metadata.adds_ontology_annotations)
+        full_kg_owl = self.full_kg.replace('noOWL', 'OWL') if self.decode_owl == 'yes' else self.full_kg
+        gets_ontology_statistics(self.write_location + full_kg_owl, self.owl_tools)
         print('The Knowledge Graph Contains: {} Triples'.format(len(self.graph)))
 
-        # STEP 5: EXTRACT AND WRITE NODE METADATA
-        print('\n*** Processing Knowledge Graph Metadata ***')
-        # if self.node_data or self.kg_metadata == 'yes': metadata.output_knowledge_graph_metadata(self.graph)
-        if self.node_data: metadata.output_knowledge_graph_metadata(self.graph)
-        del metadata, self.edge_dict, self.node_dict, self.relations_dict, self.inverse_relations_dict  # clean env
-
-        # STEP 6: DECODE OWL SEMANTICS
+        # STEP 5: DECODE OWL SEMANTICS
+        results: Tuple = tuple([self.graph, None])
         if self.decode_owl:
             print('\n*** Running OWL-NETS - Decoding OWL-Encoded Classes and Removing OWL Semantics ***')
-            owl_nets = OwlNets(self.construct_approach, self.graph, self.write_location, self.full_kg, self.owl_tools)
-            self.graph = owl_nets.run_owl_nets()
-        else:
-            converts_rdflib_to_networkx(self.write_location, self.full_kg[:-4], self.graph)
+            owl_nets = OwlNets(self.graph, self.write_location, self.full_kg, self.construct_approach, self.owl_tools)
+            results = owl_nets.run_owl_nets()
+        else: converts_rdflib_to_networkx(self.write_location, self.full_kg[:-4], self.graph)
 
-        # STEP 7: WRITE OUT KNOWLEDGE GRAPH DATA AND CREATE EDGE LISTS
+        # STEP 6: WRITE OUT KNOWLEDGE GRAPH DATA AND CREATE EDGE LISTS
         print('\n*** Writing Knowledge Graph Edge Lists ***')
-        maps_node_ids_to_integers(self.graph, self.write_location,
-                                  self.full_kg[:-4] + '_Triples_Integers.txt',
-                                  self.full_kg[:-4] + '_Triples_Integer_Identifier_Map.json')
+        f_prefix = ('', '_' + self.construct_approach.upper() + '_purified') if len(results) == 2 else ('', '')
+        for graph in results:
+            if isinstance(graph, Graph):
+                self.graph = graph
+                triple_list_file = self.full_kg[:-4] + f_prefix[results.index(graph)] + '_Triples_Integers.txt'
+                triple_map = self.full_kg[:-4] + f_prefix[results.index(graph)] + '_Triples_Integer_Identifier_Map.json'
+                node_int_map = maps_node_ids_to_integers(self.graph, self.write_location, triple_list_file, triple_map)
+
+                # STEP 7: EXTRACT AND WRITE NODE METADATA
+                print('\n*** Processing Knowledge Graph Metadata ***')
+                if self.node_data: metadata.output_knowledge_graph_metadata(node_int_map)
+
+        # clean environment
+        del metadata, self.edge_dict, self.graph, self.inverse_relations_dict, self.node_dict, self.relations_dict
 
         return None
diff --git a/pkt_kg/metadata.py b/pkt_kg/metadata.py
index e800d8712..623f0f5f7 100644
--- a/pkt_kg/metadata.py
+++ b/pkt_kg/metadata.py
@@ -7,6 +7,7 @@
 import os
 import os.path
 import pandas  # type: ignore
+import pickle
 import re
 import subprocess
 
@@ -16,6 +17,8 @@
 from tqdm import tqdm  # type: ignore
 from typing import Dict, List, Optional, Set, Union
 
+from pkt_kg.utils import *
+
 # set environmental variables
 oboinowl = Namespace('http://www.geneontology.org/formats/oboInOwl#')
 obo = Namespace('http://purl.obolibrary.org/obo/')
@@ -32,36 +35,32 @@ class Metadata(object):
         node_dict: A nested dictionary storing metadata for the nodes in the edge_dict. Where the outer key is a node
             identifier and each inner key is an identifier type keyed by the identifier type with the value being the
             corresponding metadata for that identifier type. For example:
-                {'6469': {'Label': 'SHH',
-                          'Description': 'Sonic Hedgehog Signaling Molecule is a protein-coding gene that is located on
-                                          chromosome 7 (map_location: 7q36.3).',
-                          'Synonym': 'HHG1|HLP3|HPE3|MCOPCB5|SMMCI|ShhNC|TPT|TPTPS|sonic hedgehog protein'}}
+                { 'nodes': {
+                    'http://www.ncbi.nlm.nih.gov/gene/1': {
+                        'Label': 'A1BG',
+                        'Description': "A1BG is protein-coding' and is located on chromosome 19 (19q13.43).",
+                        'Synonym': 'HYST2477alpha-1B-glycoprotein|HEL-S-163pA|ABG|A1B|GAB'} ... },
+                'relations': {
+                    'http://purl.obolibrary.org/obo/RO_0002533': {
+                        'Label': 'sequence atomic unit',
+                        'Description': 'Any individual unit of a collection of like units arranged in a linear order',
+                        'Synonym': 'None'} ... }
+                }
     """
 
     def __init__(self, kg_version: str, write_location: str, kg_location: str, node_data: Optional[List],
                  node_dict: Optional[Dict]) -> None:
 
-        self.kg_version = kg_version
-        self.write_location = write_location
-        self.full_kg = kg_location
+        self.kg_version: str = kg_version
+        self.write_location: str = write_location
+        self.full_kg: str = kg_location
         self.node_data = node_data
         self.node_dict = node_dict
 
     def node_metadata_processor(self) -> None:
-        """Processes a directory of node data sets by reading in each data set a dictionary, which is then added to the
-        class attribute node_dict. This dictionary stores the "ID" column of each data frame as the keys and all other
-        columns as values. For example:
-            {'variant-gene': {397508135: {'Label': 'NM_000492.3(CFTR):c.*80T>G',
-                            'Description': 'This variant is a germline single nucleotide variant that results when a T
-                                allele is changed to G on chromosome 7 (NC_000007.14, start:117667188/stop:117667188
-                                positions, cytogenetic location:7q31.2) and has clinical significance not provided.
-                                This entry is for the GRCh38 and was last reviewed on - with review status "no
-                                assertion provided".'}}}
-
-        Assumptions:
-            1 - Each edge contains a dictionary.
-            2 - Each edge's dictionary is keyed by "ID" and contains at least 1 of the following: "Label",
-                "Description", and "Symbol".
+        """Loads a directory of node and relations data. The dictionary is nested with the outer keys corresponding
+        to the metadata type (i.e. "nodes" or "relations") and the values containing dictionaries keyed by URI and
+        values containing a dictionary of metadata.
 
         Returns:
             None.
@@ -69,145 +68,19 @@ class attribute node_dict. This dictionary stores the "ID" column of each data f
 
         if self.node_data:
             print('Loading and Processing Node Metadata')
-            # create list where first item is edge type and the second item is the df
-            dfs = [[re.sub('.*/', '', re.sub('((_[^/]*)_.*$)', '', x)),
-                    pandas.read_csv(x, header=0, delimiter='\t')] for x in self.node_data]
-            # convert each data frame to dictionary, using the 'ID' column as the index
-            for i in range(0, len(dfs)):
-                df_processed = dfs[i][1].astype(str)
-                df_processed.drop_duplicates(keep='first', inplace=True)
-                df_processed.set_index('ID', inplace=True)
-                df_dict = df_processed.to_dict('index')
-                # add data frame to master node metadata dictionary
-                self.node_dict[dfs[i][0]] = df_dict  # type: ignore
-
-        return None
-
-    def creates_node_metadata(self, node: str, edge_type: str, url: str, graph: Graph) -> Graph:
-        """Given a node in the knowledge graph, if the node has metadata information, new edges are created to add
-        the metadata to the knowledge graph. Metadata that is added includes: labels, descriptions, and synonyms.
-
-        Args:
-            node: A node identifier (e.g. 'HP_0003269', 'rs765907815').
-            edge_type: A string which specifies the edge type (e.g. chemical-gene).
-            url: The node's url needed to form a complete uri (e.g. http://purl.obolibrary.org/obo/), which is
-                specified in the resource_info.txt document.
-            graph: An rdflib graph containing knowledge graph data.
-
-        Returns:
-            graph: An rdflib graph with updated metadata edges.
-        """
-
-        if self.node_dict:
-            metadata = self.node_dict[edge_type][node]
-            if 'Label' in metadata.keys() and metadata['Label'] != 'None':
-                graph.add((URIRef(url + str(node)), RDFS.label, Literal(metadata['Label'])))
-            if 'Description' in metadata.keys() and metadata['Description'] != 'None':
-                graph.add((URIRef(url + str(node)), URIRef(obo + 'IAO_0000115'), Literal(metadata['Description'])))
-            if 'Synonym' in metadata.keys() and metadata['Synonym'] != 'None':
-                for syn in metadata['Synonym'].split('|'):
-                    graph.add((URIRef(url + str(node)), URIRef(oboinowl + 'hasExactSynonym'), Literal(syn)))
-            return graph
-        else:
-            return graph
-
-    def adds_node_metadata(self, graph: Graph, edge_dict: Optional[Dict]) -> Graph:
-        """Iterates over nodes in each edge in the edge_dict, by edge_type. If the node has metadata available in the
-        the node_data dictionary, then it is added to the knowledge graph.
-
-        Args:
-            graph: An RDFLib graph object.
-            edge_dict: A nested dictionary storing the master edge list for the knowledge graph. Where the outer key is
-            an edge-type (e.g. gene-cell) and each inner key contains a dictionary storing details from the
-            resource_info.txt input document. For example:
-            {'chemical-complex': {'source_labels': ';;', 'data_type': 'class-instance', 'edge_relation': 'RO_0002436',
-                                  'uri': ['http://ex', 'https://ex'], 'row_splitter': 'n', 'column_splitter': 't',
-                                  'column_idx': '0;1', 'identifier_maps': 'None', 'evidence_criteria': 'None',
-                                  'filter_criteria': 'None', 'edge_list': [['CHEBI_24505', 'R-HSA-1006173'], ...]}}
-
-        Returns:
-            graph: An rdflib graph object that includes node metadata.
-        """
-
-        print('\nAdding Non-Ontology Data Metadata to Knowledge Graph')
-
-        if edge_dict:
-            for edge_type in tqdm(edge_dict.keys()):
-                edge_data_type = edge_dict[edge_type]['data_type']
-                if edge_data_type.split('-')[0] == 'class' and edge_data_type.split('-')[1] == 'class':
-                    pass
-                else:
-                    print('Processing ontology terms of {} edge type'.format(edge_data_type))
-                    node_idx = [x for x in range(2) if edge_data_type.split('-')[x] == 'entity']
-                    for edge in tqdm(edge_dict[edge_type]['edge_list']):
-                        if self.node_dict and edge_type in self.node_dict.keys():
-                            if len(node_idx) == 2:
-                                subj, obj = edge[0], edge[1]
-                                if subj in self.node_dict[edge_type].keys() and obj in self.node_dict[edge_type].keys():
-                                    subj_uri = str(edge_dict[edge_type]['uri'][0])
-                                    obj_uri = str(edge_dict[edge_type]['uri'][1])
-                                    graph = self.creates_node_metadata(subj, edge_type, subj_uri, graph)
-                                    graph = self.creates_node_metadata(obj, edge_type, obj_uri, graph)
-                            else:
-                                inst_node = edge[node_idx[0]]
-                                if inst_node in self.node_dict[edge_type].keys():
-                                    node_uri = str(edge_dict[edge_type]['uri'][0])
-                                    graph = self.creates_node_metadata(inst_node, edge_type, node_uri, graph)
-
-            return graph
-
-    def removes_annotation_assertions(self, owltools_location: str = os.path.abspath('./pkt_kg/libs/owltools')) -> None:
-        """Utilizes OWLTools to remove annotation assertions. The '--remove-annotation-assertions' method in OWLTools
-        removes annotation assertions to make a pure logic subset', which reduces the overall size of the knowledge
-        graph, while still being compatible with a reasoner.
-
-        Note. This method is usually only applied to partial builds.
-
-        Args:
-            owltools_location: A string pointing to the location of the owl tools library.
 
-        Returns:
-            None.
-        """
-
-        # remove annotation assertions
-        try:
-            subprocess.check_call([owltools_location, self.write_location + self.full_kg,
-                                   '--remove-annotation-assertions',
-                                   '-o', self.write_location + self.full_kg[:-4] + '_NoAnnotationAssertions.owl'])
-        except subprocess.CalledProcessError as error:
-            print(error.output)
+            with open(self.node_data[0], 'rb') as out:
+                self.node_dict = pickle.load(out, encoding="utf8")
 
         return None
 
-    @staticmethod
-    def adds_annotation_assertions(graph: Graph, filename: str) -> Graph:
-        """Adds edges removed from the knowledge graph when annotation assertions were removed. First, the knowledge
-        graph that was created prior to removing annotation assertions is read in. Then, the function iterates over the
-        closed knowledge graph files and adds any edges that are present in the list, but missing from the closed
-        knowledge graph.
-
-        Note. This method is usually only applied to post-closure builds.
-
-        Args:
-            graph: An rdflib graph object.
-            filename: A string containing the filepath to the full knowledge graph that includes annotation assertions.
-
-        Returns:
-            graph: An rdflib graph object that includes annotation assertions.
-        """
-
-        assertions = Graph().parse(filename)
-        for edge in tqdm(assertions):
-            graph.add(edge)
-
-        return graph
-
     def extracts_class_metadata(self, graph: Graph) -> None:
-        """Functions queries the knowledge graph to obtain labels, definitions/descriptions, and synonyms for
-        classes. This information is then added to the existing self.node_dict dictionary under the key of 'classes'.
-        Each metadata type is saved as a dictionary key with the actual string stored as the value. The metadata types
-        are packaged as a dictionary which is stored as the value to the node identifier as the key.
+        """Functions queries the knowledge graph to obtain labels, definitions/descriptions, and synonyms for all
+        owl:Class, owl:NamedIndividual, and owl:ObjectProperty objects. This information is then added to the existing
+        self.node_dict  dictionary under the key of "nodes" (for owl:Class and owl:NamedIndividual) or "relations" (for
+        owl:ObjectProperty). Each metadata type is saved as a dictionary key with the actual string stored as the
+        value. The metadata types are packaged as a dictionary which is stored as the value to the node identifier as
+        the key.
 
         Args:
             graph: An rdflib graph object.
@@ -219,86 +92,72 @@ def extracts_class_metadata(self, graph: Graph) -> None:
         print('\nExtracting Class Metadata')
 
         if self.node_dict:
-            self.node_dict['classes'] = {}
-            results = graph.query(
-                """SELECT DISTINCT ?class ?class_label ?class_definition ?class_syn
-                       WHERE {
-                          ?class rdf:type owl:Class .
-                          optional{?class rdfs:label ?class_label .}
-                          optional{?class obo:IAO_0000115 ?class_definition .}
-                          optional{?class oboinowl:hasExactSynonym ?class_syn .}}
-                       """, initNs={'rdf': RDF, 'rdfs': RDFS, 'owl': OWL, 'obo': obo, 'oboinowl': oboinowl})
-
-            # convert results to dictionary
-            for result in tqdm(results):
-                node = str(result[0]).split('/')[-1]
-                if node in self.node_dict['classes'].keys():
-                    self.node_dict['classes'][node]['Label'].append(str(result[1]) if result[1] else 'None')
-                    self.node_dict['classes'][node]['Description'].append(str(result[2]) if result[2] else 'None')
-                    self.node_dict['classes'][node]['Synonym'].append(str(result[3]) if result[3] else 'None')
-                else:
-                    self.node_dict['classes'][node] = {}
-                    self.node_dict['classes'][node]['Label'] = [str(result[1]) if result[1] else 'None']
-                    self.node_dict['classes'][node]['Description'] = [str(result[2]) if result[2] else 'None']
-                    self.node_dict['classes'][node]['Synonym'] = [str(result[3]) if result[3] else 'None']
+            domains = [
+                ('nodes', [i[0] for i in list(graph.triples((None, RDF.type, None)))
+                           if isinstance(i[0], URIRef)
+                           and (OWL.Class in i[2] or OWL.NamedIndividual in i[2])
+                           and ('#' not in str(i[0]) or '#' not in str(i[2]))]),
+                ('relations', [i[0] for i in list(graph.triples((None, RDF.type, None)))
+                               if isinstance(i[0], URIRef) and i[2] != OWL.AnnotationProperty])
+            ]
+            for x in domains:
+                key, temp_dict = 'nodes' if x[0] == 'nodes' else 'relations', dict()
+                for i in tqdm(x[1]):
+                    labels = [x for x in list(graph.triples((i, RDFS.label, None)))]
+                    descriptions = [x for x in list(graph.triples((i, obo.IAO_0000115, None)))]
+                    synonyms = [x for x in list(graph.triples((i, None, None))) if 'synonym' in str(x[1]).lower()]
+                    if len(labels) == 0: pass
+                    else:
+                        temp_dict[str(i)] = {
+                            'Label': str(labels[0][2]) if len(labels) > 0 else None,
+                            'Description': str(descriptions[0][2]) if len(descriptions) > 0 else None,
+                            'Synonym': '|'.join([str(c[2]) for c in synonyms]) if len(synonyms) > 0 else None
+                        }
+                self.node_dict[key] = {**self.node_dict[key], **temp_dict}
+
+            if self.node_data:
+                with open(self.node_data[0], 'wb') as out:
+                    pickle.dump(self.node_dict, out)
 
         return None
 
-    def output_knowledge_graph_metadata(self, graph: Graph) -> None:
-        """Loops over the self.node_dict dictionary and writes out the data to a file locally. The data is stored as
-        a tab-delimited '.txt' file with four columns: (1) node identifier; (2) node label; (3) node description or
-        definition; and (4) node synonym.
-
-        NOTE. Not every node in the knowledge class will have metadata. There are some non-ontology nodes that are
-        added (e.g. Ensembl transcript identifiers) that at the time of adding did not include labels, synonyms,
-        or definitions. While these nodes have valid metadata through their original provider, this data may not have
-        been available for download and thus would not have been added to the node_dict.
+    def creates_node_metadata(self, ent: List, e_type: Optional[List] = None, key_type: str = 'nodes') -> Graph:
+        """Given a node in the knowledge graph, if the node is not an ontology class and if it has metadata information,
+        then new edges are created to add the metadata to the knowledge graph. Metadata that is added includes: labels,
+        descriptions, and synonyms.
 
         Args:
-            graph: A rdflib graph object.
+            ent: A list of two node identifiers (e.g. ['http://example/3075', 'http://example/1080']).
+            e_type: A list of types for each node in nodes (e.g. ['entity', 'entity']).
+            key_type: A string indicating if the key should be 'nodes' or 'relations (default='nodes').
 
         Returns:
-            None.
+            edges: A list of tuples containing RDFLib objects used to add metadata to a knowledge graph.
         """
 
-        # add metadata for nodes that are data type class to self.node_dict
-        self.extracts_class_metadata(graph)
-        node_tracker: Set = set()  # creat set to ensure nodes are not written out multiple times
+        key, edges, x = key_type, [], []
 
+        # find eligible entities to add (i.e. all relations and nodes for entities that are not a class)
         if self.node_dict:
-            print('\nWriting Class Metadata')
-            filename = self.full_kg[:-4] + '_NodeLabels.txt'
-            with open(self.write_location + filename, 'w', encoding='utf-8') as outfile:
-                outfile.write('node_id' + '\t' + 'label' + '\t' + 'description/definition' + '\t' + 'synonym' + '\n')
-                for edge_type in tqdm(self.node_dict.keys()):
-                    for node in self.node_dict[edge_type]:
-                        if node not in node_tracker:
-                            node_tracker |= {node}  # increment node tracker
-                            # labels
-                            label_list = self.node_dict[edge_type][node]['Label']
-                            if isinstance(label_list, list) and len(label_list) > 1: label = '|'.join(set(label_list))
-                            elif isinstance(label_list, list) and len(label_list) == 1: label = label_list[0]
-                            else: label = label_list
-                            # descriptions
-                            desc_list = self.node_dict[edge_type][node]['Description']
-                            if isinstance(desc_list, list) and len(desc_list) > 1: desc = '|'.join(set(desc_list))
-                            elif isinstance(desc_list, list) and len(desc_list) == 1: desc = desc_list[0]
-                            else: desc = desc_list
-                            # synonyms
-                            syn_list = self.node_dict[edge_type][node]['Synonym']
-                            if isinstance(syn_list, list) and len(syn_list) > 1: syn = '|'.join(set(syn_list))
-                            elif isinstance(syn_list, list) and len(syn_list) == 1: syn = syn_list[0]
-                            else: syn = syn_list
-                            try:
-                                outfile.write(node + '\t' + label + '\t' + desc + '\t' + syn + '\n')
-                            except UnicodeEncodeError:
-                                outfile.write(node + '\t' +
-                                              label.encode('utf-8').decode() + '\t' +
-                                              desc.encode('utf-8').decode() + '\t' +
-                                              syn.encode('utf-8').decode() + '\n')
-            outfile.close()
-
-        return None
+            if key == 'relations' and e_type is None: x = [i for i in ent if i in self.node_dict[key].keys()]
+            elif e_type: x = [i for i in ent if e_type[ent.index(i)] != 'class' and i in self.node_dict[key].keys()]
+            else: pass
+            if len(x) > 0:  # add metadata for eligible entities
+                for i in x:
+                    metadata_info = self.node_dict[key][i]
+                    if 'Label' in metadata_info.keys():
+                        if metadata_info['Label'] is not None and 'None' not in metadata_info['Label']:
+                            edges += [(URIRef(i), RDFS.label, Literal(metadata_info['Label']))]
+                    if 'Description' in metadata_info.keys():
+                        if metadata_info['Description'] is not None and 'None' not in metadata_info['Description']:
+                            edges += [(URIRef(i), URIRef(obo + 'IAO_0000115'), Literal(metadata_info['Description']))]
+                    if 'Synonym' in metadata_info.keys():
+                        if metadata_info['Synonym'] is not None and 'None' not in metadata_info['Synonym']:
+                            for syn in metadata_info['Synonym'].split('|'):
+                                edges += [(URIRef(i), URIRef(oboinowl + 'hasSynonym'), Literal(syn))]
+                return edges
+            else: return None
+        else: return None
 
     def adds_ontology_annotations(self, filename: str, graph: Graph) -> Graph:
         """Updates the ontology annotation information for an input knowledge graph or ontology.
@@ -318,7 +177,7 @@ def adds_ontology_annotations(self, filename: str, graph: Graph) -> Graph:
         date_full = datetime.now().strftime('%m/%d/%Y %H:%M:%S')
         # convert filename to permanent url
         parsed_filename = '_'.join(filename.lower().split('/')[-1].split('_')[2:])
-        url = 'https://pheknowlator.com/pheknowlator_' + parsed_filename
+        url = 'https://pheknowlator.com/pheknowlator_' + parsed_filename[:-4]
         pkt_url = 'https://github.com/callahantiff/PheKnowLator'
 
         # query ontology to obtain existing ontology annotations and remove them
@@ -341,3 +200,93 @@ def adds_ontology_annotations(self, filename: str, graph: Graph) -> Graph:
         graph.add((URIRef(url + '.owl'), RDFS.comment, Literal('For more information visit: ' + pkt_url)))
 
         return graph
+
+    def output_knowledge_graph_metadata(self, node_integer_map: Dict) -> None:
+        """Loops over the self.node_dict dictionary and writes out the data to a file locally. The data is stored as
+        a tab-delimited '.txt' file with four columns: (1) node identifier; (2) node label; (3) node description or
+        definition; and (4) node synonym.
+
+        NOTE. Not every node in the knowledge class will have metadata. There are some non-ontology nodes that are
+        added (e.g. Ensembl transcript identifiers) that at the time of adding did not include labels, synonyms,
+        or definitions. While these nodes have valid metadata through their original provider, this data may not have
+        been available for download and thus would not have been added to the node_dict.
+
+        Args:
+            node_integer_map: A dictionary where keys are integers and values are node and relation identifiers.
+
+        Returns:
+            None.
+        """
+
+        if self.node_dict:
+            print('\nWriting Class Metadata')
+            filename = self.full_kg[:-4] + '_NodeLabels.txt'
+            with open(self.write_location + filename, 'w', encoding='utf-8') as out:
+                out.write('entity_type' + '\t' + 'integer_id' + '\t' + 'node_id' + '\t' + 'label' + '\t' +
+                          'description/definition' + '\t' + 'synonym' + '\n')
+                for nid, nint in tqdm(node_integer_map.items()):
+                    if nid not in self.node_dict['nodes'].keys() or nid not in self.node_dict['relations'].keys():
+                        etyp, lab, dsc, syn = 'NA', 'NA', 'NA', 'NA'
+                    else:
+                        if nid in self.node_dict['nodes'].keys():
+                            etyp, meta = 'NODES', self.node_dict['nodes'][nid]
+                        if nid in self.node_dict['relations'].keys():
+                            etyp, meta = 'RELATIONS', self.node_dict['relations'][nid]
+                        lab = meta['Label'] if meta['Label'] is not None else 'None'
+                        dsc = meta['Description'] if meta['Description'] is not None else 'None'
+                        syn = meta['Synonym'] if meta['Synonym'] is not None else 'None'
+                    try:
+                        out.write(etyp + '\t' + str(nint) + '\t' + nid + '\t' + lab + '\t' + dsc + '\t' + syn + '\n')
+                    except UnicodeEncodeError:
+                        out.write(etyp.encode('utf-8').decode() + '\t' + str(nint).encode('utf-8').decode() +
+                                  '\t' + nid.encode('utf-8').decode() + '\t' + lab.encode('utf-8').decode() +
+                                  '\t' + dsc.encode('utf-8').decode() + '\t' + syn.encode('utf-8').decode() + '\n')
+
+        return None
+
+    def removes_annotation_assertions(self, owltools_location: str = os.path.abspath('./pkt_kg/libs/owltools')) -> None:
+        """Utilizes OWLTools to remove annotation assertions. The '--remove-annotation-assertions' method in OWLTools
+        removes annotation assertions to make a pure logic subset', which reduces the overall size of the knowledge
+        graph, while still being compatible with a reasoner.
+
+        Note. This method is usually only applied to partial builds.
+
+        Args:
+            owltools_location: A string pointing to the location of the owl tools library.
+
+        Returns:
+            None.
+        """
+
+        # remove annotation assertions
+        try:
+            subprocess.check_call([owltools_location, self.write_location + self.full_kg,
+                                   '--remove-annotation-assertions',
+                                   '-o', self.write_location + self.full_kg[:-4] + '_NoAnnotationAssertions.owl'])
+        except subprocess.CalledProcessError as error:
+            print(error.output)
+
+        return None
+
+    @staticmethod
+    def adds_annotation_assertions(graph: Graph, filename: str) -> Graph:
+        """Adds edges removed from the knowledge graph when annotation assertions were removed. First, the knowledge
+        graph that was created prior to removing annotation assertions is read in. Then, the function iterates over the
+        closed knowledge graph files and adds any edges that are present in the list, but missing from the closed
+        knowledge graph.
+
+        Note. This method is usually only applied to post-closure builds.
+
+        Args:
+            graph: An rdflib graph object.
+            filename: A string containing the filepath to the full knowledge graph that includes annotation assertions.
+
+        Returns:
+            graph: An rdflib graph object that includes annotation assertions.
+        """
+
+        assertions = Graph().parse(filename)
+        for edge in tqdm(assertions):
+            graph.add(edge)
+
+        return graph
diff --git a/pkt_kg/owlnets.py b/pkt_kg/owlnets.py
index 44dbb443b..802094d93 100644
--- a/pkt_kg/owlnets.py
+++ b/pkt_kg/owlnets.py
@@ -9,29 +9,39 @@
 import pickle
 
 from collections import Counter
-from rdflib import Graph, BNode, Literal, URIRef  # type: ignore
+from rdflib import BNode, Graph, Literal, Namespace, URIRef  # type: ignore
 from rdflib.namespace import RDF, RDFS, OWL  # type: ignore
 from tqdm import tqdm  # type: ignore
-from typing import Any, Dict, IO, List, Optional, Set, Tuple
+from typing import Any, Dict, IO, List, Optional, Set, Tuple, Union
 
 from pkt_kg.utils import *
 
+# add global variables
+obo = Namespace('http://purl.obolibrary.org/obo/')
+pkt = Namespace('https://github.com/callahantiff/PheKnowLator/pkt/')
+
 
 class OwlNets(object):
-    """Class removes OWL semantics from an ontology or knowledge graph using the OWL-NETS method.
+    """Class removes OWL semantics from an ontology or knowledge graph using the OWL-NETS method. OWL-encoded or
+    semantic edges are needed in a graph in order to enable a rich semantic representation. Many of the nodes in
+    semantic edges are not clinically or biologically meaningful. This class is designed to decode all owl-encoded
+    classes and return a knowledge graph that is semantically rich and clinically and biologically meaningful.
+
+    KG CONSTRUCTION PURIFICATION: The method includes extra functionality to purify knowledge graphs according to an
+    input construction approach type (i.e. 'instance- and subclass-based). The default approach is to leave the input
+    graph alone and provide no purification steps. Alternatively, one can select "instance" or "subclass"
+    purification. For more information see the purifies_graph_build() method.
 
-    OWL-encoded or semantic edges are needed in a graph in order to enable a rich semantic representation. Many of the
-    nodes in semantic edges are not clinically or biologically meaningful. This class is designed to decode all
-    owl-encoded classes and return a knowledge graph that is semantically rich and clinically and biologically
-    meaningful.
+    REMOVAL OF NON-OBO CLASSES: The method will remove all triples containing a owl:Class or owl:NamedIndividual that is
+    not from the OBO namespace. If you do not want this functionality, comment line XXX.
 
     Additional Information: https://github.com/callahantiff/PheKnowLator/wiki/OWL-NETS-2.0
 
     Attributes:
-        kg_construct_approach: A string containing the type of construction approach used to build the knowledge graph.
         graph: An RDFLib object.
-        write_location: A file path used for writing knowledge graph data.
-        full_kg: A string containing the filename for the full knowledge graph.
+        write_location: A file path used for writing knowledge graph data (e.g. "resources/".
+        filename: A string containing the filename for the full knowledge graph (e.g. "/hpo_owlnets").
+        kg_construct_approach: A string containing the type of construction approach used to build the knowledge graph.
         owl_tools: A string pointing to the location of the owl tools library.
 
     Raises:
@@ -41,46 +51,70 @@ class OwlNets(object):
         TypeError: If the file containing owl object properties is empty.
     """
 
-    def __init__(self, kg_construct_approach: str, graph: Graph, write_location: str, full_kg: str,
-                 owl_tools: str = './pkt_kg/libs/owltools') -> None:
+    def __init__(self, graph: Union[Graph, str], write_location: str, filename: str,
+                 kg_construct_approach: Optional[str] = None, owl_tools: str = './pkt_kg/libs/owltools') -> None:
 
         self.owl_tools = owl_tools
         self.kg_construct_approach = kg_construct_approach
         self.write_location = write_location
         self.res_dir = os.path.relpath('/'.join(self.write_location.split('/')[:-1]))
-        self.full_kg = full_kg
+        self.filename = filename
+        self.nx_mdg: networkx.MultiDiGraph = networkx.MultiDiGraph()
 
-        # verify input graphs
-        if not isinstance(graph, Graph): raise TypeError('graph must be an RDFLib Graph Object.')
-        elif len(graph) == 0: raise ValueError('graph is empty.')
-        else: self.graph = graph
+        # VERIFY INPUT GRAPH
+        if not isinstance(graph, Graph) and not isinstance(graph, str):
+            raise TypeError('Graph must be an RDFLib Graph Object or a str.')
+        elif isinstance(graph, Graph) and len(graph) == 0:
+            raise ValueError('RDFLib Graph Object is empty.')
+        else:
+            self.graph = graph if isinstance(graph, Graph) else Graph().parse(graph)
+        self.class_list: List = list(gets_ontology_classes(self.graph))
+
+        # OWL-NETS CLEANING DICTIONARY
+        self.owl_nets_dict: Dict = {'owl_nets': {'decoded_classes': {}, 'complementOf': {}, 'cardinality': {},
+                                                 'negation': {}, 'misc': {}},
+                                    'disjointWith': {}, 'filtered_triples': set(),
+                                    '{}_approach_purified'.format(self.kg_construct_approach): set()}
+
+    def converts_rdflib_to_networkx_multidigraph(self) -> None:
+        """Concerts an RDFLib Graph object into a Networkx MultiDiGraph object.
+
+        Returns:
+            None.
+        """
+
+        print('\nConverting RDFLib Graph to Networkx MultiDiGraph. Note, this process can take up to 60 minutes.')
 
-        # convert RDF graph to networkx MultiDiGraph
-        print('\nConverting knowledge graph to MultiDiGraph. Note, this process can take up to 60 minutes.')
-        self.nx_mdg: networkx.MultiDiGraph = networkx.MultiDiGraph()
         for s, p, o in tqdm(self.graph):
             self.nx_mdg.add_edge(s, o, **{'key': p})
 
-        # set a list of owl:Property types to keep when filtering triples from knowledge graph
-        file_name = self.res_dir + '/owl_decoding/*.txt'
-        if '.txt' not in glob.glob(file_name)[0]:
-            raise TypeError('The owl properties file is not type .txt')
-        elif os.stat(glob.glob(file_name)[0]).st_size == 0:
-            file_path = glob.glob(file_name)[0]
-            raise TypeError('The input file: {} is empty'.format(file_path))
-        else:
-            with open(glob.glob(file_name)[0], 'r') as filepath:  # type: IO[Any]
-                self.keep_properties = [x.strip('\n') for x in filepath.read().splitlines() if x]
-                self.keep_properties += [str(RDFS.subClassOf), 'http://purl.obolibrary.org/obo/RO_0000086']
-                self.keep_properties = list(set(self.keep_properties))
+        return None
 
-        # get all classes in knowledge graph
-        self.class_list: List = list(gets_ontology_classes(self.graph))
+    def removes_disjoint_with_axioms(self) -> None:
+        """Removes owl:disjointWith axioms from an RDFLib Graph object.
+
+        Returns:
+            None.
+        """
+
+        print('Removing owl:disjointWith Axioms')
+
+        disjoint_elements = [x[0] for x in self.graph.triples((None, None, OWL.disjointWith))]
+        for x in disjoint_elements:
+            triples = list(self.graph.triples((x, None, None)))
+            self.owl_nets_dict['disjointWith'][str(x)] = {tuple([(str(x[0]), str(x[1]), str(x[2])) for x in triples])}
+
+            # remove axioms from graph
+            for triple in triples:
+                self.graph.remove(triple)
+
+        return None
 
     def updates_class_instance_identifiers(self) -> None:
-        """Iterates over all class instances in a knowledge graph that was constructed using the instance construction
-        approach and converts pkt_BNodes back to the original ontology class identifier. A new edge for each triple,
-        containing an instance of a class is updated with the original ontology identifier, is added to the graph.
+        """Iterates over all class instances in a knowledge graph that was constructed using the instance-based
+        construction approach and converts pkt_BNodes back to the original ontology class identifier. A new edge for
+        each triple, containing an instance of a class is updated with the original ontology identifier, is added to
+        the graph.
 
         Assumptions: (1) all instances of a class identifier contain the pkt namespace
                      (2) all relations used when adding new edges to a graph are part of the OBO namespace
@@ -91,28 +125,28 @@ def updates_class_instance_identifiers(self) -> None:
 
         print('Re-mapping Instances of Classes to Class Identifiers')
 
-        # get all class individuals in pheknowlator namespace
+        # get all class individuals in pheknowlator namespace and save as dict with value as original ontology class
         pkts = [x[0] for x in list(self.graph.triples((None, RDF.type, OWL.NamedIndividual))) if 'pkt' in str(x[0])]
-        remove_edges: Set = set()
+        pkt_ns_dict = {i: [x[2] for x in list(self.graph.triples((i, RDF.type, None)))
+                           if 'obo' in str(x[2])][0] for i in tqdm(pkts)}
 
+        # update triples containing BNodes with original ontology class
+        remove_edges: Set = set()
         for node in tqdm(pkts):
-            # get the node represented by the instance BNode
-            cls = [x[2] for x in list(self.graph.triples((node, RDF.type, None))) if 'obo' in str(x[2])][0]
-            # get triples where the bnode is the subject and object
             inst_triples = list(self.graph.triples((node, None, None))) + list(self.graph.triples((None, None, node)))
             for edge in inst_triples:
-                if 'obo' in str(edge[1]):
-                    if edge[0] == node: self.graph.add((cls, edge[1], edge[2]))
-                    if edge[2] == node: self.graph.add((edge[0], edge[1], cls))
-            # add edges to removal set
+                sub = pkt_ns_dict[edge[0]] if edge[0] in pkt_ns_dict.keys() else edge[0]
+                obj = pkt_ns_dict[edge[2]] if edge[2] in pkt_ns_dict.keys() else edge[2]
+                self.graph.add((sub, edge[1], obj))
             remove_edges |= set(inst_triples)
         self.graph = remove_edges_from_graph(self.graph, list(remove_edges))
 
         return None
 
     def removes_edges_with_owl_semantics(self) -> Graph:
-        """Creates a filtered knowledge graph, such that all triples that contain an owl:ObjectProperty that is not
-        included in the keep_properties list are removed. For example:
+        """Creates a filtered knowledge graph, such that only nodes that are owl:Class/owl:Individual connected via a
+        owl:ObjectProperty and not an owl:AnnotationProperty. For
+        example:
 
             REMOVE - edges needed to support owl semantics (not biologically meaningful):
                 subject: http://purl.obolibrary.org/obo/CLO_0037294
@@ -130,11 +164,29 @@ def removes_edges_with_owl_semantics(self) -> Graph:
 
         print('\nFiltering Triples')
 
-        filtered_graph = Graph()
-        for predicate in tqdm(self.keep_properties):
-            triples_to_keep = list(self.graph.triples((None, URIRef(predicate), None)))
-            new_edges = [x for x in triples_to_keep if isinstance(x[0], URIRef) and isinstance(x[2], URIRef)]
-            filtered_graph = adds_edges_to_graph(filtered_graph, new_edges)  # add kept edges to filtered graph
+        keep_predicates, filtered_triples = set(), set()
+        for x in tqdm(self.graph):
+            if isinstance(x[0], URIRef) and isinstance(x[1], URIRef) and isinstance(x[2], URIRef):
+                if len(self.owl_nets_dict['owl_nets']['decoded_classes']) == 0:
+                    s = [i for i in list(self.graph.triples((x[0], RDF.type, None)))
+                         if (OWL.Class in i[2] or OWL.NamedIndividual in i[2]) and '#' not in str(x[0])]
+                    o = [i for i in list(self.graph.triples((x[2], RDF.type, None)))
+                         if (OWL.Class in i[2] or OWL.NamedIndividual in i[2]) and '#' not in str(x[2])]
+                    p = [i for i in list(self.graph.triples((x[1], RDF.type, None))) if i[2] != OWL.AnnotationProperty]
+                    if len(s) > 0 and len(o) > 0 and len(p) > 0:
+                        if OWL.ObjectProperty in p[0][2]: keep_predicates.add(x)
+                        else: filtered_triples |= {(str(x[0]), str(x[1]), str(x[2]))}
+                    if len(s) > 0 and len(o) > 0 and len(p) == 0:
+                        if RDFS.subClassOf in x[1]: keep_predicates.add(x)
+                        elif RDF.type in x[1]: keep_predicates.add(x)
+                        else: filtered_triples |= {(str(x[0]), str(x[1]), str(x[2]))}
+                else:
+                    if OWL not in x[0] and OWL not in x[2]: keep_predicates.add(x)
+                    else: filtered_triples |= {(str(x[0]), str(x[1]), str(x[2]))}
+            else: filtered_triples |= {(str(x[0]), str(x[1]), str(x[2]))}
+
+        filtered_graph = adds_edges_to_graph(Graph(), list(keep_predicates))  # create a new graph from filtered edges
+        self.owl_nets_dict['filtered_triples'] |= filtered_triples
 
         return filtered_graph
 
@@ -195,17 +247,13 @@ def creates_edge_dictionary(self, node: URIRef) -> Tuple[Dict, Set]:
 
         # get all edges that come out of class node
         out_edges = [x for axioms in list(self.nx_mdg.out_edges(node, keys=True)) for x in axioms]
-
-        # recursively loop over anonymous nodes in out_edges to create a list of relevant edges to rebuild class
-        for axiom in out_edges:
+        for axiom in out_edges:  # recursively loop over out_edge bnodes to find list of relevant edges to rebuild class
             if isinstance(axiom, BNode):
                 for element in self.recurses_axioms([], list(self.nx_mdg.out_edges(axiom, keys=True))):
                     matches += list(self.nx_mdg.out_edges(element, keys=True))
-        # create dictionary of edge lists
-        for match in matches:
+        for match in matches:  # create dictionary of edge lists
             if 'cardinality' in str(match[2]).lower():
                 cardinality |= {'{}: {}'.format(node, match[0])}
-                pass
             else:
                 if match[0] in class_edge_dict:
                     class_edge_dict[match[0]][match[2].split('#')[-1]] = {}
@@ -216,12 +264,49 @@ def creates_edge_dictionary(self, node: URIRef) -> Tuple[Dict, Set]:
 
         return class_edge_dict, cardinality
 
+    def detects_constructed_class_to_ignore(self, node_info: Tuple, node: URIRef) -> bool:
+        """Method takes a tuple of information about a node and searches the information for nodes that contain
+        semantic support information, but also contains other types of information (i.e. cardinality, complementOf,
+        or negation (i.e. lacks_part)) we either don't full process or want to ignore.
+
+        Args:
+            node: An RDFLib URIRef object containing node information.
+            node_info: A tuple where the first item is a nested dictionary. The outer dictionary keys are anonymous
+                nodes and the inner keys are owl:ObjectProperty values from each out edge triple that comes out of that
+                anonymous node. The second item is a  set of strings, where each string is formatted such that the
+                substring that occurs before the ':' is the class node and the substring after the ':' is the
+                anonymous node naming the element where cardinality was used.
+
+        Returns:
+            True: if a class to ignore was detected.
+            False: if a class to ignore was not detected.
+        """
+
+        if len(node_info[1]) != 0:  # want to note and will process ignoring the specified integer value
+            self.owl_nets_dict['owl_nets']['cardinality'][str(node)] = {
+                tuple([(str(x[0]), str(x[1]), str(x[2])) for x in
+                       self.graph.triples((BNode(list(node_info[1])[0].split(': ')[-1]), None, None))])}
+            return False
+        elif any(v for v in node_info[0].values() if 'complementOf' in v.keys()):
+            self.owl_nets_dict['owl_nets']['complementOf'][str(node)] = {
+                tuple([(str(x[0]), str(x[1]), str(x[2])) for x in
+                       self.graph.triples((list(node_info[0].keys())[0], None, None))])}
+            return True
+        elif any(v for v in node_info[0].items() if any(i for i in v[1].items() if 'lacks_part' in str(i))):
+            self.owl_nets_dict['owl_nets']['negation'][str(node)] = {
+                tuple([tuple([(str(x[0]), str(x[1]), str(x[2])) for x in self.graph.triples((k, None, None))])
+                       for k, v in node_info[0].items() if 'onProperty' in v.keys()])}
+            return True
+
+        else:
+            return False
+
     @staticmethod
     def returns_object_property(sub: URIRef, obj: URIRef, prop: URIRef = None) -> URIRef:
         """Checks the subject and object node types in order to determine the correct type of owl:ObjectProperty.
 
         The following ObjectProperties are returned for each of the following subject-object types:
-            - sub + obj are not PATO terms + prop is None --> rdfs:subClassOf
+            - subject + object are not PATO terms + prop is None --> rdfs:subClassOf
             - sub + obj are PATO terms + prop is None --> rdfs:subClassOf
             - sub is not a PATO term, but obj is a PATO term --> owl:RO_000086
             - sub is a PATO term + obj is a PATO term + prop is not None --> prop
@@ -237,7 +322,7 @@ def returns_object_property(sub: URIRef, obj: URIRef, prop: URIRef = None) -> UR
 
         if ('PATO' in sub and 'PATO' in obj) and not prop: return RDFS.subClassOf
         elif ('PATO' not in sub and 'PATO' not in obj) and not prop: return RDFS.subClassOf
-        elif 'PATO' not in sub and 'PATO' in obj: return URIRef('http://purl.obolibrary.org/obo/RO_0000086')
+        elif 'PATO' not in sub and 'PATO' in obj: return URIRef(obo + 'RO_0000086')
         else: return prop
 
     @staticmethod
@@ -257,10 +342,14 @@ def parses_anonymous_axioms(edges: Dict, class_dict: Dict) -> Dict:
                 or 'someValuesFrom').
         """
 
-        if isinstance(edges['first'], URIRef) and isinstance(edges['rest'], BNode): return class_dict[edges['rest']]
-        elif isinstance(edges['first'], URIRef) and isinstance(edges['rest'], URIRef): return class_dict[edges['first']]
-        elif isinstance(edges['first'], BNode) and isinstance(edges['rest'], URIRef): return class_dict[edges['first']]
-        else: return {**class_dict[edges['first']], **class_dict[edges['rest']]}
+        if isinstance(edges['first'], URIRef) and isinstance(edges['rest'], BNode):
+            return class_dict[edges['rest']]
+        elif isinstance(edges['first'], URIRef) and isinstance(edges['rest'], URIRef):
+            return class_dict[edges['first']]
+        elif isinstance(edges['first'], BNode) and isinstance(edges['rest'], URIRef):
+            return class_dict[edges['first']]
+        else:
+            return {**class_dict[edges['first']], **class_dict[edges['rest']]}
 
     def parses_constructors(self, node: URIRef, edges: Dict, class_dict: Dict, relation: URIRef = None) \
             -> Tuple[Set, Optional[Dict]]:
@@ -308,10 +397,8 @@ def parses_constructors(self, node: URIRef, edges: Dict, class_dict: Dict, relat
                     obj_property = self.returns_object_property(node, edge_batch['first'], relation)
                     cleaned_classes |= {(node, obj_property, edge_batch['first'])}
                     edge_batch = None
-                else:
-                    edge_batch = self.parses_anonymous_axioms(edge_batch, class_dict)
-            else:
-                break
+                else: edge_batch = self.parses_anonymous_axioms(edge_batch, class_dict)
+            else: break
 
         return cleaned_classes, edge_batch
 
@@ -380,99 +467,158 @@ def cleans_owl_encoded_classes(self) -> Graph:
 
         decoded_graph: Graph = Graph()
         cleaned_nodes: Set = set()
-        complement_constructors: Set = set()
-        cardinality: Set = set()
-        misc: List = []
         pbar = tqdm(total=len(self.class_list))
-
         while self.class_list:
             pbar.update(1)
             node = self.class_list.pop(0)
-            node_information = self.creates_edge_dictionary(node)
-            class_edge_dict = node_information[0]
-            cardinality |= node_information[1]
-
-            if len(class_edge_dict) == 0:
-                pass
-            elif any(v for v in class_edge_dict.values() if 'complementOf' in v.keys()):
-                complement_constructors |= {node}
-                pass
+            node_info = self.creates_edge_dictionary(node)
+            if len(node_info[0]) == 0: pass
+            if self.detects_constructed_class_to_ignore(node_info, node) is True: pass
             else:
                 cleaned_nodes |= {node}
                 cleaned_classes: Set = set()
                 semantic_chunk = [x[1] for x in list(self.nx_mdg.out_edges(node, keys=True)) if isinstance(x[1], BNode)]
-                # decode owl-encoded edges
-                for element in semantic_chunk:
-                    edges = class_edge_dict[element]
+                for element in semantic_chunk:  # decode owl-encoded edges
+                    edges = node_info[0][element]
                     while edges:
                         if 'unionOf' in edges.keys():
-                            results = self.parses_constructors(node, edges, class_edge_dict)
+                            results = self.parses_constructors(node, edges, node_info[0])
                             cleaned_classes |= results[0]
                             edges = results[1]
                         elif 'intersectionOf' in edges.keys():
-                            results = self.parses_constructors(node, edges, class_edge_dict)
+                            results = self.parses_constructors(node, edges, node_info[0])
                             cleaned_classes |= results[0]
                             edges = results[1]
                         elif 'Restriction' in edges['type']:
-                            results = self.parses_restrictions(node, edges, class_edge_dict)
+                            results = self.parses_restrictions(node, edges, node_info[0])
                             cleaned_classes |= results[0]
                             edges = results[1]
-                        else:
-                            # catch all other axioms -- only catching owl:onProperty with owl:oneOf
-                            misc += [x for x in edges.keys() if x not in ['type', 'first', 'rest', 'onProperty']]
+                        else:  # catch all other axioms -- only catching owl:onProperty
+                            misc_axioms = [x for x in edges.keys() if x not in ['type', 'first', 'rest', 'onProperty']]
+                            self.owl_nets_dict['owl_nets']['misc'][str(node)] = {tuple(misc_axioms)}
                             edges = None
                 # add kept edges to filtered graph
                 decoded_graph = adds_edges_to_graph(decoded_graph, list(cleaned_classes))
+                self.owl_nets_dict['owl_nets']['decoded_classes'][str(node)] = {
+                    tuple([(str(x[0]), str(x[1]), str(x[2])) for x in cleaned_classes])}
         pbar.close()
-        del self.nx_mdg  # delete networkx graph object to free up memory
+        print('=' * 75 + '\nDecoded {} owl-encoded classes. Note the following:'.format(len(cleaned_nodes)))
+        print('{} cardinality elements'.format(len(list(self.owl_nets_dict['owl_nets']['cardinality'].keys()))))
+        print('ignored {} misc elements'.format(len(list(self.owl_nets_dict['owl_nets']['misc'].keys()))))
+        print('{} owl:complementOf removed'.format(len(list(self.owl_nets_dict['owl_nets']['complementOf'].keys()))))
+        print('{} negation elements\n'.format(len(list(self.owl_nets_dict['owl_nets']['negation'].keys()))) + '=' * 75)
+        del self.nx_mdg  # clean up environment
+        self.graph = decoded_graph  # pass decoded classes through triple cleaner
+        cleaned_decoded_graph = self.removes_edges_with_owl_semantics()
+
+        return cleaned_decoded_graph
+
+    def purifies_graph_build(self) -> None:
+        """Purifies an existing graph according to its kg_construction approach (i.e. "subclass" or "instance"). When
+        kg_construction is "subclass", then all triples where the subject and object are connected by RDF.type are
+        purified by converting RDF.type to RDFS.subClassOf for each triple as well as making the subject of this
+        triple the RDFS.subClassOf all ancestors of the object in this triple. Alternatively, when
+        kg_construction is "instance", then all triples where the subject and object are connected by RDFS.subClassOf
+        are purified by converting RDFS.subClassOf to RDF.type for each triple as well as making the subject of this
+        triple the RDF.type all ancestors of the object in this triple. Examples are provided below.
+
+        Returns:
+             None.
+        """
+
+        print('Purifying Graph Based on Knowledge Graph Construction Approach')
+
+        # get original and purification relation
+        org_rel = RDF.type if self.kg_construct_approach == 'subclass' else RDFS.subClassOf
+        pure_rel = RDFS.subClassOf if org_rel == RDF.type else RDF.type
 
-        print('=' * 75)
-        print('Decoded {} owl-encoded classes. Note the following:'.format(len(cleaned_nodes)))
-        print('{} owl class elements containing cardinality were ignored'.format(len(cardinality)))
-        print('ignored {} misc classes of the following type(s): {}'.format(len(misc), ', '.join(list(Counter(misc)))))
-        print('{} owl classes constructed using owl:complementOf were removed'.format(len(complement_constructors)))
-        print('=' * 75)
+        # find all edges that need to be updated
+        dirty_edges = list(self.graph.triples((None, org_rel, None)))
+        for edge in tqdm(dirty_edges):
+            self.graph.add((edge[0], pure_rel, edge[2]))  # fix primary edge
+            self.graph.remove(edge)
+            # make s "rel" (pure_rel - RDF.type or RDFS.subClassOf) all ancestors of o
+            o_ancs = gets_class_ancestors(self.graph, {edge[2]}, {edge[2]})  # filter to keep same namespace
+            ancs_filter = tuple([x for x in o_ancs if x.startswith('http') and URIRef(x) != edge[2]])
+            for node in ancs_filter:
+                self.graph.add((edge[0], pure_rel, URIRef(node)))
 
-        return decoded_graph
+            # update tracking dict
+            self.owl_nets_dict['{}_approach_purified'.format(self.kg_construct_approach)] |= set(edge + ancs_filter)
 
-    def run_owl_nets(self) -> Graph:
-        """Performs all steps of the OWL-NETS pipeline, including: (1) mapping all instances of class identifiers back
-        to original class identifiers; (2) filters a graph to remove all triples that include an owl:ObjectProperty
-        not included in the keep_properties list; and (3) decodes all owl-encoded classes of type intersection and
-        union constructor and all restrictions.
+        return None
+
+    def write_out_results(self, graph: Graph, kg_construction_approach: Optional[str] = None) -> None:
+        """Serializes graph and prints out basic statistics.
+
+        Args:
+            graph: An RDF Graph lib object.
+            kg_construction_approach: A string specifying the type of knowledge graph construction to implement.
 
         NOTE. It is important to check the number of unique nodes and relations in OWL-NETS and to compare the counts
         with and without the URIs (i.e. http://purl.obolibrary.org/obo/HP_0000000 vs HP_0000000). Doing this provides a
         nice sanity check and can help identify duplicate nodes (i.e. nodes with the same identifier, but different
         URIs -- where the URIs should be the same).
 
-        Returns:
-            An rdflib.Graph object that has been updated to only include triples owl decoded triples.
+        Return:
+             None.
         """
 
-        print('\nCreating OWL-NETS graph')
-
-        if self.kg_construct_approach == 'instance': self.updates_class_instance_identifiers()
-        filtered_graph = self.removes_edges_with_owl_semantics()  # filter out owl-encoded triples from original KG
-        self.graph = self.cleans_owl_encoded_classes()  # decode owl constructors and restrictions
-        owl_nets_graph = filtered_graph + self.removes_edges_with_owl_semantics()  # prune bad triples from decoded
-
-        # make final sweep over graph and remove any triples with a sub/obj containing an owl class object
-        print('\nVerifying OWL-NETS Nodes')
-        for x in tqdm(owl_nets_graph):
-            if 'owl#' in str(x[0]) or 'owl#' in str(x[2]): owl_nets_graph.remove(x)
         # write out owl-nets graph
         print('\nSerializing OWL-NETS Graph')
-        file_name = self.full_kg[:-4] + '_OWLNETS.nt'
-        owl_nets_graph.serialize(destination=self.write_location + file_name, format='nt')
+        f_name_lab = '_' + kg_construction_approach.upper() + '_purified' if kg_construction_approach else ''
+        f_name = [self.filename[:-4] + f_name_lab if '.owl' in self.filename
+                  else '.'.join(self.filename.split('.')[:-1]) + f_name_lab if '.' in self.filename
+                  else self.filename + f_name_lab][0]
+        f_name = '/' + f_name + '_OWLNETS.nt' if not f_name.startswith('/') else f_name + '_OWLNETS.nt'
+        graph.serialize(destination=self.write_location + f_name, format='nt')
+
         # get output statistics
-        unique_nodes = set([str(x) for y in [node[0::2] for node in list(owl_nets_graph)] for x in y])
-        unique_relations = set([str(rel[1]) for rel in list(owl_nets_graph)])
-        gets_ontology_statistics(self.write_location + file_name, self.owl_tools)
-        print('The OWL-Decoded Knowledge Graph Contains: {} Triples'.format(len(owl_nets_graph)))
-        print('The OWL-Decoded Knowledge Graph Contains: {} Unique Nodes'.format(len(unique_nodes)))
-        print('The OWL-Decoded Knowledge Graph Contains: {} Unique Relations'.format(len(unique_relations)))
+        unique_nodes = set([str(x) for y in [node[0::2] for node in list(graph)] for x in y])
+        unique_relations = set([str(rel[1]) for rel in list(graph)])
+        gets_ontology_statistics(self.write_location + f_name, self.owl_tools)
+        stats = 'The OWL-Decoded Knowledge Graph Contained: {} Triples, {} Unique Nodes, and {} Unique Relations'
+        print(stats.format(len(graph), len(unique_nodes), len(unique_relations)))
+
+        # write out owl_nets dictionary
+        with open(self.write_location + f_name.strip('.nt') + '_decoding_dict.pkl', 'wb') as out:
+            pickle.dump(self.owl_nets_dict, out)
+
         # convert graph to NetworkX MultiDigraph
-        converts_rdflib_to_networkx(self.write_location, file_name[:-3], owl_nets_graph)
+        converts_rdflib_to_networkx(self.write_location, f_name.strip('.nt'), graph)
+
+        return None
+
+    def run_owl_nets(self) -> Tuple:
+        """Performs all steps of the OWL-NETS pipeline, including: (1) removes owl:disjointWith axioms; (2) mapping all
+        instances of class identifiers back to original class identifiers; (3) filters a graph to remove all triples
+        that contain only semantic support triples; (4) decodes all owl-encoded classes of type intersection and union
+        constructor and all restrictions; (5) removes non-OBO namespace classes; and (6) purifies decoded graph to
+        input construction approach (i.e. None, subclass-based or instance-based).
+
+        Returns:
+            not_purified_graph: An rdflib.Graph object that has been updated to only include triples owl decoded
+                triples.
+            graph: An rdflib.Graph object that has been updated to only include triples owl decoded triples. In
+                additional to being purified according to the kg_construct_approach.
+        """
 
-        return owl_nets_graph
+        print('\nCreating OWL-NETS graph')
+
+        # run graph through OWL-NETS steps
+        self.removes_disjoint_with_axioms()
+        if self.kg_construct_approach == 'instance': self.updates_class_instance_identifiers()
+        self.converts_rdflib_to_networkx_multidigraph()  # create networkx representation
+        self.graph = self.removes_edges_with_owl_semantics() + self.cleans_owl_encoded_classes()
+
+        # Purify owl-nets representation
+        if self.kg_construct_approach is not None:
+            not_purified_graph = Graph()
+            not_purified_graph += self.graph.triples((None, None, None))
+            self.write_out_results(not_purified_graph)  # if purifying, write out original version first
+            self.purifies_graph_build()
+            self.write_out_results(self.graph, self.kg_construct_approach)
+            return not_purified_graph, self.graph
+        else:
+            self.write_out_results(self.graph)
+            return self.graph, None
diff --git a/pkt_kg/utils/__init__.py b/pkt_kg/utils/__init__.py
index 5102a20b4..2ab73ddd6 100644
--- a/pkt_kg/utils/__init__.py
+++ b/pkt_kg/utils/__init__.py
@@ -11,5 +11,5 @@
            'metadata_api_mapper', 'genomic_id_mapper', 'outputs_dictionary_data', 'gets_ontology_statistics',
            'gets_ontology_classes', 'gets_deprecated_ontology_classes', 'gets_object_properties',
            'gets_ontology_class_dbxrefs', 'gets_ontology_class_synonyms', 'merges_ontologies',
-           'ontology_file_formatter', 'adds_edges_to_graph', 'remove_edges_from_graph',
-           'finds_node_type', 'maps_node_ids_to_integers', 'converts_rdflib_to_networkx']
+           'ontology_file_formatter', 'adds_edges_to_graph', 'remove_edges_from_graph', 'gets_class_ancestors',
+           'finds_node_type', 'updates_graph_namespace', 'maps_node_ids_to_integers', 'converts_rdflib_to_networkx']
diff --git a/pkt_kg/utils/kg_utils.py b/pkt_kg/utils/kg_utils.py
index 80e7c3bda..8084ebc80 100644
--- a/pkt_kg/utils/kg_utils.py
+++ b/pkt_kg/utils/kg_utils.py
@@ -16,6 +16,8 @@
 Interacts with Knowledge Graphs
 * adds_edges_to_graph
 * remove_edges_from_graph
+* updates_graph_namespace
+* gets_class_ancestors
 
 Writes Triple Lists
 * maps_node_ids_to_integers
@@ -26,18 +28,21 @@
 
 # import needed libraries
 import glob
+import hashlib
 import json
 import networkx  # type: ignore
 import os
 import os.path
 from rdflib import Graph, Literal, Namespace, URIRef  # type: ignore
-from rdflib.namespace import RDF, OWL  # type: ignore
+from rdflib.namespace import OWL, RDF, RDFS  # type: ignore
 import subprocess
 
 from tqdm import tqdm  # type: ignore
-from typing import Dict, List, Optional, Set, Tuple
+from typing import Dict, List, Optional, Set, Tuple, Union
 
 # set-up environment variables
+obo = Namespace('http://purl.obolibrary.org/obo/')
+oboinowl = Namespace('http://www.geneontology.org/formats/oboInOwl#')
 schema = Namespace('http://www.w3.org/2001/XMLSchema#')
 
 
@@ -289,6 +294,23 @@ def remove_edges_from_graph(graph: Graph, edge_list: List) -> Graph:
     return graph
 
 
+def updates_graph_namespace(entity_namespace: str, graph: Graph, node: str) -> Graph:
+    """Adds a triple to a graph specifying a node's namespace. This is only used for non-ontology entities.
+
+    Args:
+        entity_namespace: A string containing an entity namespace (i.e. "pathway", "gene").
+        graph: An RDFLib Graph object.
+        node: A string containing the URI for a node in the graph.
+
+    Returns:
+        graph: An RDFLib Graph object.
+    """
+
+    graph.add((URIRef(node), URIRef(oboinowl + 'hasOBONamespace'), Literal(entity_namespace)))
+
+    return graph
+
+
 def finds_node_type(edge_info: Dict) -> Dict:
     """Takes a dictionary of edge information and parses the data type for each node in the edge. The function
     returns either None or a string containing a particular node from the edge.
@@ -324,14 +346,14 @@ def finds_node_type(edge_info: Dict) -> Dict:
     return nodes
 
 
-def maps_node_ids_to_integers(graph: Graph, write_location: str, output_ints: str, output_ints_map: str) -> None:
+def maps_node_ids_to_integers(graph: Graph, write_location: str, output_ints: str, output_ints_map: str) -> Dict:
     """Loops over the knowledge graph in order to create three different types of files:
         - Integers: a tab-delimited `.txt` file containing three columns, one for each part of a triple (i.e.
-        subject, predicate, object). The subject, predicate, and object identifiers have been mapped to integers.
+          subject, predicate, object). The subject, predicate, and object identifiers have been mapped to integers.
         - Identifiers: a tab-delimited `.txt` file containing three columns, one for each part of a triple (i.e.
-        subject, predicate, object). Both the subject and object identifiers have not been mapped to integers.
+          subject, predicate, object). Both the subject and object identifiers have not been mapped to integers.
         - Identifier-Integer Map: a `.json` file containing a dictionary where the keys are node identifiers and
-        the values are integers.
+          the values are integers.
 
     Args:
         graph: An rdflib graph object.
@@ -340,7 +362,7 @@ def maps_node_ids_to_integers(graph: Graph, write_location: str, output_ints: st
         output_ints_map: the name and file path to write out results.
 
     Returns:
-        None.
+        node_map: A dictionary where keys are integers and values are identifiers.
 
     Raises:
         ValueError: If the length of the graph is not the same as the number of extracted triples.
@@ -351,7 +373,6 @@ def maps_node_ids_to_integers(graph: Graph, write_location: str, output_ints: st
     # build graph from input file and set counter
     out_ints = open(write_location + output_ints, 'w', encoding='utf-8')
     out_ids = open(write_location + output_ints.replace('Integers', 'Identifiers'), 'w', encoding='utf-8')
-    # write file headers
     out_ints.write('subject' + '\t' + 'predicate' + '\t' + 'object' + '\n')
     out_ids.write('subject' + '\t' + 'predicate' + '\t' + 'object' + '\n')
     for edge in tqdm(graph):
@@ -375,7 +396,6 @@ def maps_node_ids_to_integers(graph: Graph, write_location: str, output_ints: st
                           edge[2].encode('utf-8').decode() + '\n')
         # update counter and delete edge
         output_triples += 1
-        graph.remove(edge)
     out_ints.close(), out_ids.close()
 
     # CHECK - verify we get the number of edges that we would expect to get
@@ -384,13 +404,36 @@ def maps_node_ids_to_integers(graph: Graph, write_location: str, output_ints: st
     else:
         with open(write_location + '/' + output_ints_map, 'w') as file_name:
             json.dump(node_map, file_name)
-    del graph  # clean up environment
 
-    return None
+    return node_map
 
 
 def converts_rdflib_to_networkx(write_location: str, full_kg: str, graph: Optional[Graph] = None) -> None:
-    """Converts an RDFLib.Graph object into a Networkx MultiDiGraph and pickles a copy locally.
+    """Converts an RDFLib.Graph object into a Networkx MultiDiGraph and pickles a copy locally. Each node is provided a
+    key that is the URI identifier and each edge is given a key which is an md5 hash of the triple and a weight of
+    0.0. An example of the output is shown below. The md5 hash is meant to store a unique key that represents that
+    predicate with respect to the triples it occurs with.
+
+    Source: https://networkx.org/documentation/stable/reference/classes/multidigraph.html
+
+        Example:
+            Input: (URIRef('http://purl.obolibrary.org/obo/SO_0000288'),
+                    URIRef('http://www.w3.org/2000/01/rdf-schema#subClassOf'),
+                    URIRef('http://purl.obolibrary.org/obo/SO_0000287'))
+            Output:
+                - node data: [
+                        (URIRef('http://purl.obolibrary.org/obo/SO_0000288'),
+                        {'key': 'http://purl.obolibrary.org/obo/SO_0000288'}),
+                        (URIRef('http://www.w3.org/2000/01/rdf-schema#subClassOf'),
+                        {'key': 'http://www.w3.org/2000/01/rdf-schema#subClassOf'}),
+                        (URIRef('http://purl.obolibrary.org/obo/SO_0000287'),
+                        {'key': 'http://purl.obolibrary.org/obo/SO_0000287'})
+                            ]
+                - edge data: [
+                        (URIRef('http://purl.obolibrary.org/obo/SO_0000288'),
+                         URIRef('http://purl.obolibrary.org/obo/SO_0000287'),
+                         {'predicate_key': '9cbd482627d217b38eb407d7eba48020', 'weight': 0.0})
+                            ]
 
     Args:
         write_location: A string pointing to a local directory for writing data.
@@ -408,19 +451,49 @@ def converts_rdflib_to_networkx(write_location: str, full_kg: str, graph: Option
 
     # read in knowledge graph if class graph attribute is not present
     if not isinstance(graph, Graph):
-        graph = Graph()
         file_type = 'xml' if 'OWLNETS' not in full_kg else full_kg.split('.')[-1]
         ext = '.owl' if file_type == 'xml' else '.nt'
-        graph.parse(write_location + full_kg + ext, format=file_type)
+        graph = Graph().parse(write_location + full_kg + ext, format=file_type)
 
     # convert graph to networkx object
     nx_mdg = networkx.MultiDiGraph()
     for s, p, o in tqdm(graph):
-        nx_mdg.add_edge(s, o, **{'key': p})
+        pred_key = hashlib.md5('{}{}{}'.format(str(s), str(p), str(o)).encode()).hexdigest()
+        nx_mdg.add_node(s, key=str(s))
+        nx_mdg.add_node(o, key=str(o))
+        nx_mdg.add_edge(s, o, **{'key': p, 'predicate_key': pred_key, 'weight': 0.0})
 
     # pickle networkx graph
-    print('Pickling MultiDiGraph. For Large Networks Process Takes Several Minutes.')
+    print('Pickling MultiDiGraph -- For Large Networks Process Takes Several Minutes.')
     networkx.write_gpickle(nx_mdg, write_location + full_kg + '_NetworkxMultiDiGraph.gpickle')
     del nx_mdg   # clean up environment
 
     return None
+
+
+def gets_class_ancestors(graph: Graph, class_uris: Set[Union[URIRef, str]], class_list: Optional[Set] = None) -> Set:
+    """A method that recursively searches an ontology hierarchy to pull all ancestor concepts for an input class.
+
+    Args:
+        graph: An RDFLib graph object assumed to contain ontology data.
+        class_uris: A list of at least one ontology class RDFLib URIRef object.
+        class_list: A list of URIs representing the ancestor classes found for the input class_uris.
+
+    Returns:
+        A list of ontology class ordered by the ontology hierarchy.
+    """
+
+    # instantiate list object if none passed to function
+    class_list = set() if class_list is None else class_list
+    class_list = set([x if isinstance(x, URIRef) else URIRef(obo + x) for x in class_list])
+
+    # check class uris are formatted correctly and get their ancestors
+    class_uris = set([x if isinstance(x, URIRef) else URIRef(obo + x) for x in class_uris])
+    ancestors = set([j for k in [list(graph.objects(x, RDFS.subClassOf)) for x in set(class_uris)] for j in k])
+
+    if len(ancestors) == 0 or len(ancestors.difference(class_list)) == 0:
+        return set([str(x) for x in class_list])
+    else:
+        class_uris = ancestors.difference(class_list)
+        class_list |= ancestors.difference(class_list)
+        return gets_class_ancestors(graph, class_uris, class_list)
diff --git a/resources/construction_approach/README.md b/resources/construction_approach/README.md
index d73e90d82..f4c690929 100644
--- a/resources/construction_approach/README.md
+++ b/resources/construction_approach/README.md
@@ -32,13 +32,15 @@ A table is provided below showing the different triples that are added as functi
 
 Edge Type | Relations | Needed Triples  
 :--: | :--: | :--  
-Class-Class | Relations Only | **`GO_1234567`, `REL`, `DOID_1234567`**<br><br>`UUID1`, `rdf:type`, `GO_1234567`<br>`UUID1`, `rdf:type`, `owl:NamedIndividual`<br>`UUID2`, `rdf:type`, `DOID_1234567`<br>`UUID2`, `rdf:type`, `owl:NamedIndividual`<br>`GUUID1`, `REL`, `UUID2`
+Class-Class | Relations Only | **`GO_1234567`, `REL`, `DOID_1234567`**<br><br>`UUID1`, `rdf:type`, `GO_1234567`<br>`UUID1`, `rdf:type`, `owl:NamedIndividual`<br>`UUID2`, `rdf:type`, `DOID_1234567`<br>`UUID2`, `rdf:type`, `owl:NamedIndividual`<br>`UUID1`, `REL`, `UUID2`
 Class-Class | Relations + Inverse Relations | **`GO_1234567`, `REL`, `DOID_1234567`<br>`DOID_1234567`, `INV_REL`, `GO_1234567`**<br><br>`UUID1`, `rdf:type`, `GO_1234567`<br>`UUID1`, `rdf:type`, `owl:NamedIndividual`<br>`UUID2`, `rdf:type`, `DOID_1234567`<br>`UUID2`, `rdf:type`, `owl:NamedIndividual`<br>`UUID1`, `REL`, `UUID2`<br>`UUID2`, `INV_REL`, `UUID1`
 Class-Instance | Relations Only | **`GO_1234567`, `REL`, `HGNC_1234567`**<br><br>`UUID`, `rdf:type`, `GO_1234567`<br>`UUID`, `rdf:type`, `owl:NamedIndividual`<br>`HGNC_1234567`, `rdf:type`, `subclass_dict[HGNC_1234567]`<br>`HGNC_1234567`, `rdf:type`, `owl:NamedIndividual`<br>`UUID`, `REL`, `HGNC_1234567`
 Class-Instance | Relations + Inverse Relations | **`GO_1234567`, `REL`, `HGNC_1234567`<br>`HGNC_1234567`, `INV_REL`, `GO_1234567`**<br><br>`UUID`, `rdf:type`, `GO_1234567`<br>`UUID`, `rdf:type`, `owl:NamedIndividual`<br>`HGNC_1234567`, `rdf:type`, `subclass_dict[HGNC_1234567]`<br>`HGNC_1234567`, `rdf:type`, `owl:NamedIndividual`<br>`UUID`, `REL`, `HGNC_1234567`<br>`HGNC_1234567`, `INV_REL`, `UUID`
 Instance-Instance | Relations Only | **`HGNC_1234567`, `REL`, `HGNC_7654321`**<br><br>`HGNC_1234567`, `rdf:type`,`subclass_dict[HGNC_1234567]`<br>`HGNC_1234567`, `rdf:type`, `owl:NamedIndividual`<br>`HGNC_7654321`, `rdf:type`,`subclass_dict[HGNC_7654321]`<br>`HGNC_7654321`, `rdf:type`, `owl:NamedIndividual`<br>`HGNC_1234567`, `REL`, `HGNC_7654321`
 Instance-Instance | Relations + Inverse Relations | **`HGNC_1234567`, `REL`, `HGNC_7654321`<br>`HGNC_7654321`, `INV_REL`, `HGNC_1234567`**<br><br>`HGNC_1234567`, `rdf:type`, `subclass_dict[HGNC_1234567]`<br>`HGNC_1234567`, `rdf:type`, `owl:NamedIndividual`<br>`HGNC_7654321`, `rdf:type`, `subclass_dict[HGNC_7654321]`<br>`HGNC_7654321`, `rdf:type`, `owl:NamedIndividual`<br>`HGNC_1234567`, `REL`, `HGNC_7654321`<br>`HGNC_7654321`, `INV_REL`, `HGNC_1234567`
 
+<br>
+
 *Note.* When a `UUDID` is used in the instance-based builds it is created as a md5 hash of the original `owl:Class` and added to the `pkt` namespace (i.e. `https://github.com/callahantiff/PheKnowLator/pkt/`).
 
 <br><br>
diff --git a/resources/edge_data/edge_source_metadata.txt b/resources/edge_data/edge_source_metadata.txt
index e573ef47d..2d661653e 100644
--- a/resources/edge_data/edge_source_metadata.txt
+++ b/resources/edge_data/edge_source_metadata.txt
@@ -1,15 +1,15 @@
 ===================================
-#Thu Apr 23 23:18:50 UTC 2020 
+#Sun Jan 10 07:51:19 UTC 2021 
 ===================================
 
 EDGE: chemical-disease
 DATA PROCESSING INFO
-  - IDENTIFIER MAPPING = chemical (./resources/processed_data/MESH_CHEBI_MAP.txt) | disease (./resources/processed_data/DISEASE_DOID_MAP.txt)
+  - IDENTIFIER MAPPING = chemical (./resources/processed_data/MESH_CHEBI_MAP.txt) | disease (./resources/processed_data/DISEASE_MONDO_MAP.txt)
   - FILTERING CRITERIA = None
   - EVIDENCE CRITERIA = data[5]!=''
 DATA INFO
-  - DOWNLOAD_URL = http://ctdbase.org/reports/CTD_chemicals_diseases.tsv.gz
-  - DOWNLOAD_DATE = 04/23/2020
+  - DOWNLOAD_URL = https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/original_data/CTD_chemicals_diseases.tsv
+  - DOWNLOAD_DATE = 01/10/2021
   - FILE_SIZE_IN_BYTES = 619999701
   - DOWNLOADED_FILE_LOCATION = resources/edge_data/chemical-disease_CTD_chemicals_diseases.tsv
 
@@ -19,8 +19,8 @@ DATA PROCESSING INFO
   - FILTERING CRITERIA = data[6]==Homo sapiens | data[5].startswith('gene')
   - EVIDENCE CRITERIA = None
 DATA INFO
-  - DOWNLOAD_URL = http://ctdbase.org/reports/CTD_chem_gene_ixns.tsv.gz
-  - DOWNLOAD_DATE = 04/23/2020
+  - DOWNLOAD_URL = https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/original_data/CTD_chem_gene_ixns.tsv
+  - DOWNLOAD_DATE = 01/10/2021
   - FILE_SIZE_IN_BYTES = 383684091
   - DOWNLOADED_FILE_LOCATION = resources/edge_data/chemical-gene_CTD_chem_gene_ixns.tsv
 
@@ -30,8 +30,8 @@ DATA PROCESSING INFO
   - FILTERING CRITERIA = data[3]==Biological Process
   - EVIDENCE CRITERIA = data[8]<0.0001
 DATA INFO
-  - DOWNLOAD_URL = http://ctdbase.org/reports/CTD_chem_go_enriched.tsv.gz
-  - DOWNLOAD_DATE = 04/23/2020
+  - DOWNLOAD_URL = https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/original_data/CTD_chem_go_enriched.tsv
+  - DOWNLOAD_DATE = 01/10/2021
   - FILE_SIZE_IN_BYTES = 771242902
   - DOWNLOADED_FILE_LOCATION = resources/edge_data/chemical-gobp_CTD_chem_go_enriched.tsv
 
@@ -41,8 +41,8 @@ DATA PROCESSING INFO
   - FILTERING CRITERIA = data[3]==Cellular Component
   - EVIDENCE CRITERIA = data[8]<0.0001
 DATA INFO
-  - DOWNLOAD_URL = http://ctdbase.org/reports/CTD_chem_go_enriched.tsv.gz
-  - DOWNLOAD_DATE = 04/23/2020
+  - DOWNLOAD_URL = https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/original_data/CTD_chem_go_enriched.tsv
+  - DOWNLOAD_DATE = 01/10/2021
   - FILE_SIZE_IN_BYTES = 771242902
   - DOWNLOADED_FILE_LOCATION = resources/edge_data/chemical-gocc_CTD_chem_go_enriched.tsv
 
@@ -52,8 +52,8 @@ DATA PROCESSING INFO
   - FILTERING CRITERIA = data[3]==Molecular Function
   - EVIDENCE CRITERIA = data[8]<0.0001
 DATA INFO
-  - DOWNLOAD_URL = http://ctdbase.org/reports/CTD_chem_go_enriched.tsv.gz
-  - DOWNLOAD_DATE = 04/23/2020
+  - DOWNLOAD_URL = https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/original_data/CTD_chem_go_enriched.tsv
+  - DOWNLOAD_DATE = 01/10/2021
   - FILE_SIZE_IN_BYTES = 771242902
   - DOWNLOADED_FILE_LOCATION = resources/edge_data/chemical-gomf_CTD_chem_go_enriched.tsv
 
@@ -63,8 +63,8 @@ DATA PROCESSING INFO
   - FILTERING CRITERIA = data[5]==Homo sapiens
   - EVIDENCE CRITERIA = None
 DATA INFO
-  - DOWNLOAD_URL = https://reactome.org/download/current/ChEBI2Reactome_All_Levels.txt
-  - DOWNLOAD_DATE = 04/23/2020
+  - DOWNLOAD_URL = https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/original_data/ChEBI2Reactome_All_Levels.txt
+  - DOWNLOAD_DATE = 01/10/2021
   - FILE_SIZE_IN_BYTES = 30255399
   - DOWNLOADED_FILE_LOCATION = resources/edge_data/chemical-pathway_ChEBI2Reactome_All_Levels.txt
 
@@ -74,8 +74,8 @@ DATA PROCESSING INFO
   - FILTERING CRITERIA = None
   - EVIDENCE CRITERIA = data[5]!=''
 DATA INFO
-  - DOWNLOAD_URL = http://ctdbase.org/reports/CTD_chemicals_diseases.tsv.gz
-  - DOWNLOAD_DATE = 04/23/2020
+  - DOWNLOAD_URL = https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/original_data/CTD_chemicals_diseases.tsv
+  - DOWNLOAD_DATE = 01/10/2021
   - FILE_SIZE_IN_BYTES = 619999701
   - DOWNLOADED_FILE_LOCATION = resources/edge_data/chemical-phenotype_CTD_chemicals_diseases.tsv
 
@@ -85,8 +85,8 @@ DATA PROCESSING INFO
   - FILTERING CRITERIA = data[6]==Homo sapiens | data[5] .startswith('protein') 
   - EVIDENCE CRITERIA = None
 DATA INFO
-  - DOWNLOAD_URL = http://ctdbase.org/reports/CTD_chem_gene_ixns.tsv.gz
-  - DOWNLOAD_DATE = 04/23/2020
+  - DOWNLOAD_URL = https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/original_data/CTD_chem_gene_ixns.tsv
+  - DOWNLOAD_DATE = 01/10/2021
   - FILE_SIZE_IN_BYTES = 383684091
   - DOWNLOADED_FILE_LOCATION = resources/edge_data/chemical-protein_CTD_chem_gene_ixns.tsv
 
@@ -96,8 +96,8 @@ DATA PROCESSING INFO
   - FILTERING CRITERIA = data[6]==Homo sapiens | data[5].startswith('mRNA')
   - EVIDENCE CRITERIA = None
 DATA INFO
-  - DOWNLOAD_URL = http://ctdbase.org/reports/CTD_chem_gene_ixns.tsv.gz
-  - DOWNLOAD_DATE = 04/23/2020
+  - DOWNLOAD_URL = https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/original_data/CTD_chem_gene_ixns.tsv
+  - DOWNLOAD_DATE = 01/10/2021
   - FILE_SIZE_IN_BYTES = 383684091
   - DOWNLOADED_FILE_LOCATION = resources/edge_data/chemical-rna_CTD_chem_gene_ixns.tsv
 
@@ -107,19 +107,19 @@ DATA PROCESSING INFO
   - FILTERING CRITERIA = None
   - EVIDENCE CRITERIA = None
 DATA INFO
-  - DOWNLOAD_URL = http://compbio.charite.de/jenkins/job/hpo.annotations/lastStableBuild/artifact/misc/phenotype_annotation.tab
-  - DOWNLOAD_DATE = 04/23/2020
+  - DOWNLOAD_URL = https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/original_data/phenotype_annotation.tab
+  - DOWNLOAD_DATE = 01/10/2021
   - FILE_SIZE_IN_BYTES = 7369779
   - DOWNLOADED_FILE_LOCATION = resources/edge_data/disease-phenotype_phenotype_annotation.tab
 
 EDGE: gene-disease
 DATA PROCESSING INFO
-  - IDENTIFIER MAPPING = disease (./resources/processed_data/DISEASE_DOID_MAP.txt)
+  - IDENTIFIER MAPPING = disease (./resources/processed_data/DISEASE_MONDO_MAP.txt)
   - FILTERING CRITERIA = None
   - EVIDENCE CRITERIA = data[10]>=0.70
 DATA INFO
-  - DOWNLOAD_URL = https://www.disgenet.org/static/disgenet_ap1/files/downloads/curated_gene_disease_associations.tsv.gz
-  - DOWNLOAD_DATE = 04/23/2020
+  - DOWNLOAD_URL = https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/original_data/curated_gene_disease_associations.tsv
+  - DOWNLOAD_DATE = 01/10/2021
   - FILE_SIZE_IN_BYTES = 11055467
   - DOWNLOADED_FILE_LOCATION = resources/edge_data/gene-disease_curated_gene_disease_associations.tsv
 
@@ -129,8 +129,8 @@ DATA PROCESSING INFO
   - FILTERING CRITERIA = None
   - EVIDENCE CRITERIA = data[2]>=0.00012
 DATA INFO
-  - DOWNLOAD_URL = http://genemania.org/data/current/Homo_sapiens.COMBINED/COMBINED.DEFAULT_NETWORKS.BP_COMBINING.txt
-  - DOWNLOAD_DATE = 04/23/2020
+  - DOWNLOAD_URL = https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/original_data/COMBINED.DEFAULT_NETWORKS.BP_COMBINING.txt
+  - DOWNLOAD_DATE = 01/10/2021
   - FILE_SIZE_IN_BYTES = 270656653
   - DOWNLOADED_FILE_LOCATION = resources/edge_data/gene-gene_COMBINED.DEFAULT_NETWORKS.BP_COMBINING.txt
 
@@ -140,8 +140,8 @@ DATA PROCESSING INFO
   - FILTERING CRITERIA = data[3].startswith('REACT:R-HSA-')
   - EVIDENCE CRITERIA = None
 DATA INFO
-  - DOWNLOAD_URL = http://ctdbase.org/reports/CTD_genes_pathways.tsv.gz
-  - DOWNLOAD_DATE = 04/23/2020
+  - DOWNLOAD_URL = https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/original_data/CTD_genes_pathways.tsv
+  - DOWNLOAD_DATE = 01/10/2021
   - FILE_SIZE_IN_BYTES = 8193942
   - DOWNLOADED_FILE_LOCATION = resources/edge_data/gene-pathway_CTD_genes_pathways.tsv
 
@@ -151,8 +151,8 @@ DATA PROCESSING INFO
   - FILTERING CRITERIA = None
   - EVIDENCE CRITERIA = data[10]>=0.70
 DATA INFO
-  - DOWNLOAD_URL = https://www.disgenet.org/static/disgenet_ap1/files/downloads/curated_gene_disease_associations.tsv.gz
-  - DOWNLOAD_DATE = 04/23/2020
+  - DOWNLOAD_URL = https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/original_data/curated_gene_disease_associations.tsv
+  - DOWNLOAD_DATE = 01/10/2021
   - FILE_SIZE_IN_BYTES = 11055467
   - DOWNLOADED_FILE_LOCATION = resources/edge_data/gene-phenotype_curated_gene_disease_associations.tsv
 
@@ -162,8 +162,8 @@ DATA PROCESSING INFO
   - FILTERING CRITERIA = data[2]==protein-coding
   - EVIDENCE CRITERIA = None
 DATA INFO
-  - DOWNLOAD_URL = https://www.dropbox.com/s/e5x7rq4kc2kfq49/ENTREZ_GENE_PRO_ONTOLOGY_MAP.txt?dl=1
-  - DOWNLOAD_DATE = 04/23/2020
+  - DOWNLOAD_URL = https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/processed_data/ENTREZ_GENE_PRO_ONTOLOGY_MAP.txt
+  - DOWNLOAD_DATE = 01/10/2021
   - FILE_SIZE_IN_BYTES = 2536394
   - DOWNLOADED_FILE_LOCATION = resources/edge_data/gene-protein_ENTREZ_GENE_PRO_ONTOLOGY_MAP.txt
 
@@ -173,8 +173,8 @@ DATA PROCESSING INFO
   - FILTERING CRITERIA = None
   - EVIDENCE CRITERIA = None
 DATA INFO
-  - DOWNLOAD_URL = https://www.dropbox.com/s/jxm9v7qfwm2b6ot/ENTREZ_GENE_ENSEMBL_TRANSCRIPT_MAP.txt?dl=1
-  - DOWNLOAD_DATE = 04/23/2020
+  - DOWNLOAD_URL = https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/processed_data/ENTREZ_GENE_ENSEMBL_TRANSCRIPT_MAP.txt
+  - DOWNLOAD_DATE = 01/10/2021
   - FILE_SIZE_IN_BYTES = 16378762
   - DOWNLOADED_FILE_LOCATION = resources/edge_data/gene-rna_ENTREZ_GENE_ENSEMBL_TRANSCRIPT_MAP.txt
 
@@ -184,8 +184,8 @@ DATA PROCESSING INFO
   - FILTERING CRITERIA = data[8]==P | data[12]==taxon:9606 | data[5].startswith('REACTOME')
   - EVIDENCE CRITERIA = None
 DATA INFO
-  - DOWNLOAD_URL = https://reactome.org/download/current/gene_association.reactome.gz
-  - DOWNLOAD_DATE = 04/23/2020
+  - DOWNLOAD_URL = https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/original_data/gene_association.reactome
+  - DOWNLOAD_DATE = 01/10/2021
   - FILE_SIZE_IN_BYTES = 12558216
   - DOWNLOADED_FILE_LOCATION = resources/edge_data/gobp-pathway_gene_association.reactome
 
@@ -195,8 +195,8 @@ DATA PROCESSING INFO
   - FILTERING CRITERIA = data[8]==C | data[12]==taxon:9606 | data[5].startswith('REACTOME')
   - EVIDENCE CRITERIA = None
 DATA INFO
-  - DOWNLOAD_URL = https://reactome.org/download/current/gene_association.reactome.gz
-  - DOWNLOAD_DATE = 04/23/2020
+  - DOWNLOAD_URL = https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/original_data/gene_association.reactome
+  - DOWNLOAD_DATE = 01/10/2021
   - FILE_SIZE_IN_BYTES = 12558216
   - DOWNLOADED_FILE_LOCATION = resources/edge_data/pathway-gocc_gene_association.reactome
 
@@ -206,8 +206,8 @@ DATA PROCESSING INFO
   - FILTERING CRITERIA = data[8]==F | data[12]==taxon:9606 | data[5].startswith('REACTOME')
   - EVIDENCE CRITERIA = None
 DATA INFO
-  - DOWNLOAD_URL = https://reactome.org/download/current/gene_association.reactome.gz
-  - DOWNLOAD_DATE = 04/23/2020
+  - DOWNLOAD_URL = https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/original_data/gene_association.reactome
+  - DOWNLOAD_DATE = 01/10/2021
   - FILE_SIZE_IN_BYTES = 12558216
   - DOWNLOADED_FILE_LOCATION = resources/edge_data/pathway-gomf_gene_association.reactome
 
@@ -217,8 +217,8 @@ DATA PROCESSING INFO
   - FILTERING CRITERIA = data[3]==Evidence at protein level | data[4]==anatomy
   - EVIDENCE CRITERIA = None
 DATA INFO
-  - DOWNLOAD_URL = https://www.dropbox.com/s/us3u516e4vhkuco/HPA_GTEX_RNA_GENE_PROTEIN_EDGES.txt?dl=1
-  - DOWNLOAD_DATE = 04/23/2020
+  - DOWNLOAD_URL = https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/processed_data/HPA_GTEX_RNA_GENE_PROTEIN_EDGES.txt
+  - DOWNLOAD_DATE = 01/10/2021
   - FILE_SIZE_IN_BYTES = 20467831
   - DOWNLOADED_FILE_LOCATION = resources/edge_data/protein-anatomy_HPA_GTEX_RNA_GENE_PROTEIN_EDGES.txt
 
@@ -228,8 +228,8 @@ DATA PROCESSING INFO
   - FILTERING CRITERIA = None
   - EVIDENCE CRITERIA = None
 DATA INFO
-  - DOWNLOAD_URL = https://www.dropbox.com/s/w4lh6k9wbo5qkw0/UNIPROT_PROTEIN_CATALYST.txt?dl=1
-  - DOWNLOAD_DATE = 04/23/2020
+  - DOWNLOAD_URL = https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/processed_data/UNIPROT_PROTEIN_CATALYST.txt
+  - DOWNLOAD_DATE = 01/10/2021
   - FILE_SIZE_IN_BYTES = 1092346
   - DOWNLOADED_FILE_LOCATION = resources/edge_data/protein-catalyst_UNIPROT_PROTEIN_CATALYST.txt
 
@@ -239,8 +239,8 @@ DATA PROCESSING INFO
   - FILTERING CRITERIA = data[3]==Evidence at protein level | data[4]==cell line
   - EVIDENCE CRITERIA = None
 DATA INFO
-  - DOWNLOAD_URL = https://www.dropbox.com/s/us3u516e4vhkuco/HPA_GTEX_RNA_GENE_PROTEIN_EDGES.txt?dl=1
-  - DOWNLOAD_DATE = 04/23/2020
+  - DOWNLOAD_URL = https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/processed_data/HPA_GTEX_RNA_GENE_PROTEIN_EDGES.txt
+  - DOWNLOAD_DATE = 01/10/2021
   - FILE_SIZE_IN_BYTES = 20467831
   - DOWNLOADED_FILE_LOCATION = resources/edge_data/protein-cell_HPA_GTEX_RNA_GENE_PROTEIN_EDGES.txt
 
@@ -250,8 +250,8 @@ DATA PROCESSING INFO
   - FILTERING CRITERIA = None
   - EVIDENCE CRITERIA = None
 DATA INFO
-  - DOWNLOAD_URL = https://www.dropbox.com/s/xjtljf21eqign73/UNIPROT_PROTEIN_COFACTOR.txt?dl=1
-  - DOWNLOAD_DATE = 04/23/2020
+  - DOWNLOAD_URL = https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/processed_data/UNIPROT_PROTEIN_COFACTOR.txt
+  - DOWNLOAD_DATE = 01/10/2021
   - FILE_SIZE_IN_BYTES = 101341
   - DOWNLOADED_FILE_LOCATION = resources/edge_data/protein-cofactor_UNIPROT_PROTEIN_COFACTOR.txt
 
@@ -261,8 +261,8 @@ DATA PROCESSING INFO
   - FILTERING CRITERIA = data[8]==P | data[12]==taxon:9606
   - EVIDENCE CRITERIA = None
 DATA INFO
-  - DOWNLOAD_URL = http://geneontology.org/gene-associations/goa_human.gaf.gz
-  - DOWNLOAD_DATE = 04/23/2020
+  - DOWNLOAD_URL = https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/original_data/goa_human.gaf
+  - DOWNLOAD_DATE = 01/10/2021
   - FILE_SIZE_IN_BYTES = 84936865
   - DOWNLOADED_FILE_LOCATION = resources/edge_data/protein-gobp_goa_human.gaf
 
@@ -272,8 +272,8 @@ DATA PROCESSING INFO
   - FILTERING CRITERIA = data[8]==C | data[12]==taxon:9606
   - EVIDENCE CRITERIA = None
 DATA INFO
-  - DOWNLOAD_URL = http://geneontology.org/gene-associations/goa_human.gaf.gz
-  - DOWNLOAD_DATE = 04/23/2020
+  - DOWNLOAD_URL = https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/original_data/goa_human.gaf
+  - DOWNLOAD_DATE = 01/10/2021
   - FILE_SIZE_IN_BYTES = 84936865
   - DOWNLOADED_FILE_LOCATION = resources/edge_data/protein-gocc_goa_human.gaf
 
@@ -283,8 +283,8 @@ DATA PROCESSING INFO
   - FILTERING CRITERIA = data[8]==F | data[12]==taxon:9606
   - EVIDENCE CRITERIA = None
 DATA INFO
-  - DOWNLOAD_URL = http://geneontology.org/gene-associations/goa_human.gaf.gz
-  - DOWNLOAD_DATE = 04/23/2020
+  - DOWNLOAD_URL = https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/original_data/goa_human.gaf
+  - DOWNLOAD_DATE = 01/10/2021
   - FILE_SIZE_IN_BYTES = 84936865
   - DOWNLOADED_FILE_LOCATION = resources/edge_data/protein-gomf_goa_human.gaf
 
@@ -294,8 +294,8 @@ DATA PROCESSING INFO
   - FILTERING CRITERIA = data[5]==Homo sapiens
   - EVIDENCE CRITERIA = None
 DATA INFO
-  - DOWNLOAD_URL = https://reactome.org/download/current/UniProt2Reactome_All_Levels.txt
-  - DOWNLOAD_DATE = 04/23/2020
+  - DOWNLOAD_URL = https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/original_data/UniProt2Reactome_All_Levels.txt
+  - DOWNLOAD_DATE = 01/10/2021
   - FILE_SIZE_IN_BYTES = 109193926
   - DOWNLOADED_FILE_LOCATION = resources/edge_data/protein-pathway_UniProt2Reactome_All_Levels.txt
 
@@ -305,8 +305,8 @@ DATA PROCESSING INFO
   - FILTERING CRITERIA = None
   - EVIDENCE CRITERIA = data[2]>=700
 DATA INFO
-  - DOWNLOAD_URL = https://stringdb-static.org/download/protein.links.v11.0/9606.protein.links.v11.0.txt.gz
-  - DOWNLOAD_DATE = 04/23/2020
+  - DOWNLOAD_URL = https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/original_data/9606.protein.links.v11.0.txt
+  - DOWNLOAD_DATE = 01/10/2021
   - FILE_SIZE_IN_BYTES = 540934917
   - DOWNLOADED_FILE_LOCATION = resources/edge_data/protein-protein_9606.protein.links.v11.0.txt
 
@@ -316,8 +316,8 @@ DATA PROCESSING INFO
   - FILTERING CRITERIA = data[3]==Evidence at transcript level | data[4]==anatomy
   - EVIDENCE CRITERIA = None
 DATA INFO
-  - DOWNLOAD_URL = https://www.dropbox.com/s/us3u516e4vhkuco/HPA_GTEX_RNA_GENE_PROTEIN_EDGES.txt?dl=1
-  - DOWNLOAD_DATE = 04/23/2020
+  - DOWNLOAD_URL = https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/processed_data/HPA_GTEX_RNA_GENE_PROTEIN_EDGES.txt
+  - DOWNLOAD_DATE = 01/10/2021
   - FILE_SIZE_IN_BYTES = 20467831
   - DOWNLOADED_FILE_LOCATION = resources/edge_data/rna-anatomy_HPA_GTEX_RNA_GENE_PROTEIN_EDGES.txt
 
@@ -327,8 +327,8 @@ DATA PROCESSING INFO
   - FILTERING CRITERIA = data[3]==Evidence at transcript level | data[4]==cell line
   - EVIDENCE CRITERIA = None
 DATA INFO
-  - DOWNLOAD_URL = https://www.dropbox.com/s/us3u516e4vhkuco/HPA_GTEX_RNA_GENE_PROTEIN_EDGES.txt?dl=1
-  - DOWNLOAD_DATE = 04/23/2020
+  - DOWNLOAD_URL = https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/processed_data/HPA_GTEX_RNA_GENE_PROTEIN_EDGES.txt
+  - DOWNLOAD_DATE = 01/10/2021
   - FILE_SIZE_IN_BYTES = 20467831
   - DOWNLOADED_FILE_LOCATION = resources/edge_data/rna-cell_HPA_GTEX_RNA_GENE_PROTEIN_EDGES.txt
 
@@ -338,19 +338,19 @@ DATA PROCESSING INFO
   - FILTERING CRITERIA = data[2]==protein-coding
   - EVIDENCE CRITERIA = None
 DATA INFO
-  - DOWNLOAD_URL = https://www.dropbox.com/s/9upjijohu0xzf13/ENSEMBL_TRANSCRIPT_PROTEIN_ONTOLOGY_MAP.txt?dl=1
-  - DOWNLOAD_DATE = 04/23/2020
+  - DOWNLOAD_URL = https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/processed_data/ENSEMBL_TRANSCRIPT_PROTEIN_ONTOLOGY_MAP.txt
+  - DOWNLOAD_DATE = 01/10/2021
   - FILE_SIZE_IN_BYTES = 28862488
   - DOWNLOADED_FILE_LOCATION = resources/edge_data/rna-protein_ENSEMBL_TRANSCRIPT_PROTEIN_ONTOLOGY_MAP.txt
 
 EDGE: variant-disease
 DATA PROCESSING INFO
-  - IDENTIFIER MAPPING = disease (./resources/processed_data/DISEASE_DOID_MAP.txt)
+  - IDENTIFIER MAPPING = disease (./resources/processed_data/DISEASE_MONDO_MAP.txt)
   - FILTERING CRITERIA = data[9]!=-1 | data[16]==GRCh38 | data[8-9]dedupdesc
   - EVIDENCE CRITERIA = data[24] in ["criteria provided, multiple submitters, no conflicts", "reviewed by expert panel", "practice guideline", "criteria provided, single submitter"] | data[7]==1
 DATA INFO
-  - DOWNLOAD_URL = https://www.dropbox.com/s/px3dlywz0q6gb6d/CLINVAR_VARIANT_GENE_DISEASE_PHENOTYPE_EDGES.txt?dl=1
-  - DOWNLOAD_DATE = 04/23/2020
+  - DOWNLOAD_URL = https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/processed_data/CLINVAR_VARIANT_GENE_DISEASE_PHENOTYPE_EDGES.txt
+  - DOWNLOAD_DATE = 01/10/2021
   - FILE_SIZE_IN_BYTES = 1437554430
   - DOWNLOADED_FILE_LOCATION = resources/edge_data/variant-disease_CLINVAR_VARIANT_GENE_DISEASE_PHENOTYPE_EDGES.txt
 
@@ -360,8 +360,8 @@ DATA PROCESSING INFO
   - FILTERING CRITERIA = data[9]!=-1 | data[16]==GRCh38 | data[8-9]dedupdesc
   - EVIDENCE CRITERIA = data[24] in ["criteria provided, multiple submitters, no conflicts", "reviewed by expert panel", "practice guideline", "criteria provided, single submitter"]
 DATA INFO
-  - DOWNLOAD_URL = https://www.dropbox.com/s/px3dlywz0q6gb6d/CLINVAR_VARIANT_GENE_DISEASE_PHENOTYPE_EDGES.txt?dl=1
-  - DOWNLOAD_DATE = 04/23/2020
+  - DOWNLOAD_URL = https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/processed_data/CLINVAR_VARIANT_GENE_DISEASE_PHENOTYPE_EDGES.txt
+  - DOWNLOAD_DATE = 01/10/2021
   - FILE_SIZE_IN_BYTES = 1437554430
   - DOWNLOADED_FILE_LOCATION = resources/edge_data/variant-gene_CLINVAR_VARIANT_GENE_DISEASE_PHENOTYPE_EDGES.txt
 
@@ -371,8 +371,8 @@ DATA PROCESSING INFO
   - FILTERING CRITERIA = data[9]!=-1 | data[16]==GRCh38 | data[8-9]dedupdesc
   - EVIDENCE CRITERIA = data[24] in ["criteria provided, multiple submitters, no conflicts", "reviewed by expert panel", "practice guideline", "criteria provided, single submitter"] | data[7]==1
 DATA INFO
-  - DOWNLOAD_URL = https://www.dropbox.com/s/px3dlywz0q6gb6d/CLINVAR_VARIANT_GENE_DISEASE_PHENOTYPE_EDGES.txt?dl=1
-  - DOWNLOAD_DATE = 04/23/2020
+  - DOWNLOAD_URL = https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/processed_data/CLINVAR_VARIANT_GENE_DISEASE_PHENOTYPE_EDGES.txt
+  - DOWNLOAD_DATE = 01/10/2021
   - FILE_SIZE_IN_BYTES = 1437554430
   - DOWNLOADED_FILE_LOCATION = resources/edge_data/variant-phenotype_CLINVAR_VARIANT_GENE_DISEASE_PHENOTYPE_EDGES.txt
 
diff --git a/resources/edge_source_list.txt b/resources/edge_source_list.txt
index 0d9537b85..84f553cf9 100644
--- a/resources/edge_source_list.txt
+++ b/resources/edge_source_list.txt
@@ -1,34 +1,34 @@
-chemical-disease, http://ctdbase.org/reports/CTD_chemicals_diseases.tsv.gz
-chemical-gene, http://ctdbase.org/reports/CTD_chem_gene_ixns.tsv.gz
-chemical-gobp, http://ctdbase.org/reports/CTD_chem_go_enriched.tsv.gz
-chemical-gocc, http://ctdbase.org/reports/CTD_chem_go_enriched.tsv.gz
-chemical-gomf, http://ctdbase.org/reports/CTD_chem_go_enriched.tsv.gz
-chemical-pathway, https://reactome.org/download/current/ChEBI2Reactome_All_Levels.txt
-chemical-phenotype, http://ctdbase.org/reports/CTD_chemicals_diseases.tsv.gz
-chemical-protein, http://ctdbase.org/reports/CTD_chem_gene_ixns.tsv.gz
-chemical-rna, http://ctdbase.org/reports/CTD_chem_gene_ixns.tsv.gz
-disease-phenotype, http://compbio.charite.de/jenkins/job/hpo.annotations/lastStableBuild/artifact/misc/phenotype_annotation.tab
-gene-disease, https://www.disgenet.org/static/disgenet_ap1/files/downloads/curated_gene_disease_associations.tsv.gz
-gene-gene, http://genemania.org/data/current/Homo_sapiens.COMBINED/COMBINED.DEFAULT_NETWORKS.BP_COMBINING.txt
-gene-pathway, http://ctdbase.org/reports/CTD_genes_pathways.tsv.gz
-gene-phenotype, https://www.disgenet.org/static/disgenet_ap1/files/downloads/curated_gene_disease_associations.tsv.gz
-gene-protein, https://www.dropbox.com/s/e5x7rq4kc2kfq49/ENTREZ_GENE_PRO_ONTOLOGY_MAP.txt?dl=1
-gene-rna, https://www.dropbox.com/s/jxm9v7qfwm2b6ot/ENTREZ_GENE_ENSEMBL_TRANSCRIPT_MAP.txt?dl=1
-gobp-pathway, https://reactome.org/download/current/gene_association.reactome.gz
-pathway-gocc, https://reactome.org/download/current/gene_association.reactome.gz
-pathway-gomf, https://reactome.org/download/current/gene_association.reactome.gz
-protein-anatomy, https://www.dropbox.com/s/us3u516e4vhkuco/HPA_GTEX_RNA_GENE_PROTEIN_EDGES.txt?dl=1
-protein-catalyst, https://www.dropbox.com/s/w4lh6k9wbo5qkw0/UNIPROT_PROTEIN_CATALYST.txt?dl=1
-protein-cell, https://www.dropbox.com/s/us3u516e4vhkuco/HPA_GTEX_RNA_GENE_PROTEIN_EDGES.txt?dl=1
-protein-cofactor, https://www.dropbox.com/s/xjtljf21eqign73/UNIPROT_PROTEIN_COFACTOR.txt?dl=1
-protein-gobp, http://geneontology.org/gene-associations/goa_human.gaf.gz
-protein-gocc, http://geneontology.org/gene-associations/goa_human.gaf.gz
-protein-gomf, http://geneontology.org/gene-associations/goa_human.gaf.gz
-protein-pathway, https://reactome.org/download/current/UniProt2Reactome_All_Levels.txt
-protein-protein, https://stringdb-static.org/download/protein.links.v11.0/9606.protein.links.v11.0.txt.gz
-rna-anatomy, https://www.dropbox.com/s/us3u516e4vhkuco/HPA_GTEX_RNA_GENE_PROTEIN_EDGES.txt?dl=1
-rna-cell, https://www.dropbox.com/s/us3u516e4vhkuco/HPA_GTEX_RNA_GENE_PROTEIN_EDGES.txt?dl=1
-rna-protein, https://www.dropbox.com/s/9upjijohu0xzf13/ENSEMBL_TRANSCRIPT_PROTEIN_ONTOLOGY_MAP.txt?dl=1
-variant-disease, https://www.dropbox.com/s/px3dlywz0q6gb6d/CLINVAR_VARIANT_GENE_DISEASE_PHENOTYPE_EDGES.txt?dl=1
-variant-gene, https://www.dropbox.com/s/px3dlywz0q6gb6d/CLINVAR_VARIANT_GENE_DISEASE_PHENOTYPE_EDGES.txt?dl=1
-variant-phenotype, https://www.dropbox.com/s/px3dlywz0q6gb6d/CLINVAR_VARIANT_GENE_DISEASE_PHENOTYPE_EDGES.txt?dl=1
\ No newline at end of file
+chemical-disease, https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/original_data/CTD_chemicals_diseases.tsv
+chemical-gene, https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/original_data/CTD_chem_gene_ixns.tsv
+chemical-gobp, https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/original_data/CTD_chem_go_enriched.tsv
+chemical-gocc, https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/original_data/CTD_chem_go_enriched.tsv
+chemical-gomf, https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/original_data/CTD_chem_go_enriched.tsv
+chemical-pathway, https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/original_data/ChEBI2Reactome_All_Levels.txt
+chemical-phenotype, https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/original_data/CTD_chemicals_diseases.tsv
+chemical-protein, https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/original_data/CTD_chem_gene_ixns.tsv
+chemical-rna, https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/original_data/CTD_chem_gene_ixns.tsv
+disease-phenotype, https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/original_data/phenotype_annotation.tab
+gene-disease, https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/original_data/curated_gene_disease_associations.tsv
+gene-gene, https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/original_data/COMBINED.DEFAULT_NETWORKS.BP_COMBINING.txt
+gene-pathway, https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/original_data/CTD_genes_pathways.tsv
+gene-phenotype, https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/original_data/curated_gene_disease_associations.tsv
+gene-protein, https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/processed_data/ENTREZ_GENE_PRO_ONTOLOGY_MAP.txt
+gene-rna, https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/processed_data/ENTREZ_GENE_ENSEMBL_TRANSCRIPT_MAP.txt
+gobp-pathway, https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/original_data/gene_association.reactome
+pathway-gocc, https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/original_data/gene_association.reactome
+pathway-gomf, https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/original_data/gene_association.reactome
+protein-anatomy, https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/processed_data/HPA_GTEX_RNA_GENE_PROTEIN_EDGES.txt
+protein-catalyst, https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/processed_data/UNIPROT_PROTEIN_CATALYST.txt
+protein-cell, https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/processed_data/HPA_GTEX_RNA_GENE_PROTEIN_EDGES.txt
+protein-cofactor, https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/processed_data/UNIPROT_PROTEIN_COFACTOR.txt
+protein-gobp, https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/original_data/goa_human.gaf
+protein-gocc, https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/original_data/goa_human.gaf
+protein-gomf, https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/original_data/goa_human.gaf
+protein-pathway, https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/original_data/UniProt2Reactome_All_Levels.txt
+protein-protein, https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/original_data/9606.protein.links.v11.0.txt
+rna-anatomy, https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/processed_data/HPA_GTEX_RNA_GENE_PROTEIN_EDGES.txt
+rna-cell, https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/processed_data/HPA_GTEX_RNA_GENE_PROTEIN_EDGES.txt
+rna-protein, https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/processed_data/ENSEMBL_TRANSCRIPT_PROTEIN_ONTOLOGY_MAP.txt
+variant-disease, https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/processed_data/CLINVAR_VARIANT_GENE_DISEASE_PHENOTYPE_EDGES.txt
+variant-gene, https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/processed_data/CLINVAR_VARIANT_GENE_DISEASE_PHENOTYPE_EDGES.txt
+variant-phenotype, https://storage.googleapis.com/pheknowlator/release_v2.0.0/current_build/data/processed_data/CLINVAR_VARIANT_GENE_DISEASE_PHENOTYPE_EDGES.txt
\ No newline at end of file
diff --git a/resources/node_data/README.md b/resources/node_data/README.md
index 14b7eeba0..03be9e1f3 100644
--- a/resources/node_data/README.md
+++ b/resources/node_data/README.md
@@ -8,34 +8,44 @@
 
 ___
 
-### Purpose
-The knowledge graph is built without inclusion of instance node metadata (i.e. labels, descriptions or definitions, and, synonyms). If you'd like to create node metadata, please see the Jupyter Notebook referenced above and run the code chunks listed under the **Gather Node Metadata Data** section. These code chunks should only be run once the edge lists have been created, but before the knowledge graph is constructed.
+**Purpose:** The knowledge graph can be built with or without the inclusion of node and relation metadata (i.e. labels, descriptions or definitions, and synonyms). If you'd like to create and use node metadata, please run the [`Data_Preparation.ipynb`](https://github.com/callahantiff/PheKnowLator/blob/master/Data_Preparation.ipynb) Jupyter Notebook and run the code chunks listed under the **INSTANCE AND/OR SUBCLASS (NON-ONTOLOGY CLASS) METADATA** section. These code chunks should be run before the knowledge graph is constructed. For more details on what these data sources are and how they are created, please see the `node_data` [`README.md`](https://github.com/callahantiff/PheKnowLator/blob/master/resources/node_data/README.md).
+
+Example structure of the metadata dictionary is shown below:
+
+```python
+{
+    'nodes': {
+        'http://www.ncbi.nlm.nih.gov/gene/1': {
+            'Label': 'A1BG',
+            'Description': "A1BG has locus group protein-coding' and is located on chromosome 19 (19q13.43).",
+            'Synonym': 'HYST2477alpha-1B-glycoprotein|HEL-S-163pA|ABG|A1B|GAB'} ... },
+    'relations': {
+        'http://purl.obolibrary.org/obo/RO_0002533': {
+            'Label': 'sequence atomic unit',
+            'Description': 'Any individual unit of a collection of like units arranged in a linear order',
+            'Synonym': 'None'} ... }
+} 
+```  
 
 <br>
 
-🛑 *<b>ASSUMPTIONS</b>* 🛑  
-**The algorithm makes the following assumptions:**
-- Metadata is used to create a `.txt` document that is output with the knowledge graph. Metadata is not added to the knowledge graph without running the steps described below.  
-- To add metadata to the knowledge graph, set the `add_meatadata_to_kg` flag to `yes` when running building the knowledge graph.  
-- All metadata data sets should be written to `./resources/node_data`.  
-  - There will be one file per edge type and is labeled such that the edge type occurs at the beginning of the file
-   (e.g. `chemical-gene_metadata.txt`).
-- Each metadata file, in addition to containing the primary node identifier (labeled as `ID`), will contain 1 to 3
- additional columns labeled: `Label`, `Description`, and `Synonym`. An example of these data types is shown
-  below for the Ensembl transcript identifier `ENST00000000412`:  
-
-<br>
+🛑 *<b>CONSTRAINTS</b>* 🛑  
+The algorithm makes the following assumptions:
+- If metadata is provided, only those edges with nodes that have metadata will be created; valid edges without metadata will be discarded.  
+- Metadata for all non-ontology nodes and all relations for edges added to the core set of ontologies will be saved as a dictionary in the `./resources/node_data/node_metadata_dict.pkl` repository.
+- For each identifier we try to obtain the following metadata: `Label`, `Description`, and `Synonym`. An example of these data types is shown below for a [`gene`](https://github.com/callahantiff/PheKnowLator/wiki/v2-Data-Sources#ncbi-gene) identifier `5620`:  
 
 | **Metadata Type** | **Definition** | **Metadata**  | 
 | :---: | :--- | :--- | 
 | ID | Node identifiers for instance data sources | `5620` |
 | Label | The primary label or name for the node | `LANCL2` |       
 | Description | A definition or other useful details about the node | `Lanc Like 2` is a `protein-coding` gene that is located on chromosome `7` (map_location: `7p11.2`) |        
-| Synonym | Alternative terms used for a node | `GPR69B`, `TASP`, `lanC-like protein 2`, `G protein-coupled receptor 69B`, `LanC (bacterial lantibiotic synthetase component C)-like 2`, `LanC lantibiotic synthetase component C-like 2`, `testis-specific adriamycin sensitivity protein` |          
+| Synonym | Alternative terms used for a node | `GPR69B`, `TASP`, `lanC-like protein 2`, `G protein-coupled receptor 69B`, `LanC (bacterial lantibiotic synthetase component C)-like 2`, `LanC lantibiotic synthetase component C-like 2`, `testis-specific adriamycin sensitivity protein` |
 
 <br>
 
 #### Metadata + PheKnowLator
+***  
 The metadata will be used to create the following edges in the knowledge graph:  
 - **Label** ➞ node `rdfs:label`  
 - **Description** ➞ node `obo:IAO_0000115` description 
diff --git a/resources/ontologies/ontology_source_metadata.txt b/resources/ontologies/ontology_source_metadata.txt
index ff670f6a7..afcc17874 100644
--- a/resources/ontologies/ontology_source_metadata.txt
+++ b/resources/ontologies/ontology_source_metadata.txt
@@ -1,5 +1,5 @@
 ===================================
-#Mon Jan 04 02:49:10 UTC 2021 
+#Sun Jan 10 07:51:01 UTC 2021 
 ===================================
 
 EDGE: phenotype
@@ -9,8 +9,8 @@ DATA PROCESSING INFO
   - EVIDENCE CRITERIA = None
 DATA INFO
   - DOWNLOAD_URL = http://purl.obolibrary.org/obo/hp.owl
-  - DOWNLOAD_DATE = 01/03/2021
-  - FILE_SIZE_IN_BYTES = 86014000
+  - DOWNLOAD_DATE = 01/10/2021
+  - FILE_SIZE_IN_BYTES = 86049594
   - DOWNLOADED_FILE_LOCATION = resources/ontologies/hp_with_imports.owl
 
 EDGE: go
@@ -20,8 +20,8 @@ DATA PROCESSING INFO
   - EVIDENCE CRITERIA = None
 DATA INFO
   - DOWNLOAD_URL = http://purl.obolibrary.org/obo/go.owl
-  - DOWNLOAD_DATE = 01/03/2021
-  - FILE_SIZE_IN_BYTES = 163696011
+  - DOWNLOAD_DATE = 01/10/2021
+  - FILE_SIZE_IN_BYTES = 158595338
   - DOWNLOADED_FILE_LOCATION = resources/ontologies/go_with_imports.owl
 
 EDGE: disease
@@ -31,8 +31,8 @@ DATA PROCESSING INFO
   - EVIDENCE CRITERIA = None
 DATA INFO
   - DOWNLOAD_URL = http://purl.obolibrary.org/obo/mondo.owl
-  - DOWNLOAD_DATE = 01/03/2021
-  - FILE_SIZE_IN_BYTES = 232558741
+  - DOWNLOAD_DATE = 01/10/2021
+  - FILE_SIZE_IN_BYTES = 231114807
   - DOWNLOADED_FILE_LOCATION = resources/ontologies/mondo_with_imports.owl
 
 EDGE: vaccine
@@ -42,8 +42,8 @@ DATA PROCESSING INFO
   - EVIDENCE CRITERIA = None
 DATA INFO
   - DOWNLOAD_URL = http://purl.obolibrary.org/obo/vo.owl
-  - DOWNLOAD_DATE = 01/03/2021
-  - FILE_SIZE_IN_BYTES = 7866175
+  - DOWNLOAD_DATE = 01/10/2021
+  - FILE_SIZE_IN_BYTES = 8140082
   - DOWNLOADED_FILE_LOCATION = resources/ontologies/vo_with_imports.owl
 
 EDGE: chemical
@@ -53,8 +53,8 @@ DATA PROCESSING INFO
   - EVIDENCE CRITERIA = None
 DATA INFO
   - DOWNLOAD_URL = http://purl.obolibrary.org/obo/chebi.owl
-  - DOWNLOAD_DATE = 01/03/2021
-  - FILE_SIZE_IN_BYTES = 601101705
+  - DOWNLOAD_DATE = 01/10/2021
+  - FILE_SIZE_IN_BYTES = 592367801
   - DOWNLOADED_FILE_LOCATION = resources/ontologies/chebi_with_imports.owl
 
 EDGE: anatomy
@@ -64,8 +64,8 @@ DATA PROCESSING INFO
   - EVIDENCE CRITERIA = None
 DATA INFO
   - DOWNLOAD_URL = http://purl.obolibrary.org/obo/uberon/ext.owl
-  - DOWNLOAD_DATE = 01/03/2021
-  - FILE_SIZE_IN_BYTES = 77022653
+  - DOWNLOAD_DATE = 01/10/2021
+  - FILE_SIZE_IN_BYTES = 75793278
   - DOWNLOADED_FILE_LOCATION = resources/ontologies/ext_with_imports.owl
 
 EDGE: cell
@@ -75,8 +75,8 @@ DATA PROCESSING INFO
   - EVIDENCE CRITERIA = None
 DATA INFO
   - DOWNLOAD_URL = http://purl.obolibrary.org/obo/clo.owl
-  - DOWNLOAD_DATE = 01/03/2021
-  - FILE_SIZE_IN_BYTES = 119273027
+  - DOWNLOAD_DATE = 01/10/2021
+  - FILE_SIZE_IN_BYTES = 122290057
   - DOWNLOADED_FILE_LOCATION = resources/ontologies/clo_with_imports.owl
 
 EDGE: protein
@@ -86,8 +86,8 @@ DATA PROCESSING INFO
   - EVIDENCE CRITERIA = None
 DATA INFO
   - DOWNLOAD_URL = http://purl.obolibrary.org/obo/pr.owl
-  - DOWNLOAD_DATE = 01/03/2021
-  - FILE_SIZE_IN_BYTES = 218003949
+  - DOWNLOAD_DATE = 01/10/2021
+  - FILE_SIZE_IN_BYTES = 217990574
   - DOWNLOADED_FILE_LOCATION = resources/ontologies/pr_with_imports.owl
 
 EDGE: genomic
@@ -97,8 +97,8 @@ DATA PROCESSING INFO
   - EVIDENCE CRITERIA = None
 DATA INFO
   - DOWNLOAD_URL = http://purl.obolibrary.org/obo/so.owl
-  - DOWNLOAD_DATE = 01/03/2021
-  - FILE_SIZE_IN_BYTES = 5148800
+  - DOWNLOAD_DATE = 01/10/2021
+  - FILE_SIZE_IN_BYTES = 4866009
   - DOWNLOADED_FILE_LOCATION = resources/ontologies/so_with_imports.owl
 
 EDGE: pathway
@@ -108,8 +108,8 @@ DATA PROCESSING INFO
   - EVIDENCE CRITERIA = None
 DATA INFO
   - DOWNLOAD_URL = http://purl.obolibrary.org/obo/pw.owl
-  - DOWNLOAD_DATE = 01/03/2021
-  - FILE_SIZE_IN_BYTES = 4965358
+  - DOWNLOAD_DATE = 01/10/2021
+  - FILE_SIZE_IN_BYTES = 4937325
   - DOWNLOADED_FILE_LOCATION = resources/ontologies/pw_with_imports.owl
 
 EDGE: relation
@@ -119,7 +119,7 @@ DATA PROCESSING INFO
   - EVIDENCE CRITERIA = None
 DATA INFO
   - DOWNLOAD_URL = http://purl.obolibrary.org/obo/ro.owl
-  - DOWNLOAD_DATE = 01/03/2021
-  - FILE_SIZE_IN_BYTES = 854334
+  - DOWNLOAD_DATE = 01/10/2021
+  - FILE_SIZE_IN_BYTES = 845100
   - DOWNLOADED_FILE_LOCATION = resources/ontologies/ro_with_imports.owl
 
diff --git a/resources/owl_decoding/README.md b/resources/owl_decoding/README.md
index c7c28bdae..c375a3fe4 100644
--- a/resources/owl_decoding/README.md
+++ b/resources/owl_decoding/README.md
@@ -10,35 +10,10 @@ ____
 ### Purpose
 The knowledge graph can be built with or without the inclusion of edges that contain OWL Semantics. The method we use to decode OWL semantics is called OWL-NETS. 
 
-OWL-NETS (NEtwork Transformation for Statistical learning) is a computational method that reversibly abstracts Web Ontology Language (OWL)-encoded biomedical knowledge into a more biologically meaningful network representation. OWL-NETS generates semantically rich knowledge graphs that contain heterogeneous nodes and edges and can be used for tasks which do not require OWL semantics. For additional information on this method and how it works, please see the above Wiki.
+OWL-NETS (NEtwork Transformation for Statistical learning) is a computational method that reversibly abstracts Web 
+Ontology Language (OWL)-encoded biomedical knowledge into a more biologically meaningful network representation. 
+OWL-NETS generates semantically rich knowledge graphs that contain heterogeneous nodes and edges and can be used for 
+tasks which do not require OWL semantics. For additional information on this method and how it works, please see the 
+above **[`Wiki`](https://github.com/callahantiff/PheKnowLator/wiki/OWL-NETS-2.0)**.
  
 > Callahan TJ, Baumgartner Jr WA, Bada M, Stefanski AL, Tripodi I, White EK, Hunter LE. OWL-NETS: Transforming OWL representations for improved network inference. [PMID:29218876](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC5737627/).
-
-<br>
-
-_____
-
-### Input Requirements
-In order to decode the OWL semantics in a knowledge graph, the algorithm requires a single `.txt` file that contains all `owl:Property` types that should be kept when running `OWL-NETS`. An example of the content included in this file is shown below:  
-```txt
-http://purl.obolibrary.org/obo/activates
-http://purl.obolibrary.org/obo/BFO_0000050
-http://purl.obolibrary.org/obo/BSPO_0015202
-http://purl.obolibrary.org/obo/chebi#has_functional_parent
-http://purl.obolibrary.org/obo/CLO_0054409
-http://purl.obolibrary.org/obo/core#connected_to
-http://purl.obolibrary.org/obo/RO_0000053
-```
-
-<br>
-
-_NOTE._ This file should contain all relations included in the [`resource_info.txt`](https://github.com/callahantiff/PheKnowLator/blob/master/resources/resource_info.txt) file.
-
-<br>
-
-_____
-
-
-🛑 *<b>ASSUMPTIONS</b>* 🛑  
-**The algorithm makes the following assumptions:**
-- A `.txt` file containing the `owl:Property` types has been placed in the `./resources/owl_decoding/` directory.   


Test patch:
diff --git a/tests/data/Master_Edge_List_Dict.json b/tests/data/Master_Edge_List_Dict.json
index b0db39fb5..c48acda3d 100644
--- a/tests/data/Master_Edge_List_Dict.json
+++ b/tests/data/Master_Edge_List_Dict.json
@@ -1,1 +1,1 @@
-{"chemical-disease": {"source_labels": ":;MESH_;", "data_type": "class-class", "edge_relation": "RO_0002606", "uri": ["http://purl.obolibrary.org/obo/", "http://purl.obolibrary.org/obo/"], "delimiter": "t", "column_idx": "1;4", "identifier_maps": "0:/Users/tiffanycallahan/Dropbox/GraduateSchool/PhD/LabWork/PheKnowLator/tests/data/MESH_CHEBI_MAP.txt;1:/Users/tiffanycallahan/Dropbox/GraduateSchool/PhD/LabWork/PheKnowLator/tests/data/DISEASE_DOID_MAP.txt", "evidence_criteria": "5;!=;", "filter_criteria": "None", "edge_list": []}, "gene-disease": {"source_labels": ";;", "data_type": "instance-class", "edge_relation": "RO_0003302", "uri": ["http://purl.uniprot.org/geneid/", "http://purl.obolibrary.org/obo/"], "delimiter": "t", "column_idx": "0;4", "identifier_maps": "1:/Users/tiffanycallahan/Dropbox/GraduateSchool/PhD/LabWork/PheKnowLator/tests/data/DISEASE_DOID_MAP.txt", "evidence_criteria": "10;>=;0.70", "filter_criteria": "None", "edge_list": [["19", "DOID_1936"], ["19", "DOID_2348"], ["19", "DOID_2349"], ["19", "DOID_3393"], ["19", "DOID_5162"]]}}
\ No newline at end of file
+{"chemical-disease": {"source_labels": ":;MESH_;", "data_type": "class-class", "edge_relation": "RO_0002606", "uri": ["http://purl.obolibrary.org/obo/", "http://purl.obolibrary.org/obo/"], "delimiter": "t", "column_idx": "1;4", "identifier_maps": "0:/Users/tiffanycallahan/Dropbox/GraduateSchool/PhD/LabWork/PheKnowLator/tests/data/MESH_CHEBI_MAP.txt;1:/Users/tiffanycallahan/Dropbox/GraduateSchool/PhD/LabWork/PheKnowLator/tests/data/DISEASE_DOID_MAP.txt", "evidence_criteria": "5;!=;", "filter_criteria": "None", "edge_list": []}, "gene-disease": {"source_labels": ";;", "data_type": "entity-class", "edge_relation": "RO_0003302", "uri": ["http://purl.uniprot.org/geneid/", "http://purl.obolibrary.org/obo/"], "delimiter": "t", "column_idx": "0;4", "identifier_maps": "1:/Users/tiffanycallahan/Dropbox/GraduateSchool/PhD/LabWork/PheKnowLator/tests/data/DISEASE_DOID_MAP.txt", "evidence_criteria": "10;>=;0.70", "filter_criteria": "None", "edge_list": [["19", "DOID_1936"], ["19", "DOID_2348"], ["19", "DOID_2349"], ["19", "DOID_3393"], ["19", "DOID_5162"]]}, "entity_namespaces": {"gene": "http://purl.uniprot.org/geneid/"}}
\ No newline at end of file
diff --git a/tests/data/OWL_NETS_Property_Types.txt b/tests/data/OWL_NETS_Property_Types.txt
deleted file mode 100644
index 7fc423a87..000000000
--- a/tests/data/OWL_NETS_Property_Types.txt
+++ /dev/null
@@ -1,51 +0,0 @@
-http://purl.obolibrary.org/obo/so#member_of
-http://purl.obolibrary.org/obo/so#evidence_for_feature
-http://purl.obolibrary.org/obo/so#trans_spliced_from
-http://purl.obolibrary.org/obo/so#homologous_to
-http://purl.obolibrary.org/obo/so#transcribed_from
-http://purl.obolibrary.org/obo/so#part_of
-http://purl.obolibrary.org/obo/so#partial_evidence_for_feature
-http://purl.obolibrary.org/obo/so#variant_of
-http://purl.obolibrary.org/obo/so#orthologous_to
-http://purl.obolibrary.org/obo/so#started_by
-http://purl.obolibrary.org/obo/so#overlaps
-http://purl.obolibrary.org/obo/so#guided_by
-http://purl.obolibrary.org/obo/so#disconnected_from
-http://purl.obolibrary.org/obo/so#trans_spliced_to
-http://purl.obolibrary.org/obo/so#non_functional_homolog_of
-http://purl.obolibrary.org/obo/so#starts
-http://purl.obolibrary.org/obo/so#processed_from
-http://purl.obolibrary.org/obo/so#contains
-http://purl.obolibrary.org/obo/so#complete_evidence_for_feature
-http://purl.obolibrary.org/obo/so#contained_by
-http://purl.obolibrary.org/obo/so#paralogous_to
-http://purl.obolibrary.org/obo/so#similar_to
-http://purl.obolibrary.org/obo/so#has_origin
-http://purl.obolibrary.org/obo/so#connects_on
-http://purl.obolibrary.org/obo/so#exemplar_of
-http://purl.obolibrary.org/obo/so#is_consecutive_sequence_of
-http://purl.obolibrary.org/obo/so#has_quality
-http://purl.obolibrary.org/obo/so#lost
-http://purl.obolibrary.org/obo/so#genome_of
-http://purl.obolibrary.org/obo/so#transcribed_to
-http://purl.obolibrary.org/obo/so#finishes
-http://purl.obolibrary.org/obo/so#adjacent_to
-http://purl.obolibrary.org/obo/so#sequence_of
-http://purl.obolibrary.org/obo/so#translates_to
-http://purl.obolibrary.org/obo/so#translation_of
-http://purl.obolibrary.org/obo/so#has_integral_part
-http://purl.obolibrary.org/obo/so#guides
-http://purl.obolibrary.org/obo/so#derives_from
-http://purl.obolibrary.org/obo/so#processed_into
-http://purl.obolibrary.org/obo/so#edited_to
-http://purl.obolibrary.org/obo/so#integral_part_of
-http://purl.obolibrary.org/obo/so#position_of
-http://purl.obolibrary.org/obo/so#associated_with
-http://purl.obolibrary.org/obo/so#recombined_from
-http://purl.obolibrary.org/obo/so#maximally_overlaps
-http://purl.obolibrary.org/obo/so#has_part
-http://purl.obolibrary.org/obo/so#recombined_to
-http://purl.obolibrary.org/obo/so#finished_by
-http://purl.obolibrary.org/obo/so#gained
-http://purl.obolibrary.org/obo/so#edited_from
-http://www.w3.org/2000/01/rdf-schema#subClassOf
\ No newline at end of file
diff --git a/tests/data/node_data/gene-gene_GENE_METADATA.txt b/tests/data/node_data/gene-gene_GENE_METADATA.txt
deleted file mode 100644
index 707fb3cb0..000000000
--- a/tests/data/node_data/gene-gene_GENE_METADATA.txt
+++ /dev/null
@@ -1,16 +0,0 @@
-ID	Label	Description	Synonym
-2	A2M	A2M has locus group 'protein-coding' and is located on chromosome 12 (map_location: 12p13.31).	FWP007|C3 and PZP-like alpha-2-macroglobulin domain-containing protein 5|A2MD|CPAMD5|alpha-2-M|S863-7alpha-2-macroglobulin
-9	NAT1	NAT1 has locus group 'protein-coding' and is located on chromosome 8 (map_location: 8p22).	monomorphic arylamine N-acetyltransferase|NAT-1|AAC1|MNAT|N-acetyltransferase type 1|N-acetyltransferase 1 (arylamine N-acetyltransferase)|arylamide acetylase 1|NATIarylamine N-acetyltransferase 1
-10	NAT2	NAT2 has locus group 'protein-coding' and is located on chromosome 8 (map_location: 8p22).	PNATarylamine N-acetyltransferase 2|NAT-2|AAC2|arylamide acetylase 2|N-acetyltransferase 2 (arylamine N-acetyltransferase)|N-acetyltransferase type 2
-1080	CFTR	CFTR has locus group 'protein-coding' and is located on chromosome 7 (map_location: 7q31.2).	"cAMP-dependent chloride channel|MRP7|TNR-CFTR|dJ760C5.1cystic fibrosis transmembrane conductance regulator|cystic fibrosis transmembrane conductance regulator (ATP-binding cassette sub-family C, member 7)|CF|ABC35|ABCC7|cystic fibrosis transmembrane conductance regulating|channel conductance-controlling ATPase|CFTR/MRP"
-1962	EHHADH	EHHADH has locus group 'protein-coding' and is located on chromosome 3 (map_location: 3q27.2).	"enoyl-Coenzyme A, hydratase/3-hydroxyacyl Coenzyme A dehydrogenase|LBP|L-3-hydroxyacyl-CoA dehydrogenase|3,2-trans-enoyl-CoA isomerase|LBFP|PBE|enoyl-CoA, hydratase/3-hydroxyacyl CoA dehydrogenase|L-PBE|PBFEperoxisomal bifunctional enzyme|peroxisomal enoyl-CoA hydratase|L-bifunctional protein, peroxisomal|FRTS3|ECHD"
-2729	GCLC	GCLC has locus group 'protein-coding' and is located on chromosome 6 (map_location: 6p12.1).	gamma-ECS|GCS|GCS heavy chain|gamma-glutamylcysteine synthetase|GLCLCglutamate--cysteine ligase catalytic subunit|GLCL|GCL
-3075	CFH	CFH has locus group 'protein-coding' and is located on chromosome 1 (map_location: 1q31.3).	factor H-like 1|FHL1|FH|HF|beta-1-H-globulin|AHUS1|ARMS1|H factor 2 (complement)|HF2|H factor 1 (complement)|HF1|HUScomplement factor H|beta-1H|AMBP1|adrenomedullin binding protein|factor H|CFHL3|age-related maculopathy susceptibility 1|ARMD4
-4267	CD99	CD99 has locus group 'protein-coding' and is located on chromosome X|Y (map_location: Xp22.33 and Yp11.2).	"MIC2X|MIC2Y|MIC2 (monoclonal antibody 12E7)|HBA71|antigen identified by monoclonal antibodies 12E7, F21 and O13|T-cell surface glycoprotein E2|cell surface antigen HBA-71|cell surface antigen 12E7|E2 antigen|surface antigen MIC2|MSK5XCD99 antigen|cell surface antigen O13|antigen identified by monoclonal 12E7, Y homolog|MIC2"
-4800	NFYA	NFYA has locus group 'protein-coding' and is located on chromosome 6 (map_location: 6p21.1).	"CBF-A|CBF-B|CAAT-box DNA binding protein subunit A|nuclear transcription factor Y, alpha|NF-YAnuclear transcription factor Y subunit alpha|Transcription factor NF-Y, A subunit|nuclear transcription factor Y subunit A|HAP2 CCAAT-binding protein|HAP2|CCAAT-binding transcription factor subunit B"
-5096	PCCB	PCCB has locus group 'protein-coding' and is located on chromosome 3 (map_location: 3q22.3).	"propanoyl-CoA:carbon dioxide ligase subunit beta|propionyl Coenzyme A carboxylase, beta polypeptide|propionyl-CoA carboxylase beta chain, mitochondrial|PCCase subunit beta|propionyl-CoA carboxylase beta subunit|propionyl CoA carboxylase, beta polypeptide"
-6774	STAT3	STAT3 has locus group 'protein-coding' and is located on chromosome 17 (map_location: 17q21.2).	ADMIO|ADMIO1|DNA-binding protein APRF|HIESsignal transducer and activator of transcription 3|APRF|acute-phase response factor
-8754	ADAM9	ADAM9 has locus group 'protein-coding' and is located on chromosome 8 (map_location: 8p11.22).	CORD9|Mltngdisintegrin and metalloproteinase domain-containing protein 9|ADAM metallopeptidase domain 9 (meltrin gamma)|myeloma cell metalloproteinase|MCMP|cellular disintegrin-related protein|metalloprotease/disintegrin/cysteine-rich protein 9|MDC9|cone rod dystrophy 9
-8837	CFLAR	CFLAR has locus group 'protein-coding' and is located on chromosome 2 (map_location: 2q33.1).	c-FLIPR|FADD-like antiapoptotic molecule 1|caspase homolog|CASP8AP1|c-FLIPS|c-FLIP|CLARP|MACH-related inducer of toxicity|FLAME1|FLAME|testis secretory sperm-binding protein Li 233m|cFLIPCASP8 and FADD-like apoptosis regulator|MRIT|c-FLIPL|FLAME-1|usurpin beta|caspase-related inducer of apoptosis|CASH|inhibitor of FLICE|cellular FLICE-like inhibitory protein|Casper|caspase-eight-related protein|FLIP|I-FLICE|caspase-like apoptosis regulatory protein
-10190	TXNDC9	TXNDC9 has locus group 'protein-coding' and is located on chromosome 2 (map_location: 2q11.2).	PHLP3thioredoxin domain-containing protein 9|APACD|protein 1-4|ATP binding protein associated with cell differentiation
-80219	COQ10B	COQ10B has locus group 'protein-coding' and is located on chromosome 2 (map_location: 2q33.1).	"coenzyme Q-binding protein COQ10 homolog B, mitochondrial|coenzyme Q10 homolog B"
\ No newline at end of file
diff --git a/tests/data/node_data/gene-phenotype_GENE_METADATA.txt b/tests/data/node_data/gene-phenotype_GENE_METADATA.txt
deleted file mode 100644
index 9d05de965..000000000
--- a/tests/data/node_data/gene-phenotype_GENE_METADATA.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-ID	Label	Description	Synonym
-2	A2M	A2M has locus group 'protein-coding' and is located on chromosome 12 (map_location: 12p13.31).	FWP007|C3 and PZP-like alpha-2-macroglobulin domain-containing protein 5|A2MD|CPAMD5|alpha-2-M|S863-7alpha-2-macroglobulin
-9	NAT1	NAT1 has locus group 'protein-coding' and is located on chromosome 8 (map_location: 8p22).	monomorphic arylamine N-acetyltransferase|NAT-1|AAC1|MNAT|N-acetyltransferase type 1|N-acetyltransferase 1 (arylamine N-acetyltransferase)|arylamide acetylase 1|NATIarylamine N-acetyltransferase 1
-10	NAT2	NAT2 has locus group 'protein-coding' and is located on chromosome 8 (map_location: 8p22).	PNATarylamine N-acetyltransferase 2|NAT-2|AAC2|arylamide acetylase 2|N-acetyltransferase 2 (arylamine N-acetyltransferase)|N-acetyltransferase type 2
\ No newline at end of file
diff --git a/tests/data/node_data/node_metadata_dict.pkl b/tests/data/node_data/node_metadata_dict.pkl
new file mode 100644
index 000000000..bfb695db6
Binary files /dev/null and b/tests/data/node_data/node_metadata_dict.pkl differ
diff --git a/tests/data/resource_info.txt b/tests/data/resource_info.txt
index 70c1677ec..6d2ad1cd5 100644
--- a/tests/data/resource_info.txt
+++ b/tests/data/resource_info.txt
@@ -1,2 +1,2 @@
 chemical-disease|:;MESH_;|class-class|RO_0002606|http://purl.obolibrary.org/obo/|http://purl.obolibrary.org/obo/|t|1;4|0:MESH_CHEBI_MAP.txt;1:DISEASE_DOID_MAP.txt|5;!=;''|None
-gene-disease|;;|instance-class|RO_0003302|http://purl.uniprot.org/geneid/|http://purl.obolibrary.org/obo/|t|0;4|1:DISEASE_DOID_MAP.txt|10;>=;0.70|None
\ No newline at end of file
+gene-disease|;;|entity-class|RO_0003302|http://purl.uniprot.org/geneid/|http://purl.obolibrary.org/obo/|t|0;4|1:DISEASE_DOID_MAP.txt|10;>=;0.70|None
\ No newline at end of file
diff --git a/tests/test_construction_approaches.py b/tests/test_construction_approaches.py
index 99dccc416..7aaf99d5a 100644
--- a/tests/test_construction_approaches.py
+++ b/tests/test_construction_approaches.py
@@ -180,7 +180,7 @@ def test_maps_node_to_class(self):
 
         return None
 
-    def test_class_edge_constructor_with_inverse(self):
+    def test_subclass_class_edge_constructor_with_inverse(self):
         """Tests the class_edge_constructor method with inverse relations."""
 
         # prepare input vars
@@ -192,14 +192,33 @@ def test_class_edge_constructor_with_inverse(self):
         inverse_relation = URIRef('http://purl.obolibrary.org/obo/RO_0002435')
 
         # add edges
-        edges = self.kg_builder.edge_constructor(node1, node2, relation, inverse_relation)
+        edges = self.kg_builder.subclass_edge_constructor(node1, node2, relation, inverse_relation)
 
         self.assertIsInstance(edges, Tuple)
         self.assertEqual(len(edges), 12)
 
         return None
 
-    def test_class_edge_constructor_without_inverse(self):
+    def test_instance_class_edge_constructor_with_inverse(self):
+        """Tests the class_edge_constructor method with inverse relations."""
+
+        # prepare input vars
+        # nodes
+        node1, node2 = URIRef('https://www.ncbi.nlm.nih.gov/gene/2'), URIRef('https://www.ncbi.nlm.nih.gov/gene/10')
+
+        # relations
+        relation = URIRef('http://purl.obolibrary.org/obo/RO_0002435')
+        inverse_relation = URIRef('http://purl.obolibrary.org/obo/RO_0002435')
+
+        # add edges
+        edges = self.kg_builder.instance_edge_constructor(node1, node2, relation, inverse_relation)
+
+        self.assertIsInstance(edges, Tuple)
+        self.assertEqual(len(edges), 10)
+
+        return None
+
+    def test_subclass_class_edge_constructor_without_inverse(self):
         """Tests the class_edge_constructor method without inverse relations."""
 
         # prepare input vars
@@ -212,13 +231,32 @@ def test_class_edge_constructor_without_inverse(self):
         inverse_relation = None
 
         # add edges
-        edges = self.kg_builder.edge_constructor(node1, node2, relation, inverse_relation)
+        edges = self.kg_builder.subclass_edge_constructor(node1, node2, relation, inverse_relation)
 
         self.assertIsInstance(edges, Tuple)
         self.assertEqual(len(edges), 7)
 
         return None
 
+    def test_instance_class_edge_constructor_without_inverse(self):
+        """Tests the class_edge_constructor method without inverse relations."""
+
+        # prepare input vars
+        # nodes
+        node1, node2 = URIRef('https://www.ncbi.nlm.nih.gov/gene/2'), URIRef('https://www.ncbi.nlm.nih.gov/gene/10')
+
+        # relations
+        relation = URIRef('http://purl.obolibrary.org/obo/RO_0002435')
+        inverse_relation = None
+
+        # add edges
+        edges = self.kg_builder.instance_edge_constructor(node1, node2, relation, inverse_relation)
+
+        self.assertIsInstance(edges, Tuple)
+        self.assertEqual(len(edges), 8)
+
+        return None
+
     def test_subclass_constructor_bad_map(self):
         """Tests the subclass_constructor method for an edge that contains an identifier that is not included in the
         subclass_map_dict."""
@@ -246,7 +284,7 @@ def test_subclass_constructor_bad_map(self):
 
         return None
 
-    def test_subclass_constructor_class_subclass(self):
+    def test_subclass_constructor_class_subclass_without_inverse(self):
         """Tests the subclass_constructor method for edge with class-subclass data type."""
 
         # prepare input vars - NO INVERSE RELATIONS
@@ -263,6 +301,11 @@ def test_subclass_constructor_class_subclass(self):
         self.assertIsInstance(edges, List)
         self.assertEqual(len(edges), 9)
 
+        return None
+
+    def test_subclass_constructor_class_subclass_inverse(self):
+        """Tests the subclass_constructor method for edge with class-subclass data type with inverse relations."""
+
         # prepare input vars - WITH INVERSE RELATIONS
         # edge information
         edge_info = {'n1': 'subclass', 'n2': 'class', 'rel': 'RO_0003302', 'inv_rel': 'RO_0003302',
@@ -279,8 +322,8 @@ def test_subclass_constructor_class_subclass(self):
 
         return None
 
-    def test_subclass_constructor_class_class(self):
-        """Tests the subclass_constructor method for edge with class-class data type."""
+    def test_subclass_constructor_class_class_no_inverse(self):
+        """Tests the subclass_constructor method for edge with class-class data type with relations only."""
 
         # prepare input vars - NO INVERSE
         # edge information
@@ -296,6 +339,11 @@ def test_subclass_constructor_class_class(self):
         self.assertIsInstance(edges, List)
         self.assertEqual(len(edges), 7)
 
+        return None
+
+    def test_subclass_constructor_class_class_inverse(self):
+        """Tests the subclass_constructor method for edge with class-class data type with inverse relations."""
+
         # prepare input vars - WITH INVERSE
         # edge information
         edge_info = {'n1': 'class', 'n2': 'class', 'rel': 'RO_0003302', 'inv_rel': 'RO_0003302',
@@ -312,8 +360,8 @@ def test_subclass_constructor_class_class(self):
 
         return None
 
-    def test_subclass_constructor_subclass_subclass(self):
-        """Tests the subclass_constructor method for edge with subclass-subclass data type."""
+    def test_subclass_constructor_subclass_subclass_relations(self):
+        """Tests the subclass_constructor method for edge with subclass-subclass data type with single relations."""
 
         # prepare input vars - NO INVERSE RELATIONS
         # edge information
@@ -329,6 +377,11 @@ def test_subclass_constructor_subclass_subclass(self):
         self.assertIsInstance(edges, List)
         self.assertEqual(len(edges), 11)
 
+        return None
+
+    def test_subclass_constructor_subclass_subclass_inverse_relations(self):
+        """Tests the subclass_constructor method for edge with subclass-subclass data type with inverse relations."""
+
         # prepare input vars - WITH INVERSE RELATIONS
         # edge information
         edge_info = {'n1': 'subclass', 'n2': 'subclass', 'rel': 'RO_0003302', 'inv_rel': 'RO_0003302',
@@ -345,8 +398,8 @@ def test_subclass_constructor_subclass_subclass(self):
 
         return None
 
-    def test_instance_constructor_class_class(self):
-        """Tests the instance_constructor method for edge with class-class data type."""
+    def test_instance_constructor_class_class_relations(self):
+        """Tests the instance_constructor method for edge with class-class data type with a single relation."""
 
         # prepare input vars -- NO INVERSE RELATIONS
         # edge information
@@ -360,7 +413,12 @@ def test_instance_constructor_class_class(self):
         # check returned results
         self.assertIsInstance(dic, Dict)
         self.assertIsInstance(edges, List)
-        self.assertEqual(len(edges), 7)
+        self.assertEqual(len(edges), 8)
+
+        return None
+
+    def test_instance_constructor_class_class_inverse_relations(self):
+        """Tests the instance_constructor method for edge with class-class data type with inverse relations."""
 
         # prepare input vars -- WITH INVERSE RELATIONS
         # edge information
@@ -374,12 +432,12 @@ def test_instance_constructor_class_class(self):
         # check returned results
         self.assertIsInstance(dic, Dict)
         self.assertIsInstance(edges, List)
-        self.assertEqual(len(edges), 12)
+        self.assertEqual(len(edges), 10)
 
         return None
 
-    def test_instance_constructor_instance_instance(self):
-        """Tests the instance_constructor method for edge with instance-instance data type."""
+    def test_instance_constructor_instance_instance_relations(self):
+        """Tests the instance_constructor method for edge with instance-instance data type with single relations."""
 
         # prepare input vars - NO INVERSE RELATIONS
         # edge information
@@ -395,6 +453,11 @@ def test_instance_constructor_instance_instance(self):
         self.assertIsInstance(edges, List)
         self.assertEqual(len(edges), 8)
 
+        return None
+
+    def test_instance_constructor_instance_instance_inverse_relations(self):
+        """Tests the instance_constructor method for edge with instance-instance data type with inverse relations."""
+
         # prepare input vars - WITH INVERSE RELATIONS
         # edge information
         edge_info = {'n1': 'instance', 'n2': 'instance', 'rel': 'RO_0003302', 'inv_rel': 'RO_0003302',
@@ -411,8 +474,8 @@ def test_instance_constructor_instance_instance(self):
 
         return None
 
-    def test_instance_constructor_instance_class(self):
-        """Tests the instance_constructor method for edge with instance-class data type."""
+    def test_instance_constructor_instance_class_relations(self):
+        """Tests the instance_constructor method for edge with instance-class data type with relations only."""
 
         # prepare input vars - NO INVERSE RELATIONS
         # edge information
@@ -428,6 +491,11 @@ def test_instance_constructor_instance_class(self):
         self.assertIsInstance(edges, List)
         self.assertEqual(len(edges), 8)
 
+        return None
+
+    def test_instance_constructor_instance_class_inverse(self):
+        """Tests the instance_constructor method for edge with instance-class data type with inverse relations."""
+
         # prepare input vars - WITH INVERSE RELATIONS
         # edge information
         edge_info = {'n1': 'instance', 'n2': 'class', 'rel': 'RO_0003302', 'inv_rel': 'RO_0003302',
diff --git a/tests/test_edge_list.py b/tests/test_edge_list.py
index 577ca28bf..d6f02f2d7 100644
--- a/tests/test_edge_list.py
+++ b/tests/test_edge_list.py
@@ -293,6 +293,19 @@ def tests_process_mapping_data(self):
 
         return None
 
+    def tests_gets_entity_namespaces(self):
+        """Tests gets_entity_namespaces method."""
+
+        self.master_edge_list.gets_entity_namespaces()
+
+        # verify results
+        self.assertIn('entity_namespaces', self.master_edge_list.source_info.keys())
+        self.assertIn('gene', self.master_edge_list.source_info['entity_namespaces'].keys())
+        self.assertEqual('http://purl.uniprot.org/geneid/',
+                         self.master_edge_list.source_info['entity_namespaces']['gene'])
+
+        return None
+
     def tests_creates_knowledge_graph_edges(self):
         """Tests creates_knowledge_graph_edges method."""
 
diff --git a/tests/test_kg_utils.py b/tests/test_kg_utils.py
index cdb42855c..d77a1a7c4 100644
--- a/tests/test_kg_utils.py
+++ b/tests/test_kg_utils.py
@@ -1,15 +1,16 @@
 import glob
+import networkx as nx
 import os
 import os.path
 import unittest
 
 from typing import Dict, List, Set
-from rdflib import Graph, URIRef, BNode
+from rdflib import BNode, Graph, Literal, URIRef
 
 from pkt_kg.utils import gets_ontology_statistics, merges_ontologies, ontology_file_formatter, \
-    maps_node_ids_to_integers, adds_edges_to_graph, remove_edges_from_graph, finds_node_type, \
-    converts_rdflib_to_networkx, gets_ontology_classes, gets_deprecated_ontology_classes, gets_object_properties,\
-    gets_ontology_class_dbxrefs, gets_ontology_class_synonyms
+    maps_node_ids_to_integers, adds_edges_to_graph, remove_edges_from_graph, finds_node_type, updates_graph_namespace, \
+    converts_rdflib_to_networkx, gets_ontology_classes, gets_deprecated_ontology_classes, gets_object_properties, \
+    gets_ontology_class_dbxrefs, gets_ontology_class_synonyms, gets_class_ancestors
 
 
 class TestKGUtils(unittest.TestCase):
@@ -197,18 +198,34 @@ def test_finds_node_type(self):
 
         return None
 
+    def test_updates_graph_namespace(self):
+        """tests the updates_graph_namespace method."""
+
+        # test method
+        graph = updates_graph_namespace('phenotype', Graph(), 'http://purl.obolibrary.org/obo/HP_0100443')
+        test_edge = (URIRef('http://purl.obolibrary.org/obo/HP_0100443'),
+                     URIRef('http://www.geneontology.org/formats/oboInOwl#hasOBONamespace'),
+                     Literal('phenotype'))
+        self.assertIsInstance(graph, Graph)
+        self.assertTrue(len(graph) == 1)
+        self.assertIn(test_edge, graph)
+
+        return None
+
     def test_maps_node_ids_to_integers(self):
         """Tests the maps_node_ids_to_integers method."""
 
         # set-up input variables
-        graph = Graph()
-        graph.parse(self.good_ontology_file_location)
+        graph = Graph().parse(self.good_ontology_file_location)
 
         # run method
-        maps_node_ids_to_integers(graph=graph,
-                                  write_location=self.dir_loc,
-                                  output_ints='/so_with_imports_Triples_Integers.txt',
-                                  output_ints_map='/so_with_imports_Triples_Integer_Identifier_Map.json')
+        mapped_dict = maps_node_ids_to_integers(graph=graph,
+                                                write_location=self.dir_loc,
+                                                output_ints='/so_with_imports_Triples_Integers.txt',
+                                                output_ints_map='/so_with_imports_Triples_Integer_Identifier_Map.json')
+
+        # check that a dictionary is returned
+        self.assertIsInstance(mapped_dict, Dict)
 
         # check that files were created
         self.assertTrue(os.path.exists(self.dir_loc + '/so_with_imports_Triples_Integers.txt'))
@@ -225,11 +242,17 @@ def test_maps_node_ids_to_integers(self):
     def test_converts_rdflib_to_networkx(self):
         """Tests the converts_rdflib_to_networkx method."""
 
-        converts_rdflib_to_networkx(write_location=self.dir_loc, full_kg='/so_with_imports', graph=None)
-
         # check that files were created
+        converts_rdflib_to_networkx(write_location=self.dir_loc, full_kg='/so_with_imports', graph=None)
         self.assertTrue(os.path.exists(self.dir_loc + '/so_with_imports_NetworkxMultiDiGraph.gpickle'))
 
+        # load graph and check structure
+        s = URIRef('http://purl.obolibrary.org/obo/SO_0000288')
+        o = URIRef('http://purl.obolibrary.org/obo/SO_0000287')
+        p = URIRef('http://www.w3.org/2000/01/rdf-schema#subClassOf')
+        graph = nx.read_gpickle(self.dir_loc + '/so_with_imports_NetworkxMultiDiGraph.gpickle')
+        self.assertEqual(graph[s][o][p], {'predicate_key': '9cbd482627d217b38eb407d7eba48020', 'weight': 0.0})
+
         # clean up the environment
         os.remove(self.dir_loc + '/so_with_imports_NetworkxMultiDiGraph.gpickle')
 
@@ -316,3 +339,52 @@ def test_gets_ontology_class_dbxrefs(self):
         self.assertEqual(393, len(dbxref_type_dict))
 
         return None
+
+    def test_finds_class_ancestors(self):
+        """Tests the finds_class_ancestors method."""
+
+        # load ontology
+        graph = Graph().parse(self.good_ontology_file_location, format='xml')
+        so_class = {URIRef('http://purl.obolibrary.org/obo/SO_0000348')}
+
+        # get ancestors when a valid class is provided -- class is URIRef
+        ancestors1 = gets_class_ancestors(graph, so_class, so_class)
+        self.assertIsInstance(ancestors1, Set)
+        self.assertEqual(sorted(list(ancestors1)),
+                         sorted(list({'http://purl.obolibrary.org/obo/SO_0000348',
+                                      'http://purl.obolibrary.org/obo/SO_0000400',
+                                      'http://purl.obolibrary.org/obo/SO_0000443'})))
+
+        return None
+
+    def test_finds_class_ancestors_bad_format(self):
+        """Tests the finds_class_ancestors method when badly formatted class_uris are passed."""
+
+        # load ontology
+        graph = Graph().parse(self.good_ontology_file_location, format='xml')
+        so_class = [URIRef('http://purl.obolibrary.org/obo/SO_0000348')]
+
+        # get ancestors when a valid class is provided -- class is not URIRef
+        class_uri = set([str(x).split('/')[-1] for x in so_class])
+        ancestors1 = gets_class_ancestors(graph, class_uri, class_uri)
+        self.assertIsInstance(ancestors1, Set)
+        self.assertEqual(sorted(list(ancestors1)),
+                         sorted(list({'http://purl.obolibrary.org/obo/SO_0000348',
+                                      'http://purl.obolibrary.org/obo/SO_0000400',
+                                      'http://purl.obolibrary.org/obo/SO_0000443'})))
+
+        return None
+
+    def test_finds_class_ancestors_none(self):
+        """Tests the finds_class_ancestors method when an empty set of class uris is passed."""
+
+        # load ontology
+        graph = Graph().parse(self.good_ontology_file_location, format='xml')
+        so_class = [URIRef('http://purl.obolibrary.org/obo/SO_0000348')]
+
+        # get ancestors when no class is provided
+        ancestors2 = gets_class_ancestors(graph, set())
+        self.assertIsInstance(ancestors2, Set)
+        self.assertEqual(ancestors2, set())
+
+        return None
\ No newline at end of file
diff --git a/tests/test_knowledge_graph.py b/tests/test_knowledge_graph.py
index d3d7bc6e5..5500cd17f 100644
--- a/tests/test_knowledge_graph.py
+++ b/tests/test_knowledge_graph.py
@@ -7,10 +7,12 @@
 import shutil
 import unittest
 
+from mock import patch
 from rdflib import Graph, URIRef, BNode
 from rdflib.namespace import OWL, RDF
 from typing import Dict, List
 
+from pkt_kg.__version__ import __version__
 from pkt_kg.knowledge_graph import FullBuild, PartialBuild, PostClosureBuild
 from pkt_kg.metadata import Metadata
 from pkt_kg.utils import gets_object_properties, gets_ontology_classes
@@ -20,7 +22,6 @@ class TestKGBuilder(unittest.TestCase):
     """Class to test the KGBuilder class from the knowledge graph script."""
 
     def setUp(self):
-
         # initialize file location
         current_directory = os.path.dirname(__file__)
         dir_loc = os.path.join(current_directory, 'data')
@@ -38,10 +39,8 @@ def setUp(self):
 
         # copy needed data data
         # node metadata
-        shutil.copyfile(self.dir_loc + '/node_data/gene-phenotype_GENE_METADATA.txt',
-                        self.dir_loc_resources + '/node_data/gene-phenotype_GENE_METADATA.txt')
-        shutil.copyfile(self.dir_loc + '/node_data/gene-gene_GENE_METADATA.txt',
-                        self.dir_loc_resources + '/node_data/gene-gene_GENE_METADATA.txt')
+        shutil.copyfile(self.dir_loc + '/node_data/node_metadata_dict.pkl',
+                        self.dir_loc_resources + '/node_data/node_metadata_dict.pkl')
         # ontology data
         shutil.copyfile(self.dir_loc + '/ontologies/empty_hp_with_imports.owl',
                         self.dir_loc_resources + '/ontologies/hp_with_imports.owl')
@@ -61,54 +60,56 @@ def setUp(self):
         # create edge list
         edge_dict = {"gene-phenotype": {"data_type": "entity-class",
                                         "edge_relation": "RO_0003302",
-                                        "uri": ["https://www.ncbi.nlm.nih.gov/gene/",
+                                        "uri": ["http://www.ncbi.nlm.nih.gov/gene/",
                                                 "http://purl.obolibrary.org/obo/"],
-                                        "edge_list": [["2", "HP_0002511"], ["2", "HP_0000716"],
-                                                      ["2", "HP_0000100"], ["9", "HP_0030955"],
-                                                      ["9", "HP_0009725"], ["9", "HP_0100787"],
-                                                      ["9", "HP_0012125"], ["10", "HP_0009725"],
-                                                      ["10", "HP_0010301"], ["10", "HP_0045005"]]},
+                                        "edge_list": [["2", "SO_0000162"], ["2", "SO_0000196"],
+                                                      ["2", "SO_0000323"], ["9", "SO_0001490"],
+                                                      ["9", "SO_0000301"], ["9", "SO_0001560"],
+                                                      ["9", "SO_0001560"], ["10", "SO_0000444"],
+                                                      ["10", "SO_0002138"], ["10", "SO_0000511"]]},
                      "gene-gene": {"data_type": "entity-entity",
                                    "edge_relation": "RO_0002435",
-                                   "uri": ["https://www.ncbi.nlm.nih.gov/gene/",
-                                           "https://www.ncbi.nlm.nih.gov/gene/"],
-                                   "edge_list": [["3075", "1080"], ["3075", "4267"], ["4800", "10190"],
-                                                 ["4800", "80219"], ["2729", "1962"], ["2729", "5096"],
-                                                 ["8837", "6774"], ["8837", "8754"]]},
+                                   "uri": ["http://www.ncbi.nlm.nih.gov/gene/",
+                                           "http://www.ncbi.nlm.nih.gov/gene/"],
+                                   "edge_list": [["1", "2"], ["2", "3"], ["3", "18"],
+                                                 ["17", "19"], ["4", "17"], ["5", "11"],
+                                                 ["11", "12"], ["4", "5"]]},
                      "disease-disease": {"data_type": "class-class",
                                          "edge_relation": "RO_0002435",
-                                         "uri": ["https://www.ncbi.nlm.nih.gov/gene/",
-                                                 "https://www.ncbi.nlm.nih.gov/gene/"],
+                                         "uri": ["http://www.ncbi.nlm.nih.gov/gene/",
+                                                 "http://www.ncbi.nlm.nih.gov/gene/"],
                                          "edge_list": [["DOID_3075", "DOID_1080"], ["DOID_3075", "DOID_4267"],
                                                        ["DOID_4800", "DOID_10190"], ["DOID_4800", "DOID_80219"],
                                                        ["DOID_2729", "DOID_1962"], ["DOID_2729", "DOID_5096"],
-                                                       ["DOID_8837", "DOID_6774"], ["DOID_8837", "DOID_8754"]]}
+                                                       ["DOID_8837", "DOID_6774"], ["DOID_8837", "DOID_8754"]]},
+                     "entity_namespaces": {"gene": "http://purl.uniprot.org/geneid/"}
                      }
 
         edge_dict_inst = {"gene-phenotype": {"data_type": "entity-class",
                                              "edge_relation": "RO_0003302",
-                                             "uri": ["https://www.ncbi.nlm.nih.gov/gene/",
+                                             "uri": ["http://www.ncbi.nlm.nih.gov/gene/",
                                                      "http://purl.obolibrary.org/obo/"],
-                                             "edge_list": [["2", "HP_0002511"], ["2", "HP_0000716"],
-                                                           ["2", "HP_0000100"], ["9", "HP_0030955"],
-                                                           ["9", "HP_0009725"], ["9", "HP_0100787"],
-                                                           ["9", "HP_0012125"], ["10", "HP_0009725"],
-                                                           ["10", "HP_0010301"], ["10", "HP_0045005"]]},
+                                             "edge_list": [["2", "SO_0000162"], ["2", "SO_0000196"],
+                                                           ["2", "SO_0000323"], ["9", "SO_0001490"],
+                                                           ["9", "SO_0000301"], ["9", "SO_0001560"],
+                                                           ["9", "SO_0001560"], ["10", "SO_0000444"],
+                                                           ["10", "SO_0002138"], ["10", "SO_0000511"]]},
                           "gene-gene": {"data_type": "entity-entity",
                                         "edge_relation": "RO_0002435",
-                                        "uri": ["https://www.ncbi.nlm.nih.gov/gene/",
-                                                "https://www.ncbi.nlm.nih.gov/gene/"],
-                                        "edge_list": [["3075", "1080"], ["3075", "4267"], ["4800", "10190"],
-                                                      ["4800", "80219"], ["2729", "1962"], ["2729", "5096"],
-                                                      ["8837", "6774"], ["8837", "8754"]]},
+                                        "uri": ["http://www.ncbi.nlm.nih.gov/gene/",
+                                                "http://www.ncbi.nlm.nih.gov/gene/"],
+                                        "edge_list": [["1", "2"], ["2", "3"], ["3", "18"],
+                                                      ["17", "19"], ["4", "17"], ["5", "11"],
+                                                      ["11", "12"], ["4", "5"]]},
                           "disease-disease": {"data_type": "class-class",
                                               "edge_relation": "RO_0002435",
-                                              "uri": ["https://www.ncbi.nlm.nih.gov/gene/",
-                                                      "https://www.ncbi.nlm.nih.gov/gene/"],
+                                              "uri": ["http://www.ncbi.nlm.nih.gov/gene/",
+                                                      "http://www.ncbi.nlm.nih.gov/gene/"],
                                               "edge_list": [["DOID_3075", "DOID_1080"], ["DOID_3075", "DOID_4267"],
                                                             ["DOID_4800", "DOID_10190"], ["DOID_4800", "DOID_80219"],
                                                             ["DOID_2729", "DOID_1962"], ["DOID_2729", "DOID_5096"],
-                                                            ["DOID_8837", "DOID_6774"], ["DOID_8837", "DOID_8754"]]}
+                                                            ["DOID_8837", "DOID_6774"], ["DOID_8837", "DOID_8754"]]},
+                          "entity_namespaces": {"gene": "http://purl.uniprot.org/geneid/"}
                           }
 
         # save data
@@ -119,12 +120,9 @@ def setUp(self):
             json.dump(edge_dict_inst, filepath)
 
         # create subclass mapping data
-        subcls_map = {"2": ['SO_0001217'], "9": ['SO_0001217'], "10": ['SO_0001217'], "1080": ['SO_0001217'],
-                      "1962": ['SO_0001217'], "2729": ['SO_0001217'], "3075": ['SO_0001217'],
-                      "4267": ['SO_0001217'],
-                      "4800": ['SO_0001217'], "5096": ['SO_0001217'], "6774": ['SO_0001217'],
-                      "8754": ['SO_0001217'],
-                      "8837": ['SO_0001217'], "10190": ['SO_0001217'], "80219": ['SO_0001217']}
+        subcls_map = {"1": ['SO_0001217'], "2": ['SO_0001217'], "3": ['SO_0001217'], "4": ['SO_0001217'],
+                      "5": ['SO_0001217'], "11": ['SO_0001217'], "12": ['SO_0001217'], "17": ['SO_0001217'],
+                      "18": ['SO_0001217'], "5096": ['SO_0001217'], "6774": ['SO_0001217'], "19": ['SO_0001217']}
 
         # save data
         with open(self.dir_loc_resources + '/construction_approach/subclass_construction_map.pkl', 'wb') as f:
@@ -134,10 +132,14 @@ def setUp(self):
         self.write_location = self.dir_loc_resources + '/knowledge_graphs'
 
         # build 3 different knowledge graphs
-        self.kg_subclass = FullBuild('subclass', 'yes', 'yes', 'yes', self.write_location)
-        self.kg_instance = PartialBuild('instance', 'yes', 'no', 'no', self.write_location)
-        self.kg_instance2 = PartialBuild('instance', 'yes', 'yes', 'yes', self.write_location)
-        self.kg_closure = PostClosureBuild('instance', 'yes', 'yes', 'no', self.write_location)
+        self.kg_subclass = FullBuild(construction='subclass', node_data='yes',
+                                     inverse_relations='yes', decode_owl='yes', write_location=self.write_location)
+        self.kg_instance = PartialBuild(construction='instance', node_data='yes',
+                                        inverse_relations='no', decode_owl='no', write_location=self.write_location)
+        self.kg_instance2 = PartialBuild(construction='instance', node_data='yes',
+                                         inverse_relations='yes', decode_owl='yes', write_location=self.write_location)
+        self.kg_closure = PostClosureBuild(construction='instance', node_data='yes',
+                                           inverse_relations='yes', decode_owl='no', write_location=self.write_location)
 
         # update class attributes for the location of owltools
         dir_loc_owltools = os.path.join(current_directory, 'utils/owltools')
@@ -145,12 +147,15 @@ def setUp(self):
         self.kg_instance.owl_tools = os.path.abspath(dir_loc_owltools)
         self.kg_instance2.owl_tools = os.path.abspath(dir_loc_owltools)
 
+        # get release
+        self.current_release = 'v' + __version__
+
         return None
 
     def test_class_initialization_parameters_version(self):
         """Tests the class initialization parameters for version."""
 
-        self.assertEqual(self.kg_subclass.kg_version, 'v2.0.0')
+        self.assertEqual(self.kg_subclass.kg_version, self.current_release)
 
         return None
 
@@ -237,17 +242,14 @@ def test_class_initialization_parameters_node_metadata_missing(self):
         """Tests the class initialization parameters for node_metadata."""
 
         # remove node metadata
-        gene_phen_data = self.dir_loc_resources + '/node_data/gene-phenotype_GENE_METADATA.txt'
-        gene_gene_data = self.dir_loc_resources + '/node_data/gene-gene_GENE_METADATA.txt'
+        gene_phen_data = self.dir_loc_resources + '/node_data/node_metadata_dict.pkl'
         os.remove(gene_phen_data)
-        os.remove(gene_gene_data)
 
         # test method
         self.assertRaises(TypeError, FullBuild, 'subclass', 'yes', 'yes', 'yes', self.write_location)
 
         # add back deleted data
-        shutil.copyfile(self.dir_loc + '/node_data/gene-phenotype_GENE_METADATA.txt', gene_phen_data)
-        shutil.copyfile(self.dir_loc + '/node_data/gene-gene_GENE_METADATA.txt', gene_gene_data)
+        shutil.copyfile(self.dir_loc + '/node_data/node_metadata_dict.pkl', gene_phen_data)
 
         return None
 
@@ -279,7 +281,7 @@ def test_class_initialization_attributes(self):
 
         self.assertTrue(self.kg_subclass.build == 'full')
         self.assertTrue(self.kg_subclass.construct_approach == 'subclass')
-        self.assertTrue(self.kg_subclass.kg_version == 'v2.0.0')
+        self.assertTrue(self.kg_subclass.kg_version == self.current_release)
         path = os.path.abspath(self.dir_loc_resources + '/knowledge_graphs')
         self.assertTrue(self.kg_subclass.write_location == path)
 
@@ -342,7 +344,7 @@ def test_class_initialization_subclass(self):
         self.assertTrue(self.kg_subclass.construct_approach == 'subclass')
 
         # check filepath and write location for knowledge graph
-        write_file = '/PheKnowLator_v2.0.0_full_subclass_inverseRelations_noOWL.owl'
+        write_file = '/PheKnowLator_' + self.current_release + '_full_subclass_inverseRelations_noOWL.owl'
         self.assertEqual(self.kg_subclass.full_kg, write_file)
 
         return None
@@ -361,7 +363,7 @@ def test_class_initialization_instance(self):
         self.assertTrue(self.kg_instance.construct_approach == 'instance')
 
         # check filepath and write location for knowledge graph
-        write_file = '/PheKnowLator_v2.0.0_partial_instance_relationsOnly_OWL.owl'
+        write_file = '/PheKnowLator_' + self.current_release + '_partial_instance_relationsOnly_OWL.owl'
         self.assertEqual(self.kg_instance.full_kg, write_file)
 
         return None
@@ -488,28 +490,32 @@ def test_creates_knowledge_graph_edges_not_adding_metadata_to_kg(self):
         """Tests the creates_knowledge_graph_edges method without adding node metadata to the KG."""
 
         self.kg_subclass.reverse_relation_processor()
-
         # make sure that kg is empty
-        self.kg_subclass.graph.parse(self.dir_loc + '/ontologies/so_with_imports.owl')
+        self.kg_subclass.graph = Graph().parse(self.dir_loc + '/ontologies/so_with_imports.owl')
         self.kg_subclass.obj_properties = gets_object_properties(self.kg_subclass.graph)
         self.kg_subclass.ont_classes = gets_ontology_classes(self.kg_subclass.graph)
 
-        # make sure to add node_metadata
-        self.kg_subclass.kg_metadata = 'no'
-
+        # make sure to not add node_metadata
+        self.kg_subclass.node_dict, self.kg_subclass.node_data = None, None
         # initialize metadata class
         metadata = Metadata(self.kg_subclass.kg_version, self.kg_subclass.write_location, self.kg_subclass.full_kg,
                             self.kg_subclass.node_data, self.kg_subclass.node_dict)
+        if self.kg_subclass.node_data:
+            metadata.node_metadata_processor()
+            metadata.extracts_class_metadata(self.kg_subclass.graph)
+        self.kg_subclass.node_dict = metadata.node_dict
 
         # test method
-        self.kg_subclass.creates_knowledge_graph_edges(metadata.adds_node_metadata, metadata.adds_ontology_annotations)
+        self.kg_subclass.creates_knowledge_graph_edges(metadata.creates_node_metadata,
+                                                       metadata.adds_ontology_annotations)
 
         # check that edges were added to the graph
         self.assertTrue(len(self.kg_subclass.graph) > 0)
-        self.assertEqual(len(self.kg_subclass.graph), 42327)
+        self.assertEqual(len(self.kg_subclass.graph), 42347)
 
         # check graph was saved
-        self.assertTrue(os.path.exists(self.kg_subclass.write_location + self.kg_subclass.full_kg))
+        f_name = self.kg_subclass.full_kg.replace('_noOWL.owl', '_OWL.owl')
+        self.assertTrue(os.path.exists(self.kg_subclass.write_location + f_name))
 
         return None
 
@@ -517,29 +523,47 @@ def test_creates_knowledge_graph_edges_adding_metadata_to_kg(self):
         """Tests the creates_knowledge_graph_edges method and adds node metadata to the KG."""
 
         self.kg_subclass.reverse_relation_processor()
-
         # make sure that kg is empty
-        self.kg_subclass.graph.parse(self.dir_loc + '/ontologies/so_with_imports.owl')
+        self.kg_subclass.graph = Graph().parse(self.dir_loc + '/ontologies/so_with_imports.owl')
         self.kg_subclass.obj_properties = gets_object_properties(self.kg_subclass.graph)
         self.kg_subclass.ont_classes = gets_ontology_classes(self.kg_subclass.graph)
 
         # make sure to add node_metadata
-        self.kg_subclass.kg_metadata = 'yes'
-
-        # initialize metadata class
         metadata = Metadata(self.kg_subclass.kg_version, self.kg_subclass.write_location, self.kg_subclass.full_kg,
                             self.kg_subclass.node_data, self.kg_subclass.node_dict)
-        metadata.node_metadata_processor()
-
+        if self.kg_subclass.node_data:
+            metadata.node_metadata_processor()
+            metadata.extracts_class_metadata(self.kg_subclass.graph)
+        self.kg_subclass.node_dict = metadata.node_dict
         # test method
-        self.kg_subclass.creates_knowledge_graph_edges(metadata.adds_node_metadata, metadata.adds_ontology_annotations)
-
+        self.kg_subclass.creates_knowledge_graph_edges(metadata.creates_node_metadata,
+                                                       metadata.adds_ontology_annotations)
         # check that edges were added to the graph
         self.assertTrue(len(self.kg_subclass.graph) > 0)
-        self.assertEqual(len(self.kg_subclass.graph), 42477)
-
+        self.assertEqual(len(self.kg_subclass.graph), 42421)
         # check graph was saved
-        self.assertTrue(os.path.exists(self.kg_subclass.write_location + self.kg_subclass.full_kg))
+        f_name = self.kg_subclass.full_kg.replace('_noOWL.owl', '_OWL.owl')
+        self.assertTrue(os.path.exists(self.kg_subclass.write_location + f_name))
+
+        return None
+
+    @patch('builtins.print')
+    def test_gets_edge_statistics(self, mock_print):
+        """Tests the gets_edge_statistics method."""
+
+        # no inverse edges
+        self.kg_subclass.gets_edge_statistics('gene-gene', None, [x for x in range(0, 100)])
+        mock_print.assert_called_with('Unique gene: 8')
+
+        return None
+
+    @patch('builtins.print')
+    def test_gets_edge_statistics_inverse_relations(self, mock_print):
+        """Tests the gets_edge_statistics method when including inverse relations."""
+
+        # no inverse edges
+        self.kg_subclass.gets_edge_statistics('gene-gene', 'RO_0000000', [x for x in range(0, 100)])
+        mock_print.assert_called_with('Unique gene: 8')
 
         return None
 
@@ -555,14 +579,18 @@ def test_creates_knowledge_graph_edges_instance_no_inverse(self):
         # initialize metadata class
         metadata = Metadata(self.kg_instance.kg_version, self.kg_instance.write_location, self.kg_instance.full_kg,
                             self.kg_instance.node_data, self.kg_instance.node_dict)
-        metadata.node_metadata_processor()
+        if self.kg_instance.node_data:
+            metadata.node_metadata_processor()
+            metadata.extracts_class_metadata(self.kg_instance.graph)
+        self.kg_instance.node_dict = metadata.node_dict
 
         # test method
-        self.kg_instance.creates_knowledge_graph_edges(metadata.adds_node_metadata, metadata.adds_ontology_annotations)
+        self.kg_instance.creates_knowledge_graph_edges(metadata.creates_node_metadata,
+                                                       metadata.adds_ontology_annotations)
 
         # check that edges were added to the graph
         self.assertTrue(len(self.kg_instance.graph) > 0)
-        self.assertEqual(len(self.kg_instance.graph), 192)
+        self.assertEqual(len(self.kg_instance.graph), 111)
 
         # check graph was saved
         self.assertTrue(os.path.exists(self.kg_instance.write_location + self.kg_instance.full_kg))
@@ -581,23 +609,28 @@ def test_creates_knowledge_graph_edges_instance_inverse(self):
         # initialize metadata class
         metadata = Metadata(self.kg_instance2.kg_version, self.kg_instance2.write_location, self.kg_instance2.full_kg,
                             self.kg_instance2.node_data, self.kg_instance2.node_dict)
-        metadata.node_metadata_processor()
+        if self.kg_instance2.node_data:
+            metadata.node_metadata_processor()
+            metadata.extracts_class_metadata(self.kg_instance2.graph)
+        self.kg_instance2.node_dict = metadata.node_dict
 
         # test method
-        self.kg_instance2.creates_knowledge_graph_edges(metadata.adds_node_metadata, metadata.adds_ontology_annotations)
+        self.kg_instance2.creates_knowledge_graph_edges(metadata.creates_node_metadata,
+                                                        metadata.adds_ontology_annotations)
 
         # check that edges were added to the graph
         self.assertTrue(len(self.kg_instance2.graph) > 0)
-        self.assertEqual(len(self.kg_instance2.graph), 200)
+        self.assertEqual(len(self.kg_instance2.graph), 118)
 
         # check graph was saved
-        self.assertTrue(os.path.exists(self.kg_instance2.write_location + self.kg_instance2.full_kg))
+        f_name = self.kg_instance2.full_kg.replace('_noOWL.owl', '_OWL.owl')
+        self.assertTrue(os.path.exists(self.kg_instance2.write_location + f_name))
 
         return None
 
     def test_creates_knowledge_graph_edges_adding_metadata_to_kg_bad(self):
         """Tests the creates_knowledge_graph_edges method and adds node metadata to the KG, but also makes sure that a
-        log file is writen for genes that are not in the subclass_map."""
+        log file is written for genes that are not in the subclass_map."""
 
         self.kg_subclass.reverse_relation_processor()
 
@@ -606,17 +639,13 @@ def test_creates_knowledge_graph_edges_adding_metadata_to_kg_bad(self):
         self.kg_subclass.obj_properties = gets_object_properties(self.kg_subclass.graph)
         self.kg_subclass.ont_classes = gets_ontology_classes(self.kg_subclass.graph)
 
-        # make sure to add node_metadata
-        self.kg_subclass.kg_metadata = 'yes'
-
         # initialize metadata class
-        metadata = Metadata(self.kg_subclass.kg_version,
-                            self.kg_subclass.write_location,
-                            self.kg_subclass.full_kg,
-                            self.kg_subclass.node_data,
-                            self.kg_subclass.node_dict)
-
-        metadata.node_metadata_processor()
+        metadata = Metadata(self.kg_subclass.kg_version, self.kg_subclass.write_location, self.kg_subclass.full_kg,
+                            self.kg_subclass.node_data, self.kg_subclass.node_dict)
+        if self.kg_subclass.node_data:
+            metadata.node_metadata_processor()
+            metadata.extracts_class_metadata(self.kg_subclass.graph)
+        self.kg_subclass.node_dict = metadata.node_dict
 
         # alter gene list - adding genes not in the subclass_map dictionary
         self.kg_subclass.edge_dict['gene-gene']['edge_list'] = [["1", "1080"], ["1", "4267"], ["4800", "10190"],
@@ -624,7 +653,8 @@ def test_creates_knowledge_graph_edges_adding_metadata_to_kg_bad(self):
                                                                 ["8837", "6774"], ["8837", "8754"]]
 
         # test method
-        self.kg_subclass.creates_knowledge_graph_edges(metadata.adds_node_metadata, metadata.adds_ontology_annotations)
+        self.kg_subclass.creates_knowledge_graph_edges(metadata.creates_node_metadata,
+                                                       metadata.adds_ontology_annotations)
 
         # check that log file was written out
         log_file = '/construction_approach/subclass_map_missing_node_log.json'
diff --git a/tests/test_knowledge_graph_full.py b/tests/test_knowledge_graph_full.py
index d640deb40..19486b3ef 100644
--- a/tests/test_knowledge_graph_full.py
+++ b/tests/test_knowledge_graph_full.py
@@ -7,7 +7,6 @@
 import shutil
 import unittest
 
-
 from pkt_kg.knowledge_graph import FullBuild
 
 
@@ -31,12 +30,10 @@ def setUp(self):
         os.mkdir(self.dir_loc_resources + '/construction_approach')
         os.mkdir(self.dir_loc_resources + '/owl_decoding')
 
-        # copy needed data
+        # copy needed data data
         # node metadata
-        shutil.copyfile(self.dir_loc + '/node_data/gene-phenotype_GENE_METADATA.txt',
-                        self.dir_loc_resources + '/node_data/gene-phenotype_GENE_METADATA.txt')
-        shutil.copyfile(self.dir_loc + '/node_data/gene-gene_GENE_METADATA.txt',
-                        self.dir_loc_resources + '/node_data/gene-gene_GENE_METADATA.txt')
+        shutil.copyfile(self.dir_loc + '/node_data/node_metadata_dict.pkl',
+                        self.dir_loc_resources + '/node_data/node_metadata_dict.pkl')
         # ontology data
         shutil.copyfile(self.dir_loc + '/ontologies/empty_hp_with_imports.owl',
                         self.dir_loc_resources + '/ontologies/hp_with_imports.owl')
@@ -49,9 +46,6 @@ def setUp(self):
         # inverse relations
         shutil.copyfile(self.dir_loc + '/INVERSE_RELATIONS.txt',
                         self.dir_loc_resources + '/relations_data/INVERSE_RELATIONS.txt')
-        # owl nets properties file
-        shutil.copyfile(self.dir_loc + '/OWL_NETS_Property_Types.txt',
-                        self.dir_loc_resources + '/owl_decoding/OWL_NETS_Property_Types.txt')
         # empty master edges
         shutil.copyfile(self.dir_loc + '/Master_Edge_List_Dict_empty.json',
                         self.dir_loc_resources + '/Master_Edge_List_Dict_empty.json')
@@ -59,20 +53,29 @@ def setUp(self):
         # create edge list
         edge_dict = {"gene-phenotype": {"data_type": "entity-class",
                                         "edge_relation": "RO_0003302",
-                                        "uri": ["https://www.ncbi.nlm.nih.gov/gene/",
+                                        "uri": ["http://www.ncbi.nlm.nih.gov/gene/",
                                                 "http://purl.obolibrary.org/obo/"],
-                                        "edge_list": [["2", "HP_0002511"], ["2", "HP_0000716"],
-                                                      ["2", "HP_0000100"], ["9", "HP_0030955"],
-                                                      ["9", "HP_0009725"], ["9", "HP_0100787"],
-                                                      ["9", "HP_0012125"], ["10", "HP_0009725"],
-                                                      ["10", "HP_0010301"], ["10", "HP_0045005"]]},
+                                        "edge_list": [["2", "SO_0000162"], ["2", "SO_0000196"],
+                                                      ["2", "SO_0000323"], ["9", "SO_0001490"],
+                                                      ["9", "SO_0000301"], ["9", "SO_0001560"],
+                                                      ["9", "SO_0001560"], ["10", "SO_0000444"],
+                                                      ["10", "SO_0002138"], ["10", "SO_0000511"]]},
                      "gene-gene": {"data_type": "entity-entity",
                                    "edge_relation": "RO_0002435",
-                                   "uri": ["https://www.ncbi.nlm.nih.gov/gene/",
-                                           "https://www.ncbi.nlm.nih.gov/gene/"],
-                                   "edge_list": [["3075", "1080"], ["3075", "4267"], ["4800", "10190"],
-                                                 ["4800", "80219"], ["2729", "1962"], ["2729", "5096"],
-                                                 ["8837", "6774"], ["8837", "8754"]]}
+                                   "uri": ["http://www.ncbi.nlm.nih.gov/gene/",
+                                           "http://www.ncbi.nlm.nih.gov/gene/"],
+                                   "edge_list": [["1", "2"], ["2", "3"], ["3", "18"],
+                                                 ["17", "19"], ["4", "17"], ["5", "11"],
+                                                 ["11", "12"], ["4", "5"]]},
+                     "disease-disease": {"data_type": "class-class",
+                                         "edge_relation": "RO_0002435",
+                                         "uri": ["http://www.ncbi.nlm.nih.gov/gene/",
+                                                 "http://www.ncbi.nlm.nih.gov/gene/"],
+                                         "edge_list": [["DOID_3075", "DOID_1080"], ["DOID_3075", "DOID_4267"],
+                                                       ["DOID_4800", "DOID_10190"], ["DOID_4800", "DOID_80219"],
+                                                       ["DOID_2729", "DOID_1962"], ["DOID_2729", "DOID_5096"],
+                                                       ["DOID_8837", "DOID_6774"], ["DOID_8837", "DOID_8754"]]},
+                     "entity_namespaces": {"gene": "http://purl.uniprot.org/geneid/"}
                      }
 
         # save data
@@ -80,12 +83,9 @@ def setUp(self):
             json.dump(edge_dict, filepath)
 
         # create subclass mapping data
-        subcls_map = {"2": ['SO_0001217'], "9": ['SO_0001217'], "10": ['SO_0001217'], "1080": ['SO_0001217'],
-                      "1962": ['SO_0001217'], "2729": ['SO_0001217'], "3075": ['SO_0001217'],
-                      "4267": ['SO_0001217'],
-                      "4800": ['SO_0001217'], "5096": ['SO_0001217'], "6774": ['SO_0001217'],
-                      "8754": ['SO_0001217'],
-                      "8837": ['SO_0001217'], "10190": ['SO_0001217'], "80219": ['SO_0001217']}
+        subcls_map = {"1": ['SO_0001217'], "2": ['SO_0001217'], "3": ['SO_0001217'], "4": ['SO_0001217'],
+                      "5": ['SO_0001217'], "11": ['SO_0001217'], "12": ['SO_0001217'], "17": ['SO_0001217'],
+                      "18": ['SO_0001217'], "5096": ['SO_0001217'], "6774": ['SO_0001217'], "19": ['SO_0001217']}
 
         # save data
         with open(self.dir_loc_resources + '/construction_approach/subclass_construction_map.pkl', 'wb') as f:
@@ -95,7 +95,11 @@ def setUp(self):
         self.write_location = self.dir_loc_resources + '/knowledge_graphs'
 
         # build knowledge graph
-        self.kg = FullBuild('subclass', 'yes', 'yes', 'yes', self.write_location)
+        self.kg = FullBuild(construction='subclass',
+                            node_data='yes',
+                            inverse_relations='yes',
+                            decode_owl='yes',
+                            write_location=self.write_location)
 
         # update class attributes
         dir_loc_owltools = os.path.join(current_directory, 'utils/owltools')
@@ -121,37 +125,44 @@ def test_construct_knowledge_graph(self):
 
         # check for output files
         # kg
-        kg = self.kg.full_kg
-        self.assertTrue(os.path.exists(self.dir_loc_resources + '/knowledge_graphs/' + kg))
+        f_name = self.kg.full_kg.replace('_noOWL.owl', '_OWL.owl')
+        self.assertTrue(os.path.exists(self.dir_loc_resources + '/knowledge_graphs/' + f_name))
 
         # kg - owl
-        kg_owl = 'PheKnowLator_v2.0.0_full_subclass_inverseRelations_noOWL_OWLNETS.nt'
+        kg_owl = self.kg.full_kg.replace('.owl', '_OWLNETS.nt')
         self.assertTrue(os.path.exists(self.dir_loc_resources + '/knowledge_graphs/' + kg_owl))
 
         # kg - nx.multiDiGraph
-        kg_mdg = 'PheKnowLator_v2.0.0_full_subclass_inverseRelations_noOWL_OWLNETS_NetworkxMultiDiGraph.gpickle'
+        kg_mdg = self.kg.full_kg.replace('.owl', '_OWLNETS_NetworkxMultiDiGraph.gpickle')
         self.assertTrue(os.path.exists(self.dir_loc_resources + '/knowledge_graphs/' + kg_mdg))
 
+        # kg - owl-nets decoding dict
+        kg_dict = self.kg.full_kg.replace('.owl', '_OWLNETS_decoding_dict.pkl')
+        self.assertTrue(os.path.exists(self.dir_loc_resources + '/knowledge_graphs/' + kg_dict))
+
         # node metadata
-        meta = 'PheKnowLator_v2.0.0_full_subclass_inverseRelations_noOWL_NodeLabels.txt'
+        meta = self.kg.full_kg.replace('.owl', '_NodeLabels.txt')
         self.assertTrue(os.path.exists(self.dir_loc_resources + '/knowledge_graphs/' + meta))
 
         # edge list - identifiers
-        ids = 'PheKnowLator_v2.0.0_full_subclass_inverseRelations_noOWL_Triples_Identifiers.txt'
+        ids = self.kg.full_kg.replace('.owl', '_Triples_Identifiers.txt')
         self.assertTrue(os.path.exists(self.dir_loc_resources + '/knowledge_graphs/' + ids))
 
         # edge list - integers
-        ints = 'PheKnowLator_v2.0.0_full_subclass_inverseRelations_noOWL_Triples_Integers.txt'
+        ints = self.kg.full_kg.replace('.owl', '_Triples_Integers.txt')
         self.assertTrue(os.path.exists(self.dir_loc_resources + '/knowledge_graphs/' + ints))
 
         # edge list map
-        int_map = 'PheKnowLator_v2.0.0_full_subclass_inverseRelations_noOWL_Triples_Integer_Identifier_Map.json'
+        int_map = self.kg.full_kg.replace('.owl', '_Triples_Integer_Identifier_Map.json')
         self.assertTrue(os.path.exists(self.dir_loc_resources + '/knowledge_graphs/' + int_map))
 
+        # build dict
+        build_log = 'subclass_map_missing_node_log.json'
+        self.assertTrue(os.path.exists(self.dir_loc_resources + '/construction_approach/' + build_log))
+
         return None
 
     def tearDown(self):
-
         # remove resource directory
         shutil.rmtree(self.dir_loc_resources)
 
diff --git a/tests/test_knowledge_graph_partial.py b/tests/test_knowledge_graph_partial.py
index b82985864..035a68b52 100644
--- a/tests/test_knowledge_graph_partial.py
+++ b/tests/test_knowledge_graph_partial.py
@@ -30,17 +30,13 @@ def setUp(self):
         os.mkdir(self.dir_loc_resources + '/ontologies')
         os.mkdir(self.dir_loc_resources + '/construction_approach')
 
-        # copy needed data
+        # copy needed data data
         # node metadata
-        shutil.copyfile(self.dir_loc + '/node_data/gene-phenotype_GENE_METADATA.txt',
-                        self.dir_loc_resources + '/node_data/gene-phenotype_GENE_METADATA.txt')
-        shutil.copyfile(self.dir_loc + '/node_data/gene-gene_GENE_METADATA.txt',
-                        self.dir_loc_resources + '/node_data/gene-gene_GENE_METADATA.txt')
-
+        shutil.copyfile(self.dir_loc + '/node_data/node_metadata_dict.pkl',
+                        self.dir_loc_resources + '/node_data/node_metadata_dict.pkl')
         # ontology data
         shutil.copyfile(self.dir_loc + '/ontologies/empty_hp_with_imports.owl',
                         self.dir_loc_resources + '/ontologies/hp_with_imports.owl')
-
         # merged ontology data
         shutil.copyfile(self.dir_loc + '/ontologies/so_with_imports.owl',
                         self.dir_loc_resources + '/knowledge_graphs/PheKnowLator_MergedOntologies.owl')
@@ -57,20 +53,29 @@ def setUp(self):
         # create edge list
         edge_dict = {"gene-phenotype": {"data_type": "entity-class",
                                         "edge_relation": "RO_0003302",
-                                        "uri": ["https://www.ncbi.nlm.nih.gov/gene/",
+                                        "uri": ["http://www.ncbi.nlm.nih.gov/gene/",
                                                 "http://purl.obolibrary.org/obo/"],
-                                        "edge_list": [["2", "HP_0002511"], ["2", "HP_0000716"],
-                                                      ["2", "HP_0000100"], ["9", "HP_0030955"],
-                                                      ["9", "HP_0009725"], ["9", "HP_0100787"],
-                                                      ["9", "HP_0012125"], ["10", "HP_0009725"],
-                                                      ["10", "HP_0010301"], ["10", "HP_0045005"]]},
+                                        "edge_list": [["2", "SO_0000162"], ["2", "SO_0000196"],
+                                                      ["2", "SO_0000323"], ["9", "SO_0001490"],
+                                                      ["9", "SO_0000301"], ["9", "SO_0001560"],
+                                                      ["9", "SO_0001560"], ["10", "SO_0000444"],
+                                                      ["10", "SO_0002138"], ["10", "SO_0000511"]]},
                      "gene-gene": {"data_type": "entity-entity",
                                    "edge_relation": "RO_0002435",
-                                   "uri": ["https://www.ncbi.nlm.nih.gov/gene/",
-                                           "https://www.ncbi.nlm.nih.gov/gene/"],
-                                   "edge_list": [["3075", "1080"], ["3075", "4267"], ["4800", "10190"],
-                                                 ["4800", "80219"], ["2729", "1962"], ["2729", "5096"],
-                                                 ["8837", "6774"], ["8837", "8754"]]}
+                                   "uri": ["http://www.ncbi.nlm.nih.gov/gene/",
+                                           "http://www.ncbi.nlm.nih.gov/gene/"],
+                                   "edge_list": [["1", "2"], ["2", "3"], ["3", "18"],
+                                                 ["17", "19"], ["4", "17"], ["5", "11"],
+                                                 ["11", "12"], ["4", "5"]]},
+                     "disease-disease": {"data_type": "class-class",
+                                         "edge_relation": "RO_0002435",
+                                         "uri": ["http://www.ncbi.nlm.nih.gov/gene/",
+                                                 "http://www.ncbi.nlm.nih.gov/gene/"],
+                                         "edge_list": [["DOID_3075", "DOID_1080"], ["DOID_3075", "DOID_4267"],
+                                                       ["DOID_4800", "DOID_10190"], ["DOID_4800", "DOID_80219"],
+                                                       ["DOID_2729", "DOID_1962"], ["DOID_2729", "DOID_5096"],
+                                                       ["DOID_8837", "DOID_6774"], ["DOID_8837", "DOID_8754"]]},
+                     "entity_namespaces": {"gene": "http://purl.uniprot.org/geneid/"}
                      }
 
         # save data
@@ -78,12 +83,9 @@ def setUp(self):
             json.dump(edge_dict, filepath)
 
         # create subclass mapping data
-        subcls_map = {"2": ['SO_0001217'], "9": ['SO_0001217'], "10": ['SO_0001217'], "1080": ['SO_0001217'],
-                      "1962": ['SO_0001217'], "2729": ['SO_0001217'], "3075": ['SO_0001217'],
-                      "4267": ['SO_0001217'],
-                      "4800": ['SO_0001217'], "5096": ['SO_0001217'], "6774": ['SO_0001217'],
-                      "8754": ['SO_0001217'],
-                      "8837": ['SO_0001217'], "10190": ['SO_0001217'], "80219": ['SO_0001217']}
+        subcls_map = {"1": ['SO_0001217'], "2": ['SO_0001217'], "3": ['SO_0001217'], "4": ['SO_0001217'],
+                      "5": ['SO_0001217'], "11": ['SO_0001217'], "12": ['SO_0001217'], "17": ['SO_0001217'],
+                      "18": ['SO_0001217'], "5096": ['SO_0001217'], "6774": ['SO_0001217'], "19": ['SO_0001217']}
 
         # save data
         with open(self.dir_loc_resources + '/construction_approach/subclass_construction_map.pkl', 'wb') as f:
@@ -118,8 +120,8 @@ def test_construct_knowledge_graph(self):
         self.kg.construct_knowledge_graph()
 
         # check for output files
-        kg = 'PheKnowLator_v2.0.0_partial_subclass_inverseRelations_noOWL.owl'
-        self.assertTrue(os.path.exists(self.dir_loc_resources + '/knowledge_graphs/' + kg))
+        f_name = self.kg.full_kg.replace('_noOWL.owl', '_OWL.owl')
+        self.assertTrue(os.path.exists(self.dir_loc_resources + '/knowledge_graphs/' + f_name))
 
         return None
 
diff --git a/tests/test_knowledge_graph_post_closure.py b/tests/test_knowledge_graph_post_closure.py
index dbcda01ab..fc9b96889 100644
--- a/tests/test_knowledge_graph_post_closure.py
+++ b/tests/test_knowledge_graph_post_closure.py
@@ -27,37 +27,28 @@ def setUp(self):
         self.dir_loc_resources = os.path.abspath(dir_loc_resources)
         os.mkdir(self.dir_loc_resources)
         os.mkdir(self.dir_loc_resources + '/knowledge_graphs')
-        os.mkdir(self.dir_loc_resources + '/knowledge_graphs/inverse_relations')
         os.mkdir(self.dir_loc_resources + '/relations_data')
         os.mkdir(self.dir_loc_resources + '/node_data')
         os.mkdir(self.dir_loc_resources + '/ontologies')
         os.mkdir(self.dir_loc_resources + '/construction_approach')
         os.mkdir(self.dir_loc_resources + '/owl_decoding')
 
-        # copy needed data
+        # copy needed data data
         # node metadata
-        shutil.copyfile(self.dir_loc + '/node_data/gene-phenotype_GENE_METADATA.txt',
-                        self.dir_loc_resources + '/node_data/gene-phenotype_GENE_METADATA.txt')
-        shutil.copyfile(self.dir_loc + '/node_data/gene-gene_GENE_METADATA.txt',
-                        self.dir_loc_resources + '/node_data/gene-gene_GENE_METADATA.txt')
+        shutil.copyfile(self.dir_loc + '/node_data/node_metadata_dict.pkl',
+                        self.dir_loc_resources + '/node_data/node_metadata_dict.pkl')
         # ontology data
         shutil.copyfile(self.dir_loc + '/ontologies/empty_hp_with_imports.owl',
                         self.dir_loc_resources + '/ontologies/hp_with_imports.owl')
         # merged ontology data
         shutil.copyfile(self.dir_loc + '/ontologies/so_with_imports.owl',
                         self.dir_loc_resources + '/knowledge_graphs/PheKnowLator_MergedOntologies.owl')
-        # closed kg file
-        shutil.copyfile(self.dir_loc + '/ontologies/so_with_imports.owl',
-                        self.dir_loc_resources + '/knowledge_graphs/PheKnowLator_Closed_KG.owl')
         # relations data
         shutil.copyfile(self.dir_loc + '/RELATIONS_LABELS.txt',
                         self.dir_loc_resources + '/relations_data/RELATIONS_LABELS.txt')
         # inverse relations
         shutil.copyfile(self.dir_loc + '/INVERSE_RELATIONS.txt',
                         self.dir_loc_resources + '/relations_data/INVERSE_RELATIONS.txt')
-        # owl nets properties file
-        shutil.copyfile(self.dir_loc + '/OWL_NETS_Property_Types.txt',
-                        self.dir_loc_resources + '/owl_decoding/OWL_NETS_Property_Types.txt')
         # empty master edges
         shutil.copyfile(self.dir_loc + '/Master_Edge_List_Dict_empty.json',
                         self.dir_loc_resources + '/Master_Edge_List_Dict_empty.json')
@@ -65,20 +56,29 @@ def setUp(self):
         # create edge list
         edge_dict = {"gene-phenotype": {"data_type": "entity-class",
                                         "edge_relation": "RO_0003302",
-                                        "uri": ["https://www.ncbi.nlm.nih.gov/gene/",
+                                        "uri": ["http://www.ncbi.nlm.nih.gov/gene/",
                                                 "http://purl.obolibrary.org/obo/"],
-                                        "edge_list": [["2", "HP_0002511"], ["2", "HP_0000716"],
-                                                      ["2", "HP_0000100"], ["9", "HP_0030955"],
-                                                      ["9", "HP_0009725"], ["9", "HP_0100787"],
-                                                      ["9", "HP_0012125"], ["10", "HP_0009725"],
-                                                      ["10", "HP_0010301"], ["10", "HP_0045005"]]},
+                                        "edge_list": [["2", "SO_0000162"], ["2", "SO_0000196"],
+                                                      ["2", "SO_0000323"], ["9", "SO_0001490"],
+                                                      ["9", "SO_0000301"], ["9", "SO_0001560"],
+                                                      ["9", "SO_0001560"], ["10", "SO_0000444"],
+                                                      ["10", "SO_0002138"], ["10", "SO_0000511"]]},
                      "gene-gene": {"data_type": "entity-entity",
                                    "edge_relation": "RO_0002435",
-                                   "uri": ["https://www.ncbi.nlm.nih.gov/gene/",
-                                           "https://www.ncbi.nlm.nih.gov/gene/"],
-                                   "edge_list": [["3075", "1080"], ["3075", "4267"], ["4800", "10190"],
-                                                 ["4800", "80219"], ["2729", "1962"], ["2729", "5096"],
-                                                 ["8837", "6774"], ["8837", "8754"]]}
+                                   "uri": ["http://www.ncbi.nlm.nih.gov/gene/",
+                                           "http://www.ncbi.nlm.nih.gov/gene/"],
+                                   "edge_list": [["1", "2"], ["2", "3"], ["3", "18"],
+                                                 ["17", "19"], ["4", "17"], ["5", "11"],
+                                                 ["11", "12"], ["4", "5"]]},
+                     "disease-disease": {"data_type": "class-class",
+                                         "edge_relation": "RO_0002435",
+                                         "uri": ["http://www.ncbi.nlm.nih.gov/gene/",
+                                                 "http://www.ncbi.nlm.nih.gov/gene/"],
+                                         "edge_list": [["DOID_3075", "DOID_1080"], ["DOID_3075", "DOID_4267"],
+                                                       ["DOID_4800", "DOID_10190"], ["DOID_4800", "DOID_80219"],
+                                                       ["DOID_2729", "DOID_1962"], ["DOID_2729", "DOID_5096"],
+                                                       ["DOID_8837", "DOID_6774"], ["DOID_8837", "DOID_8754"]]},
+                     "entity_namespaces": {"gene": "http://purl.uniprot.org/geneid/"}
                      }
 
         # save data
@@ -86,12 +86,9 @@ def setUp(self):
             json.dump(edge_dict, filepath)
 
         # create subclass mapping data
-        subcls_map = {"2": ['SO_0001217'], "9": ['SO_0001217'], "10": ['SO_0001217'], "1080": ['SO_0001217'],
-                      "1962": ['SO_0001217'], "2729": ['SO_0001217'], "3075": ['SO_0001217'],
-                      "4267": ['SO_0001217'],
-                      "4800": ['SO_0001217'], "5096": ['SO_0001217'], "6774": ['SO_0001217'],
-                      "8754": ['SO_0001217'],
-                      "8837": ['SO_0001217'], "10190": ['SO_0001217'], "80219": ['SO_0001217']}
+        subcls_map = {"1": ['SO_0001217'], "2": ['SO_0001217'], "3": ['SO_0001217'], "4": ['SO_0001217'],
+                      "5": ['SO_0001217'], "11": ['SO_0001217'], "12": ['SO_0001217'], "17": ['SO_0001217'],
+                      "18": ['SO_0001217'], "5096": ['SO_0001217'], "6774": ['SO_0001217'], "19": ['SO_0001217']}
 
         # save data
         with open(self.dir_loc_resources + '/construction_approach/subclass_construction_map.pkl', 'wb') as f:
@@ -127,31 +124,34 @@ def test_construct_knowledge_graph(self):
 
         # check for output files
         # kg
-        kg = 'PheKnowLator_v2.0.0_post-closure_subclass_inverseRelations_noOWL.owl'
-        self.assertTrue(os.path.exists(self.dir_loc_resources + '/knowledge_graphs/' + kg))
+        self.assertTrue(os.path.exists(self.dir_loc_resources + '/knowledge_graphs/' + self.kg.full_kg))
 
-        # kg - owl
-        kg_owl = 'PheKnowLator_v2.0.0_post-closure_subclass_inverseRelations_noOWL_OWLNETS.nt'
+        # kg - owl-nets
+        kg_owl = self.kg.full_kg.replace('.owl', '_OWLNETS.nt')
         self.assertTrue(os.path.exists(self.dir_loc_resources + '/knowledge_graphs/' + kg_owl))
 
         # kg - nx.multiDiGraph
-        kg_mdg = 'PheKnowLator_v2.0.0_post-closure_subclass_inverseRelations_noOWL_OWLNETS_NetworkxMultiDiGraph.gpickle'
+        kg_mdg = self.kg.full_kg.replace('.owl', '_OWLNETS_NetworkxMultiDiGraph.gpickle')
         self.assertTrue(os.path.exists(self.dir_loc_resources + '/knowledge_graphs/' + kg_mdg))
 
+        # kg - owl-nets decoding dict
+        kg_dict = self.kg.full_kg.replace('.owl', '_OWLNETS_decoding_dict.pkl')
+        self.assertTrue(os.path.exists(self.dir_loc_resources + '/knowledge_graphs/' + kg_dict))
+
         # node metadata
-        meta = 'PheKnowLator_v2.0.0_post-closure_subclass_inverseRelations_noOWL_NodeLabels.txt'
+        meta = self.kg.full_kg.replace('.owl', '_NodeLabels.txt')
         self.assertTrue(os.path.exists(self.dir_loc_resources + '/knowledge_graphs/' + meta))
 
         # edge list - identifiers
-        ids = 'PheKnowLator_v2.0.0_post-closure_subclass_inverseRelations_noOWL_Triples_Identifiers.txt'
+        ids = self.kg.full_kg.replace('.owl', '_Triples_Identifiers.txt')
         self.assertTrue(os.path.exists(self.dir_loc_resources + '/knowledge_graphs/' + ids))
 
         # edge list - integers
-        ints = 'PheKnowLator_v2.0.0_post-closure_subclass_inverseRelations_noOWL_Triples_Integers.txt'
+        ints = self.kg.full_kg.replace('.owl', '_Triples_Integers.txt')
         self.assertTrue(os.path.exists(self.dir_loc_resources + '/knowledge_graphs/' + ints))
 
         # edge list map
-        int_map = 'PheKnowLator_v2.0.0_post-closure_subclass_inverseRelations_noOWL_Triples_Integer_Identifier_Map.json'
+        int_map = self.kg.full_kg.replace('.owl', '_Triples_Integer_Identifier_Map.json')
         self.assertTrue(os.path.exists(self.dir_loc_resources + '/knowledge_graphs/' + int_map))
 
         return None
diff --git a/tests/test_metadata.py b/tests/test_metadata.py
index b4eb29652..7787d98b8 100644
--- a/tests/test_metadata.py
+++ b/tests/test_metadata.py
@@ -1,6 +1,7 @@
 import glob
 import os
 import os.path
+import pickle
 import unittest
 
 from rdflib import Graph
@@ -22,11 +23,14 @@ def setUp(self):
         dir_loc2 = os.path.join(current_directory, 'utils/owltools')
         self.owltools_location = os.path.abspath(dir_loc2)
 
+        # create graph data
+        self.graph = Graph().parse(self.dir_loc + '/ontologies/so_with_imports.owl')
+
         # set-up input arguments
         self.metadata = Metadata(kg_version='v2.0.0',
                                  write_location=self.dir_loc,
                                  kg_location=self.dir_loc + '/ontologies/so_with_imports.owl',
-                                 node_data=glob.glob(self.dir_loc + '/node_data/*.txt'),
+                                 node_data=glob.glob(self.dir_loc + '/node_data/*.pkl'),
                                  node_dict=dict())
 
         # load dictionary
@@ -37,84 +41,125 @@ def setUp(self):
     def test_node_metadata_processor(self):
         """Tests the node_metadata_processor method."""
 
+        # set-up input arguments
+        self.metadata = Metadata(kg_version='v2.0.0', write_location=self.dir_loc,
+                                 kg_location=self.dir_loc + '/ontologies/so_with_imports.owl',
+                                 node_data=glob.glob(self.dir_loc + '/node_data/*dict.pkl'),
+                                 node_dict=dict())
+        self.metadata.node_metadata_processor()  # load dictionary
+
         # make sure that the dictionary has the "schtuff"
         self.assertIsInstance(self.metadata.node_dict, Dict)
-        self.assertTrue('gene-phenotype' in self.metadata.node_dict.keys())
-        self.assertIsInstance(self.metadata.node_dict['gene-phenotype'], Dict)
-        self.assertTrue(len(self.metadata.node_dict['gene-phenotype']) == 3)
-        self.assertIn('Label', self.metadata.node_dict['gene-phenotype']['2'].keys())
-        self.assertIn('Synonym', self.metadata.node_dict['gene-phenotype']['2'].keys())
-        self.assertIn('Description', self.metadata.node_dict['gene-phenotype']['2'].keys())
+        self.assertTrue('nodes' in self.metadata.node_dict.keys())
+        self.assertTrue('relations' in self.metadata.node_dict.keys())
+
+        # check node dict
+        node_key = 'http://www.ncbi.nlm.nih.gov/gene/1'
+        self.assertIsInstance(self.metadata.node_dict['nodes'], Dict)
+        self.assertTrue(len(self.metadata.node_dict['nodes']) == 20)
+        self.assertIn('Label', self.metadata.node_dict['nodes'][node_key].keys())
+        self.assertIn('Synonym', self.metadata.node_dict['nodes'][node_key].keys())
+        self.assertIn('Description', self.metadata.node_dict['nodes'][node_key].keys())
+
+        # check relations dict
+        relations_key = 'http://purl.obolibrary.org/obo/RO_0002597'
+        self.assertIsInstance(self.metadata.node_dict['relations'], Dict)
+        self.assertTrue(len(self.metadata.node_dict['relations']) == 20)
+        self.assertIn('Label', self.metadata.node_dict['relations'][relations_key].keys())
+        self.assertIn('Synonym', self.metadata.node_dict['relations'][relations_key].keys())
+        self.assertIn('Description', self.metadata.node_dict['relations'][relations_key].keys())
 
         return None
 
-    def test_creates_node_metadata(self):
+    def test_creates_node_metadata_nodes(self):
         """Tests the creates_node_metadata method."""
 
-        # load graph
-        graph = Graph()
-        graph.parse(self.dir_loc + '/ontologies/so_with_imports.owl')
-        org_graph_len = len(graph)
+        self.metadata.node_data = [self.metadata.node_data[0].replace('.pkl', '_test.pkl')]
+        self.metadata.extracts_class_metadata(self.graph)
 
         # test when the node has metadata
-        updated_graph_good = self.metadata.creates_node_metadata(node='2',
-                                                                 edge_type='gene-phenotype',
-                                                                 url='https://www.ncbi.nlm.nih.gov/gene/',
-                                                                 graph=graph)
-        # verify that new edges were added to the graph
-        self.assertTrue(len(updated_graph_good) > org_graph_len)
-
-        # check that the right number of new edges were added to the graph
-        graph_diff = len(updated_graph_good) - org_graph_len
-        new_edges = sum([len(x[1].split('|')) for x in self.metadata.node_dict['gene-phenotype']['2'].items()])
-        self.assertTrue(graph_diff == new_edges)
+        updated_graph_1 = self.metadata.creates_node_metadata(ent=['http://www.ncbi.nlm.nih.gov/gene/1',
+                                                                   'http://www.ncbi.nlm.nih.gov/gene/2'],
+                                                              e_type=['entity', 'entity'])
+        self.assertTrue(len(updated_graph_1) == 16)
+
+        # check that the correct info is returned if only one is an entity
+        updated_graph_2 = self.metadata.creates_node_metadata(ent=['http://www.ncbi.nlm.nih.gov/gene/1',
+                                                                   'http://www.ncbi.nlm.nih.gov/gene/2'],
+                                                              e_type=['entity', 'class'])
+        self.assertTrue(len(updated_graph_2) == 8)
+        # check that nothing is returned if the entities are classes
+        updated_graph_3 = self.metadata.creates_node_metadata(ent=['http://www.ncbi.nlm.nih.gov/gene/1',
+                                                                   'http://www.ncbi.nlm.nih.gov/gene/2'],
+                                                              e_type=['class', 'class'])
+        self.assertTrue(updated_graph_3 is None)
 
         # test when the node does not have metadata
-        self.assertRaises(KeyError, self.metadata.creates_node_metadata,
-                          '0',
-                          'gene-phenotype',
-                          'https://www.ncbi.nlm.nih.gov/gene/',
-                          graph)
+        updated_graph_4 = self.metadata.creates_node_metadata(ent=['http://www.ncbi.nlm.nih.gov/gene/None',
+                                                                   'http://www.ncbi.nlm.nih.gov/gene/None'],
+                                                              e_type=['entity', 'entity'])
+        self.assertTrue(updated_graph_4 is None)
+
+        # test when node_data is None
+        self.metadata.node_data = None
+        updated_graph_5 = self.metadata.creates_node_metadata(ent=['http://www.ncbi.nlm.nih.gov/gene/None',
+                                                                   'http://www.ncbi.nlm.nih.gov/gene/None'],
+                                                              e_type=['entity', 'entity'])
+        self.assertTrue(updated_graph_5 is None)
 
         return None
 
-    def test_adds_node_metadata(self):
-        """Tests the adds_node_metadata method."""
+    def test_creates_node_metadata_relations(self):
+        """Tests the creates_node_metadata method."""
 
-        # load graph
-        graph = Graph()
-        graph.parse(self.dir_loc + '/ontologies/so_with_imports.owl')
-        org_graph_len = len(graph)
-
-        # set up edge_dictionary
-        edge_dict = {"gene-phenotype": {"data_type": "entity-class",
-                                        "uri": ["https://www.ncbi.nlm.nih.gov/gene/",
-                                                "http://purl.obolibrary.org/obo/"],
-                                        "edge_list": [["2", "HP_0002511"],
-                                                      ["2", "HP_0000716"],
-                                                      ["2", "HP_0000100"],
-                                                      ["9", "HP_0030955"],
-                                                      ["9", "HP_0009725"],
-                                                      ["9", "HP_0100787"],
-                                                      ["9", "HP_0012125"],
-                                                      ["10", "HP_0009725"],
-                                                      ["10", "HP_0010301"],
-                                                      ["10", "HP_0045005"]]}}
+        self.metadata.node_data = [self.metadata.node_data[0].replace('.pkl', '_test.pkl')]
+        self.metadata.extracts_class_metadata(self.graph)
 
         # test when the node has metadata
-        updated_graph = self.metadata.adds_node_metadata(graph=graph, edge_dict=edge_dict)
+        updated_graph_1 = self.metadata.creates_node_metadata(ent=['http://purl.obolibrary.org/obo/RO_0002310'],
+                                                              key_type='relations')
+        self.assertTrue(len(updated_graph_1) == 2)
 
-        # verify that new edges were added to the graph
-        self.assertTrue(len(updated_graph) > org_graph_len)
+        # check that nothing is returned if the entities are classes
+        updated_graph_2 = self.metadata.creates_node_metadata(ent=['http://purl.obolibrary.org/obo/RO_0002597'],
+                                                              e_type=['class'], key_type='relations')
+        self.assertTrue(updated_graph_2 is None)
+
+        # test when the node does not have metadata
+        updated_graph_3 = self.metadata.creates_node_metadata(['http://www.ncbi.nlm.nih.gov/gene/None'],
+                                                              key_type='relations')
+        self.assertTrue(updated_graph_3 is None)
+
+        return None
+
+    def test_creates_node_metadata_none(self):
+        """Tests the creates_node_metadata method when node_dict is None."""
 
-        # check that the right number of new edges were added to the graph
-        graph_diff = len(updated_graph) - org_graph_len
+        self.metadata.node_data = [self.metadata.node_data[0].replace('.pkl', '_test.pkl')]
+        self.metadata.extracts_class_metadata(self.graph)
+        self.metadata.node_dict = None
 
-        new_edges = 0
-        for node in ["2", "9", "10"]:
-            new_edges += sum([len(x[1].split('|')) for x in self.metadata.node_dict['gene-phenotype'][node].items()])
+        # relations -- with valid input
+        updated_graph_1 = self.metadata.creates_node_metadata(ent=['http://purl.obolibrary.org/obo/RO_0002597'],
+                                                              key_type='relations')
+        self.assertTrue(updated_graph_1 is None)
 
-        self.assertTrue(graph_diff == new_edges)
+        # relations -- without valid input
+        updated_graph_2 = self.metadata.creates_node_metadata(ent=['http://purl.obolibrary.org/obo/None'],
+                                                              key_type='relations')
+        self.assertTrue(updated_graph_2 is None)
+
+        # nodes -- with valid input
+        updated_graph_3 = self.metadata.creates_node_metadata(ent=['http://www.ncbi.nlm.nih.gov/gene/1',
+                                                                   'http://www.ncbi.nlm.nih.gov/gene/2'],
+                                                              e_type=['class', 'class'])
+        self.assertTrue(updated_graph_3 is None)
+
+        # nodes -- without valid input
+        updated_graph_4 = self.metadata.creates_node_metadata(ent=['http://www.ncbi.nlm.nih.gov/gene/None',
+                                                                   'http://www.ncbi.nlm.nih.gov/gene/None'],
+                                                              e_type=['entity', 'entity'])
+        self.assertTrue(updated_graph_4 is None)
 
         return None
 
@@ -151,8 +196,7 @@ def test_adds_annotation_assertions(self):
         filename = self.metadata.full_kg[:-4] + '_NoAnnotationAssertions.owl'
 
         # load graph
-        graph = Graph()
-        graph.parse(filename)
+        graph = Graph().parse(filename)
         no_annot_graph_len = len(graph)
 
         # add back annotation assertions
@@ -169,46 +213,54 @@ def test_adds_annotation_assertions(self):
     def test_extracts_class_metadata(self):
         """Tests the extracts_class_metadata data."""
 
-        # load dictionary
-        self.metadata.node_metadata_processor()
-
-        # load graph
-        graph = Graph()
-        graph.parse(self.dir_loc + '/ontologies/so_with_imports.owl')
+        org_file_size = os.path.getsize(self.metadata.node_data[0])
 
         # extract metadata
-        self.metadata.extracts_class_metadata(graph=graph)
+        self.metadata.node_data = [self.metadata.node_data[0].replace('.pkl', '_test.pkl')]
+        self.metadata.extracts_class_metadata(graph=self.graph)
 
         # check that it worked
-        self.assertIn('classes', self.metadata.node_dict.keys())
-        self.assertTrue(len(self.metadata.node_dict['classes']) == 2793)
-        self.assertIn('Label', self.metadata.node_dict['classes']['SO_0000373'].keys())
-        self.assertIn('Synonym', self.metadata.node_dict['classes']['SO_0000373'].keys())
-        self.assertIn('Description', self.metadata.node_dict['classes']['SO_0000373'].keys())
+        # nodes
+        node_key = 'http://purl.obolibrary.org/obo/SO_0000373'
+        self.assertTrue(len(self.metadata.node_dict['nodes']) == 2461)
+        self.assertIn('Label', self.metadata.node_dict['nodes'][node_key])
+        self.assertIn('Description', self.metadata.node_dict['nodes'][node_key])
+        self.assertIn('Synonym', self.metadata.node_dict['nodes'][node_key])
+        # relations
+        relation_key = 'http://purl.obolibrary.org/obo/so#genome_of'
+        self.assertTrue(len(self.metadata.node_dict['relations']) == 2511)
+        self.assertIn('Label', self.metadata.node_dict['relations'][relation_key])
+        self.assertIn('Description', self.metadata.node_dict['relations'][relation_key])
+        self.assertIn('Synonym', self.metadata.node_dict['relations'][relation_key])
+
+        # check that larger dict was saved
+        self.assertTrue(os.path.getsize(self.metadata.node_data[0]) >= org_file_size)
 
         return None
 
     def test_output_knowledge_graph_metadata(self):
         """Tests the output_knowledge_graph_metadata method."""
 
-        # update environment var
-        self.metadata.write_location = ''
+        self.metadata.write_location = ''  # update environment var
+        self.metadata.node_metadata_processor()  # load dictionary
+        graph = Graph().parse(self.dir_loc + '/ontologies/so_with_imports.owl')  # load graph
+        filename = self.dir_loc + '/ontologies/'
 
-        # load dictionary
-        self.metadata.node_metadata_processor()
-
-        # load graph
-        graph = Graph()
-        graph.parse(self.dir_loc + '/ontologies/so_with_imports.owl')
+        # get node integer map
+        node_ints = maps_node_ids_to_integers(graph, filename, 'SO_Triples_Integers.txt',
+                                              'SO_Triples_Integer_Identifier_Map.json')
 
         # run function
-        self.metadata.output_knowledge_graph_metadata(graph=graph)
+        self.metadata.output_knowledge_graph_metadata(node_ints)
 
         # make sure that node data wrote out
         self.assertTrue(os.path.exists(self.dir_loc + '/ontologies/so_with_imports_NodeLabels.txt'))
 
         # remove file
         os.remove(self.dir_loc + '/ontologies/so_with_imports_NodeLabels.txt')
+        os.remove(filename + 'SO_Triples_Integers.txt')
+        os.remove(filename + 'SO_Triples_Identifiers.txt')
+        os.remove(filename + 'SO_Triples_Integer_Identifier_Map.json')
 
         return None
 
@@ -216,11 +268,10 @@ def test_adds_ontology_annotations(self):
         """Tests the adds_ontology_annotations method."""
 
         # load graph
-        graph = Graph()
-        graph.parse(self.dir_loc + '/ontologies/so_with_imports.owl')
+        graph = Graph().parse(self.dir_loc + '/ontologies/so_with_imports.owl')
 
         # run function
-        updated_graph = self.metadata.adds_ontology_annotations(filename='tests/data/so_tests_test_file', graph=graph)
+        updated_graph = self.metadata.adds_ontology_annotations(filename='tests/data/so_tests_file.owl', graph=graph)
 
         # check that the annotations were added
         results = updated_graph.query(
@@ -233,7 +284,15 @@ def test_adds_ontology_annotations(self):
 
         results_list = [str(x) for y in results for x in y]
         self.assertTrue(len(results_list) == 21)
-        self.assertIn('https://pheknowlator.com/pheknowlator_test_file.owl', results_list)
-        self.assertIn('tests/data/so_tests_test_file', results_list)
+        self.assertIn('https://pheknowlator.com/pheknowlator_file.owl', results_list)
+
+        return None
+
+    def tearDown(self):
+
+        if self.metadata.node_data:
+            test_data_location = glob.glob(self.dir_loc + '/node_data/*_test.pkl')
+            if len(test_data_location) > 0:
+                os.remove(test_data_location[0])
 
         return None
diff --git a/tests/test_owlnets.py b/tests/test_owlnets.py
index 0f6e113d6..defa927ca 100644
--- a/tests/test_owlnets.py
+++ b/tests/test_owlnets.py
@@ -31,29 +31,21 @@ def setUp(self):
         # ontology data
         shutil.copyfile(self.dir_loc + '/ontologies/so_with_imports.owl',
                         self.dir_loc_resources + '/knowledge_graphs/so_with_imports.owl')
-
-        # owl properties
-        shutil.copyfile(self.dir_loc + '/OWL_NETS_Property_Types.txt',
-                        self.dir_loc_resources + '/owl_decoding/OWL_NETS_Property_Types.txt')
-
         # set-up input arguments
         self.write_location = self.dir_loc_resources + '/knowledge_graphs'
         self.kg_filename = '/so_with_imports.owl'
-        self.object_properties = self.dir_loc_resources + '/owl_decoding/OWL_NETS_Property_Types.txt'
-
         # read in knowledge graph
-        self.graph = Graph()
-        self.graph.parse(self.dir_loc_resources + '/knowledge_graphs/so_with_imports.owl', format='xml')
-
+        self.graph = Graph().parse(self.dir_loc_resources + '/knowledge_graphs/so_with_imports.owl', format='xml')
         # initialize class
-        self.owl_nets = OwlNets(kg_construct_approach='subclass',
-                                graph=self.graph,
-                                write_location=self.write_location,
-                                full_kg=self.kg_filename)
+        self.owl_nets = OwlNets(kg_construct_approach='subclass', graph=self.graph,
+                                write_location=self.write_location, filename=self.kg_filename)
+        self.owl_nets2 = OwlNets(kg_construct_approach='instance', graph=self.graph,
+                                 write_location=self.write_location, filename=self.kg_filename)
 
         # update class attributes
         dir_loc_owltools = os.path.join(current_directory, 'utils/owltools')
         self.owl_nets.owl_tools = os.path.abspath(dir_loc_owltools)
+        self.owl_nets2.owl_tools = os.path.abspath(dir_loc_owltools)
 
         return None
 
@@ -76,7 +68,7 @@ def test_initialization_owltools_default(self):
         owl_nets = OwlNets(kg_construct_approach='subclass',
                            graph=self.graph,
                            write_location=self.write_location,
-                           full_kg=self.kg_filename)
+                           filename=self.kg_filename)
 
         self.assertEqual(owl_nets.owl_tools, './pkt_kg/libs/owltools')
 
@@ -88,7 +80,7 @@ def test_initialization_owltools(self):
         owl_nets = OwlNets(kg_construct_approach='subclass',
                            graph=self.graph,
                            write_location=self.write_location,
-                           full_kg=self.kg_filename,
+                           filename=self.kg_filename,
                            owl_tools='test_location')
 
         self.assertEqual(owl_nets.owl_tools, 'test_location')
@@ -99,63 +91,70 @@ def test_initialization_state_graph(self):
         """Tests the class initialization state for graphs."""
 
         # verify input graph object - when wrong data type
-        self.assertRaises(TypeError, OwlNets, 'subclass', list(), self.write_location, self.kg_filename)
+        self.assertRaises(TypeError, OwlNets,
+                          kg_construct_approach='subclass', graph=list(),
+                          write_location=self.write_location, filename=self.kg_filename)
 
         # verify input graph object - when graph file is empty
-        self.assertRaises(ValueError, OwlNets, 'subclass', Graph(), self.write_location, self.kg_filename)
+        self.assertRaises(ValueError, OwlNets, kg_construct_approach='subclass', graph=Graph(),
+                          write_location=self.write_location, filename=self.kg_filename)
 
         return None
 
-    def test_initialization_state_construction_approach(self):
-        """Tests the class initialization state for construction approach type."""
-
-        self.assertIsInstance(self.owl_nets.kg_construct_approach, str)
-        self.assertTrue(self.owl_nets.kg_construct_approach == 'subclass')
-        self.assertFalse(self.owl_nets.kg_construct_approach == 'instance')
+    def test_graph_input_types(self):
+        """Tests different graph input types."""
 
-        return None
+        # when graph is provided
+        owl_nets = OwlNets(kg_construct_approach='subclass',
+                           graph=self.graph,
+                           write_location=self.write_location,
+                           filename=self.kg_filename,
+                           owl_tools='test_location')
 
-    def test_initialization_state_object_properties(self):
-        """Tests the class initialization state for object properties."""
+        self.assertIsInstance(owl_nets.graph, Graph)
 
-        # verify owl properties file - when not a text file
-        os.remove(self.dir_loc_resources + '/owl_decoding/OWL_NETS_Property_Types.txt')
-        shutil.copyfile(self.dir_loc + '/OWL_NETS_Property_Types.txt',
-                        self.dir_loc_resources + '/owl_decoding/OWL_NETS_Property_Types.csv')
+        # when path to graph is provided
+        owl_nets = OwlNets(kg_construct_approach='subclass',
+                           graph=self.dir_loc_resources + '/knowledge_graphs/so_with_imports.owl',
+                           write_location=self.write_location,
+                           filename=self.kg_filename,
+                           owl_tools='test_location')
 
-        self.assertRaises(TypeError, OwlNets, self.graph, self.write_location, self.kg_filename)
+        self.assertIsInstance(owl_nets.graph, Graph)
 
-        # verify owl properties file - when the file is empty
-        os.remove(self.dir_loc_resources + '/owl_decoding/OWL_NETS_Property_Types.csv')
-        shutil.copyfile(self.dir_loc + '/ontology_source_list_empty.txt',
-                        self.dir_loc_resources + '/owl_decoding/OWL_NETS_Property_Types.txt')
+        return None
 
-        self.assertRaises(TypeError, OwlNets, self.graph, self.write_location, self.kg_filename)
+    def test_initialization_state_construction_approach(self):
+        """Tests the class initialization state for construction approach type."""
 
-        # check length of file after a successful run
-        self.assertIsInstance(self.owl_nets.keep_properties, List)
-        self.assertTrue(len(self.owl_nets.keep_properties) == 52)
+        self.assertIsInstance(self.owl_nets.kg_construct_approach, str)
+        self.assertTrue(self.owl_nets.kg_construct_approach == 'subclass')
+        self.assertFalse(self.owl_nets.kg_construct_approach == 'instance')
 
         return None
 
-    def test_initialization_state_object_properties_keep_list(self):
-        """Tests the class initialization state for object properties, specifically checking that the list contains
-        the two properties needed to run OWL-NETS."""
-
-        self.assertIn('http://purl.obolibrary.org/obo/RO_0000086', self.owl_nets.keep_properties)
-        self.assertIn('http://www.w3.org/2000/01/rdf-schema#subClassOf', self.owl_nets.keep_properties)
+    def test_initialization_owl_nets_dict(self):
+        """Tests the class initialization state for owl_nets_dict."""
+
+        self.assertIsInstance(self.owl_nets.owl_nets_dict, Dict)
+        self.assertIn('owl_nets', self.owl_nets.owl_nets_dict.keys())
+        self.assertIn('decoded_classes', self.owl_nets.owl_nets_dict['owl_nets'])
+        self.assertIn('complementOf', self.owl_nets.owl_nets_dict['owl_nets'].keys())
+        self.assertIn('cardinality', self.owl_nets.owl_nets_dict['owl_nets'].keys())
+        self.assertIn('negation', self.owl_nets.owl_nets_dict['owl_nets'].keys())
+        self.assertIn('misc', self.owl_nets.owl_nets_dict['owl_nets'].keys())
+        self.assertIn('disjointWith', self.owl_nets.owl_nets_dict.keys())
+        self.assertIn('filtered_triples', self.owl_nets.owl_nets_dict.keys())
+        self.assertIn('{}_approach_purified'.format(self.owl_nets.kg_construct_approach),
+                      self.owl_nets.owl_nets_dict.keys())
 
         return None
 
-    def test_multidigraph_conversion(self):
-        """Tests the transformation of a RDFLib graph object into a Networkx MultiDiGraph."""
+    def test_initialization_class_list(self):
+        """Tests the class initialization state for class_list."""
 
-        # make sure that the object exists and is the right type
-        self.assertFalse(not self.owl_nets.nx_mdg)
-        self.assertIsInstance(self.owl_nets.nx_mdg, networkx.MultiDiGraph)
-
-        # check the length of the object
-        self.assertTrue(len(self.owl_nets.nx_mdg) == 20277)
+        self.assertIsInstance(self.owl_nets.class_list, List)
+        self.assertEqual(len(self.owl_nets.class_list), 2573)
 
         return None
 
@@ -192,7 +191,7 @@ def test_updates_class_instance_identifiers_instance(self):
 
         # run method to roll back to re-map instances of classes
         self.owl_nets.updates_class_instance_identifiers()
-        self.assertEqual(len(self.owl_nets.graph), 6)
+        self.assertEqual(len(self.owl_nets.graph), 8)
         self.assertIn((URIRef('http://purl.obolibrary.org/obo/CHEBI_2504'),
                        URIRef('http://purl.obolibrary.org/obo/RO_0002434'),
                        URIRef('https://www.ncbi.nlm.nih.gov/gene/55847')),
@@ -211,6 +210,44 @@ def test_updates_class_instance_identifiers_subclass(self):
 
         return None
 
+    def test_converts_rdflib_to_networkx_multidigraph(self):
+        """Tests the converts_rdflib_to_networkx_multidigraph method."""
+
+        self.owl_nets.converts_rdflib_to_networkx_multidigraph()
+        self.assertIsInstance(self.owl_nets.nx_mdg, networkx.MultiDiGraph)
+        self.assertTrue(len(self.owl_nets.nx_mdg) == 20277)
+
+        return None
+
+    def test_removes_disjoint_with_axioms(self):
+        """Tests the removes_disjoint_with_axioms method."""
+
+        # create test data
+        graph = Graph()
+        triples = [(BNode('N9f94b1ff016149d0859c059b74e5360f'),
+                    URIRef('http://www.geneontology.org/formats/oboInOwl#source'),
+                    Literal('lexical', datatype=URIRef('http://www.w3.org/2001/XMLSchema#string'))),
+                   (BNode('N9f94b1ff016149d0859c059b74e5360f'),
+                    URIRef('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'),
+                    URIRef('http://www.w3.org/2002/07/owl#Axiom')),
+                   (BNode('N9f94b1ff016149d0859c059b74e5360f'),
+                    URIRef('http://www.w3.org/2002/07/owl#annotatedTarget'),
+                    URIRef('http://purl.obolibrary.org/obo/UBERON_0022716')),
+                   (BNode('N9f94b1ff016149d0859c059b74e5360f'),
+                    URIRef('http://www.w3.org/2002/07/owl#annotatedSource'),
+                    URIRef('http://purl.obolibrary.org/obo/UBERON_0022352')),
+                   (BNode('N9f94b1ff016149d0859c059b74e5360f'),
+                    URIRef('http://www.w3.org/2002/07/owl#annotatedProperty'),
+                    URIRef('http://www.w3.org/2002/07/owl#disjointWith'))]
+        for x in triples: graph.add(x)
+        self.owl_nets.graph = graph
+
+        # test method
+        self.owl_nets.removes_disjoint_with_axioms()
+        self.assertTrue(len(self.owl_nets.graph) == 0)
+
+        return None
+
     def test_removes_edges_with_owl_semantics(self):
         """Tests the removes_edges_with_owl_semantics method."""
 
@@ -225,6 +262,22 @@ def test_removes_edges_with_owl_semantics(self):
 
         return None
 
+    def test_removes_edges_with_owl_semantics_decoded(self):
+        """Tests the removes_edges_with_owl_semantics method when owl has been decoded."""
+
+        self.owl_nets.owl_nets_dict['owl_nets']['decoded_classes'] = [1, 2, 3, 4, 5]
+
+        # run method
+        filtered_graph = self.owl_nets.removes_edges_with_owl_semantics()
+
+        # check output type
+        self.assertIsInstance(filtered_graph, Graph)
+
+        # check output length
+        self.assertEqual(len(filtered_graph), 3009)
+
+        return None
+
     def test_recurses_axioms(self):
         """Tests the recurses_axioms method."""
 
@@ -259,15 +312,126 @@ def test_creates_edge_dictionary(self):
         """Tests the creates_edge_dictionary method."""
 
         # set-up inputs
+        self.owl_nets.converts_rdflib_to_networkx_multidigraph()
         node = URIRef('http://purl.obolibrary.org/obo/SO_0000822')
-        edge_dict, cardinality = self.owl_nets.creates_edge_dictionary(node)
+        edge_dict = self.owl_nets.creates_edge_dictionary(node)
+
+        # test method
+        self.assertIsInstance(edge_dict[0], Dict)
+        self.assertEqual(len(edge_dict[0]), 5)
+        self.assertIsInstance(edge_dict[0][list(edge_dict[0].keys())[0]], Dict)
+        self.assertIsInstance(edge_dict[1], Set)
+        self.assertEqual(len(edge_dict[1]), 0)
+
+        return None
+
+    def test_detects_constructed_class_to_ignore_complement(self):
+        """Tests the detects_constructed_class_to_ignore method for a complementOf object."""
+
+        # set-up input
+        node = URIRef('http://purl.obolibrary.org/obo/SO_0000340')
+        results = ({BNode('N47395fcce3fc4bae86eb0a785f6a50fe'): {
+            'type': URIRef('http://www.w3.org/2002/07/owl#Class'),
+            'complementOf': URIRef('http://purl.obolibrary.org/obo/BFO_0000016')},
+                       BNode('N7d4b70b12467414383dcda0b2de14fac'): {
+                           'type': URIRef(
+                               'http://www.w3.org/2002/07/owl#Restriction'),
+                           'onProperty': URIRef('http://purl.obolibrary.org/obo/BFO_0000186'),
+                           'allValuesFrom': BNode('N47395fcce3fc4bae86eb0a785f6a50fe')},
+                       BNode('N9912bdc5f0b64056b8f733834c3f8788'): {
+                           'complementOf': URIRef(
+                               'http://purl.obolibrary.org/obo/BFO_0000016'),
+                           'type': URIRef('http://www.w3.org/2002/07/owl#Class')},
+                       BNode('N20dddb25602944319568f385e40fd434'): {
+                           'type': URIRef(
+                               'http://www.w3.org/2002/07/owl#Restriction'),
+                           'onProperty': URIRef('http://purl.obolibrary.org/obo/BFO_0000176'),
+                           'allValuesFrom': BNode('N9912bdc5f0b64056b8f733834c3f8788')}}, set())
+
+        # test method
+        decision = self.owl_nets.detects_constructed_class_to_ignore(results, node)
+        self.assertIsInstance(decision, bool)
+        self.assertEqual(decision, True)
+
+        return None
+
+    def test_detects_constructed_class_to_ignore_cardinality(self):
+        """Tests the detects_constructed_class_to_ignore method for a cardinality object."""
+
+        # set-up input
+        node = URIRef('http://purl.obolibrary.org/obo/SO_0000340')
+        results = ({BNode('Nbfdbf873070f4ff4b8e421afdafcc0d1'): {
+            'onProperty': URIRef('http://purl.obolibrary.org/obo/RO_0002180'),
+            'onClass': URIRef('http://purl.obolibrary.org/obo/PR_Q9BW19'),
+            'type': URIRef('http://www.w3.org/2002/07/owl#Restriction')},
+                       BNode('N92d1234ef3d1431e9696302f8b640855'): {
+                           'type': URIRef('http://www.w3.org/2002/07/owl#Class'),
+                           'intersectionOf': BNode('N75923190ee8d4c569ad0860e55143000')},
+                       BNode('N75923190ee8d4c569ad0860e55143000'): {
+                           'rest': BNode('Ndb37cfaab1534414acc1bd5782cd226d'),
+                           'first': URIRef('http://purl.obolibrary.org/obo/PR_000027264')},
+                       BNode('Ndb37cfaab1534414acc1bd5782cd226d'): {
+                           'first': BNode('N7f08599ce3a945f2a0a7188e30313957'),
+                           'rest': URIRef('http://www.w3.org/1999/02/22-rdf-syntax-ns#nil')},
+                       BNode('N7f08599ce3a945f2a0a7188e30313957'): {
+                           'onProperty': URIRef(
+                               'http://purl.obolibrary.org/obo/RO_0002160'),
+                           'type': URIRef('http://www.w3.org/2002/07/owl#Restriction'),
+                           'someValuesFrom': URIRef('http://purl.obolibrary.org/obo/NCBITaxon_9606')},
+                       BNode('Nacc48f51eda64c25ab95f675c9de9b22'): {
+                           'onProperty': URIRef(
+                               'http://purl.obolibrary.org/obo/RO_0002160'),
+                           'type': URIRef('http://www.w3.org/2002/07/owl#Restriction'),
+                           'someValuesFrom': URIRef('http://purl.obolibrary.org/obo/NCBITaxon_9606')}},
+                   {'http://purl.obolibrary.org/obo/PR_000027428: Nbfdbf873070f4ff4b8e421afdafcc0d1'})
+
+        # test method
+        decision = self.owl_nets.detects_constructed_class_to_ignore(results, node)
+        self.assertIsInstance(decision, bool)
+        self.assertEqual(decision, False)
+
+        return None
+
+    def test_detects_constructed_class_to_ignore_lacks_part(self):
+        """Tests the detects_constructed_class_to_ignore method for a lacks_part object."""
+
+        # set-up input
+        node = URIRef('http://purl.obolibrary.org/obo/SO_000047373')
+        results = ({BNode('Nfb450d1260944ec0a6be7f302e785ee9'): {
+            'type': URIRef('http://www.w3.org/2002/07/owl#Restriction'),
+            'onProperty': URIRef('http://purl.obolibrary.org/obo/RO_0002160'),
+            'someValuesFrom': URIRef('http://purl.obolibrary.org/obo/NCBITaxon_9606')},
+                       BNode('N22b5a01aab6f4257b3bdc5d291c01e31'): {
+                           'someValuesFrom': URIRef(
+                               'http://purl.obolibrary.org/obo/MOD_00115'),
+                           'type': URIRef('http://www.w3.org/2002/07/owl#Restriction'),
+                           'onProperty': URIRef('http://purl.obolibrary.org/obo/BFO_0000051')},
+                       BNode('N9407d67a6bb642c09c50cf98fba99eec'): {
+                           'type': URIRef('http://www.w3.org/2002/07/owl#Restriction'),
+                           'onProperty': URIRef('http://purl.obolibrary.org/obo/pr#lacks_part'),
+                           'someValuesFrom': URIRef('http://purl.obolibrary.org/obo/PR_000021937')}}, set())
+
+        # test method
+        decision = self.owl_nets.detects_constructed_class_to_ignore(results, node)
+        self.assertIsInstance(decision, bool)
+        self.assertEqual(decision, True)
+
+        return None
+
+    def test_detects_constructed_class_to_ignore_regular(self):
+        """Tests the detects_constructed_class_to_ignore method for a regular class."""
+
+        # set-up input
+        node = URIRef('http://purl.obolibrary.org/obo/MONDO_0014439')
+        results = ({BNode('N384aa47973974606a24846db62455903'): {
+            'someValuesFrom': URIRef('http://purl.obolibrary.org/obo/NCBITaxon_9606'),
+            'onProperty': URIRef('http://purl.obolibrary.org/obo/RO_0002160'),
+            'type': URIRef('http://www.w3.org/2002/07/owl#Restriction')}}, set())
 
         # test method
-        self.assertIsInstance(edge_dict, Dict)
-        self.assertEqual(len(edge_dict), 5)
-        self.assertIsInstance(edge_dict[list(edge_dict.keys())[0]], Dict)
-        self.assertIsInstance(cardinality, Set)
-        self.assertEqual(len(cardinality), 0)
+        decision = self.owl_nets.detects_constructed_class_to_ignore(results, node)
+        self.assertIsInstance(decision, bool)
+        self.assertEqual(decision, False)
 
         return None
 
@@ -473,6 +637,7 @@ def test_cleans_owl_encoded_classes(self):
 
         # set-up inputs
         self.owl_nets.class_list = [URIRef('http://purl.obolibrary.org/obo/SO_0000822')]
+        self.owl_nets.converts_rdflib_to_networkx_multidigraph()
 
         # test method
         decoded_graph = self.owl_nets.cleans_owl_encoded_classes()
@@ -490,17 +655,118 @@ def test_cleans_owl_encoded_classes(self):
 
         return None
 
-    def test_run_owl_nets(self):
-        """Tests the run_owl_nets method."""
+    def test_purifies_graph_build_none(self):
+        """Tests the purifies_graph_build method when kg_construction is None."""
+
+        # initialize method
+        owl_nets = OwlNets(graph=self.graph, write_location=self.write_location, filename=self.kg_filename)
+
+        # test method
+        owl_nets.purifies_graph_build()
+        dict_keys = owl_nets.owl_nets_dict['{}_approach_purified'.format(owl_nets.kg_construct_approach)]
+        self.assertTrue(len(dict_keys), 0)
 
+        return None
+
+    def test_purifies_graph_build_instance(self):
+        """Tests the purifies_graph_build method when kg_construction is instance."""
+
+        # initialize method
+        owl_nets = OwlNets(kg_construct_approach='instance', graph=self.graph,
+                           write_location=self.write_location, filename=self.kg_filename)
+
+        # test method
+        owl_nets.purifies_graph_build()
+        dict_keys = owl_nets.owl_nets_dict['{}_approach_purified'.format(owl_nets.kg_construct_approach)]
+        self.assertTrue(len(dict_keys), 3054)
+
+        return None
+
+    def test_purifies_graph_build_subclass(self):
+        """Tests the purifies_graph_build method when kg_construction is subclass."""
+
+        # initialize method
+        owl_nets = OwlNets(kg_construct_approach='subclass', graph=self.graph,
+                           write_location=self.write_location, filename=self.kg_filename)
+
+        # test method
+        owl_nets.purifies_graph_build()
+        dict_keys = owl_nets.owl_nets_dict['{}_approach_purified'.format(owl_nets.kg_construct_approach)]
+        self.assertTrue(len(dict_keys), 6616)
+
+        return None
+
+    def test_write_out_results_regular(self):
+        """Tests the write_out_results method."""
+
+        self.owl_nets.kg_construct_approach = None
         owl_nets_graph = self.owl_nets.run_owl_nets()
-        self.assertIsInstance(owl_nets_graph, Graph)
-        self.assertEqual(len(owl_nets_graph), 2940)
+        self.assertIsInstance(owl_nets_graph, Tuple)
+        self.assertIsInstance(owl_nets_graph[0], Graph)
+        self.assertEqual(owl_nets_graph[1], None)
+        self.assertEqual(len(owl_nets_graph[0]), 2940)
 
         # make sure files are written locally
         nx_mdg_file = 'so_with_imports_OWLNETS_NetworkxMultiDiGraph.gpickle'
         self.assertTrue(os.path.exists(self.dir_loc_resources + '/knowledge_graphs/so_with_imports_OWLNETS.nt'))
         self.assertTrue(os.path.exists(self.dir_loc_resources + '/knowledge_graphs/' + nx_mdg_file))
+        self.assertTrue(os.path.exists(self.dir_loc_resources + '/knowledge_graphs'
+                                                                '/so_with_imports_OWLNETS_decoding_dict.pkl'))
+
+        return None
+
+    def test_write_out_results_subclass_purified(self):
+        """Tests the run_owl_nets method."""
+
+        self.owl_nets.kg_construct_approach = "subclass"
+        owl_nets_graph = self.owl_nets.run_owl_nets()
+        self.assertIsInstance(owl_nets_graph, Tuple)
+        self.assertIsInstance(owl_nets_graph[0], Graph)
+        self.assertEqual(len(owl_nets_graph[0]), 2940)
+        self.assertIsInstance(owl_nets_graph[1], Graph)
+        self.assertEqual(len(owl_nets_graph[0]), len(owl_nets_graph[1]))
+
+        # make sure files are written locally for each graph
+        # purified
+        nx_mdg_file = 'so_with_imports_SUBCLASS_purified_OWLNETS_NetworkxMultiDiGraph.gpickle'
+        nt_file = 'so_with_imports_SUBCLASS_purified_OWLNETS.nt'
+        dict_file = '/so_with_imports_SUBCLASS_purified_OWLNETS_decoding_dict.pkl'
+        self.assertTrue(os.path.exists(self.dir_loc_resources + '/knowledge_graphs/' + nt_file))
+        self.assertTrue(os.path.exists(self.dir_loc_resources + '/knowledge_graphs/' + nx_mdg_file))
+        self.assertTrue(os.path.exists(self.dir_loc_resources + '/knowledge_graphs' + dict_file))
+        # regular
+        nx_mdg_file = 'so_with_imports_OWLNETS_NetworkxMultiDiGraph.gpickle'
+        self.assertTrue(os.path.exists(self.dir_loc_resources + '/knowledge_graphs/so_with_imports_OWLNETS.nt'))
+        self.assertTrue(os.path.exists(self.dir_loc_resources + '/knowledge_graphs/' + nx_mdg_file))
+        self.assertTrue(os.path.exists(self.dir_loc_resources + '/knowledge_graphs'
+                                                                '/so_with_imports_OWLNETS_decoding_dict.pkl'))
+
+        return None
+
+    def test_write_out_results_instance_purified(self):
+        """Tests the run_owl_nets method."""
+
+        owl_nets_graph = self.owl_nets2.run_owl_nets()
+        self.assertIsInstance(owl_nets_graph, Tuple)
+        self.assertIsInstance(owl_nets_graph[0], Graph)
+        self.assertEqual(len(owl_nets_graph[0]), 2940)
+        self.assertIsInstance(owl_nets_graph[1], Graph)
+        self.assertTrue(len(owl_nets_graph[1]) > len(owl_nets_graph[0]))
+
+        # make sure files are written locally for each graph
+        # purified
+        nx_mdg_file = 'so_with_imports_INSTANCE_purified_OWLNETS_NetworkxMultiDiGraph.gpickle'
+        nt_file = 'so_with_imports_INSTANCE_purified_OWLNETS.nt'
+        dict_file = '/so_with_imports_INSTANCE_purified_OWLNETS_decoding_dict.pkl'
+        self.assertTrue(os.path.exists(self.dir_loc_resources + '/knowledge_graphs/' + nt_file))
+        self.assertTrue(os.path.exists(self.dir_loc_resources + '/knowledge_graphs/' + nx_mdg_file))
+        self.assertTrue(os.path.exists(self.dir_loc_resources + '/knowledge_graphs' + dict_file))
+        # regular
+        nx_mdg_file = 'so_with_imports_OWLNETS_NetworkxMultiDiGraph.gpickle'
+        self.assertTrue(os.path.exists(self.dir_loc_resources + '/knowledge_graphs/so_with_imports_OWLNETS.nt'))
+        self.assertTrue(os.path.exists(self.dir_loc_resources + '/knowledge_graphs/' + nx_mdg_file))
+        self.assertTrue(os.path.exists(self.dir_loc_resources + '/knowledge_graphs'
+                                                                '/so_with_imports_OWLNETS_decoding_dict.pkl'))
 
         return None