Repository: INCATools/dead_simple_owl_design_patterns (language: python)

Issue:
Release schema + validator (+doc gen) on PyPi
structure


```
src/schema/dosdp_schema.yaml  #  = spec/DOSDP_schema_full.yaml
src/schema/test/
src/schema/test/generic_test # Is it valid JSON schema.
src/schema/test/positive_test_set/ # Some list of passing files to test
src/schema/test/negative test_set/# Some list of failing files to test
src/dosdp/dosdp.py # wrapper script with argparse (or click)
src/dosdp/validator.py # sub module based on existing code Allow setting threshold for hard vs soft fail # based on src/dosdp/simple_pattern_tester.py
src/dosdp/document.py # sub module -  patterns as input and generates markdown doc (@matentzn has code)

# dosdp = package name
```




Release schema + validator (+doc gen) on PyPi
structure


```
src/schema/dosdp_schema.yaml  #  = spec/DOSDP_schema_full.yaml
src/schema/test/
src/schema/test/generic_test # Is it valid JSON schema.
src/schema/test/positive_test_set/ # Some list of passing files to test
src/schema/test/negative test_set/# Some list of failing files to test
src/dosdp/dosdp.py # wrapper script with argparse (or click)
src/dosdp/validator.py # sub module based on existing code Allow setting threshold for hard vs soft fail # based on src/dosdp/simple_pattern_tester.py
src/dosdp/document.py # sub module -  patterns as input and generates markdown doc (@matentzn has code)

# dosdp = package name
```




set up github.io with schema as markdown (input for mkdocs)
Aim: generate browsable, human-readable documentation of [DOSDP JSON SCHEMA](https://github.com/INCATools/dead_simple_owl_design_patterns/blob/master/spec/DOSDP_schema_full.yaml) 


- [ ] Research existing auto-documentation options - markdown output preferred, but we could use static html generator if better.
- [ ]  ...
Release schema + validator (+doc gen) on PyPi
structure


```
src/schema/dosdp_schema.yaml  #  = spec/DOSDP_schema_full.yaml
src/schema/test/
src/schema/test/generic_test # Is it valid JSON schema.
src/schema/test/positive_test_set/ # Some list of passing files to test
src/schema/test/negative test_set/# Some list of failing files to test
src/dosdp/dosdp.py # wrapper script with argparse (or click)
src/dosdp/validator.py # sub module based on existing code Allow setting threshold for hard vs soft fail # based on src/dosdp/simple_pattern_tester.py
src/dosdp/document.py # sub module -  patterns as input and generates markdown doc (@matentzn has code)

# dosdp = package name
```




Release schema + validator (+doc gen) on PyPi
structure


```
src/schema/dosdp_schema.yaml  #  = spec/DOSDP_schema_full.yaml
src/schema/test/
src/schema/test/generic_test # Is it valid JSON schema.
src/schema/test/positive_test_set/ # Some list of passing files to test
src/schema/test/negative test_set/# Some list of failing files to test
src/dosdp/dosdp.py # wrapper script with argparse (or click)
src/dosdp/validator.py # sub module based on existing code Allow setting threshold for hard vs soft fail # based on src/dosdp/simple_pattern_tester.py
src/dosdp/document.py # sub module -  patterns as input and generates markdown doc (@matentzn has code)

# dosdp = package name
```




Release schema + validator (+doc gen) on PyPi
structure


```
src/schema/dosdp_schema.yaml  #  = spec/DOSDP_schema_full.yaml
src/schema/test/
src/schema/test/generic_test # Is it valid JSON schema.
src/schema/test/positive_test_set/ # Some list of passing files to test
src/schema/test/negative test_set/# Some list of failing files to test
src/dosdp/dosdp.py # wrapper script with argparse (or click)
src/dosdp/validator.py # sub module based on existing code Allow setting threshold for hard vs soft fail # based on src/dosdp/simple_pattern_tester.py
src/dosdp/document.py # sub module -  patterns as input and generates markdown doc (@matentzn has code)

# dosdp = package name
```




Release schema + validator (+doc gen) on PyPi
structure


```
src/schema/dosdp_schema.yaml  #  = spec/DOSDP_schema_full.yaml
src/schema/test/
src/schema/test/generic_test # Is it valid JSON schema.
src/schema/test/positive_test_set/ # Some list of passing files to test
src/schema/test/negative test_set/# Some list of failing files to test
src/dosdp/dosdp.py # wrapper script with argparse (or click)
src/dosdp/validator.py # sub module based on existing code Allow setting threshold for hard vs soft fail # based on src/dosdp/simple_pattern_tester.py
src/dosdp/document.py # sub module -  patterns as input and generates markdown doc (@matentzn has code)

# dosdp = package name
```




Release schema + validator (+doc gen) on PyPi
structure


```
src/schema/dosdp_schema.yaml  #  = spec/DOSDP_schema_full.yaml
src/schema/test/
src/schema/test/generic_test # Is it valid JSON schema.
src/schema/test/positive_test_set/ # Some list of passing files to test
src/schema/test/negative test_set/# Some list of failing files to test
src/dosdp/dosdp.py # wrapper script with argparse (or click)
src/dosdp/validator.py # sub module based on existing code Allow setting threshold for hard vs soft fail # based on src/dosdp/simple_pattern_tester.py
src/dosdp/document.py # sub module -  patterns as input and generates markdown doc (@matentzn has code)

# dosdp = package name
```




Release schema + validator (+doc gen) on PyPi
structure


```
src/schema/dosdp_schema.yaml  #  = spec/DOSDP_schema_full.yaml
src/schema/test/
src/schema/test/generic_test # Is it valid JSON schema.
src/schema/test/positive_test_set/ # Some list of passing files to test
src/schema/test/negative test_set/# Some list of failing files to test
src/dosdp/dosdp.py # wrapper script with argparse (or click)
src/dosdp/validator.py # sub module based on existing code Allow setting threshold for hard vs soft fail # based on src/dosdp/simple_pattern_tester.py
src/dosdp/document.py # sub module -  patterns as input and generates markdown doc (@matentzn has code)

# dosdp = package name
```




Release schema + validator (+doc gen) on PyPi
structure


```
src/schema/dosdp_schema.yaml  #  = spec/DOSDP_schema_full.yaml
src/schema/test/
src/schema/test/generic_test # Is it valid JSON schema.
src/schema/test/positive_test_set/ # Some list of passing files to test
src/schema/test/negative test_set/# Some list of failing files to test
src/dosdp/dosdp.py # wrapper script with argparse (or click)
src/dosdp/validator.py # sub module based on existing code Allow setting threshold for hard vs soft fail # based on src/dosdp/simple_pattern_tester.py
src/dosdp/document.py # sub module -  patterns as input and generates markdown doc (@matentzn has code)

# dosdp = package name
```




Simplify generated schema doc
The [current autogenerated schema doc](https://github.com/INCATools/dead_simple_owl_design_patterns/blob/pypi_release/src/schema/dosdp_schema.md) is overly complicated in the sections that deference definitions.

This would be sufficient:

- **`equivalentTo`**:
   * **`text`** *(string)*: A print format string in OWL Manchester syntax. Each entry must correspond to an entry in o the name of a var in the var field of the pattern. Entries in single quotes must correspond to the labels of entries in owl_entity dictionaries (classes, relations, dataProperties).
   *  **`vars`** *(list)*: An ordered list of variables for substitution into the accompanying print format string. Each entry must correspond to the name of a variable specified in either the 'vars' field or the data_var field of the pattern
   *  **`annotations`** *(list)*
       *  **`annotationProperty`** *(string)*: A string corresponding to the rdfs:label of an owl annotation property. If the annotation property has no label, the shortForm ID should be used. The annotation property must be listed in the annotation property dictionary.'.
       *  **`text`** *(string)*: A print format string.
       *  **`vars`** *(list)*: An ordered list of variables for substitution into the accompanying print format string. Each entry must correspond to the name of a variable specified in either the 'vars' field or the data_var field of the pattern. Where an OWL entity is specified, the label for the OWL entity should be used in the substitution.  An empty var list can be specified simply by leaving this field out.
      * **`annotations` (list)**
         * ...


Note lack of reference names internal to pattern (no printf_owl).  This makes sense - a semantically identical pattern could be written with all of the nested structure explicitly repeated under every primary property.  I don't see any need for quoting.

I may be wrong, but I also think it is sufficient to just list type (string or list) in brackets, inferring that a list of sets of the keys nested underneath are permitted, rather than including an 'items' keyword.

Deal with recursive structure implicitly by using an ellipsis at the first level of recursion.


Simplify generated schema doc
The [current autogenerated schema doc](https://github.com/INCATools/dead_simple_owl_design_patterns/blob/pypi_release/src/schema/dosdp_schema.md) is overly complicated in the sections that deference definitions.

This would be sufficient:

- **`equivalentTo`**:
   * **`text`** *(string)*: A print format string in OWL Manchester syntax. Each entry must correspond to an entry in o the name of a var in the var field of the pattern. Entries in single quotes must correspond to the labels of entries in owl_entity dictionaries (classes, relations, dataProperties).
   *  **`vars`** *(list)*: An ordered list of variables for substitution into the accompanying print format string. Each entry must correspond to the name of a variable specified in either the 'vars' field or the data_var field of the pattern
   *  **`annotations`** *(list)*
       *  **`annotationProperty`** *(string)*: A string corresponding to the rdfs:label of an owl annotation property. If the annotation property has no label, the shortForm ID should be used. The annotation property must be listed in the annotation property dictionary.'.
       *  **`text`** *(string)*: A print format string.
       *  **`vars`** *(list)*: An ordered list of variables for substitution into the accompanying print format string. Each entry must correspond to the name of a variable specified in either the 'vars' field or the data_var field of the pattern. Where an OWL entity is specified, the label for the OWL entity should be used in the substitution.  An empty var list can be specified simply by leaving this field out.
      * **`annotations` (list)**
         * ...


Note lack of reference names internal to pattern (no printf_owl).  This makes sense - a semantically identical pattern could be written with all of the nested structure explicitly repeated under every primary property.  I don't see any need for quoting.

I may be wrong, but I also think it is sufficient to just list type (string or list) in brackets, inferring that a list of sets of the keys nested underneath are permitted, rather than including an 'items' keyword.

Deal with recursive structure implicitly by using an ellipsis at the first level of recursion.


Simplify generated schema doc
The [current autogenerated schema doc](https://github.com/INCATools/dead_simple_owl_design_patterns/blob/pypi_release/src/schema/dosdp_schema.md) is overly complicated in the sections that deference definitions.

This would be sufficient:

- **`equivalentTo`**:
   * **`text`** *(string)*: A print format string in OWL Manchester syntax. Each entry must correspond to an entry in o the name of a var in the var field of the pattern. Entries in single quotes must correspond to the labels of entries in owl_entity dictionaries (classes, relations, dataProperties).
   *  **`vars`** *(list)*: An ordered list of variables for substitution into the accompanying print format string. Each entry must correspond to the name of a variable specified in either the 'vars' field or the data_var field of the pattern
   *  **`annotations`** *(list)*
       *  **`annotationProperty`** *(string)*: A string corresponding to the rdfs:label of an owl annotation property. If the annotation property has no label, the shortForm ID should be used. The annotation property must be listed in the annotation property dictionary.'.
       *  **`text`** *(string)*: A print format string.
       *  **`vars`** *(list)*: An ordered list of variables for substitution into the accompanying print format string. Each entry must correspond to the name of a variable specified in either the 'vars' field or the data_var field of the pattern. Where an OWL entity is specified, the label for the OWL entity should be used in the substitution.  An empty var list can be specified simply by leaving this field out.
      * **`annotations` (list)**
         * ...


Note lack of reference names internal to pattern (no printf_owl).  This makes sense - a semantically identical pattern could be written with all of the nested structure explicitly repeated under every primary property.  I don't see any need for quoting.

I may be wrong, but I also think it is sufficient to just list type (string or list) in brackets, inferring that a list of sets of the keys nested underneath are permitted, rather than including an 'items' keyword.

Deal with recursive structure implicitly by using an ellipsis at the first level of recursion.


Simplify generated schema doc
The [current autogenerated schema doc](https://github.com/INCATools/dead_simple_owl_design_patterns/blob/pypi_release/src/schema/dosdp_schema.md) is overly complicated in the sections that deference definitions.

This would be sufficient:

- **`equivalentTo`**:
   * **`text`** *(string)*: A print format string in OWL Manchester syntax. Each entry must correspond to an entry in o the name of a var in the var field of the pattern. Entries in single quotes must correspond to the labels of entries in owl_entity dictionaries (classes, relations, dataProperties).
   *  **`vars`** *(list)*: An ordered list of variables for substitution into the accompanying print format string. Each entry must correspond to the name of a variable specified in either the 'vars' field or the data_var field of the pattern
   *  **`annotations`** *(list)*
       *  **`annotationProperty`** *(string)*: A string corresponding to the rdfs:label of an owl annotation property. If the annotation property has no label, the shortForm ID should be used. The annotation property must be listed in the annotation property dictionary.'.
       *  **`text`** *(string)*: A print format string.
       *  **`vars`** *(list)*: An ordered list of variables for substitution into the accompanying print format string. Each entry must correspond to the name of a variable specified in either the 'vars' field or the data_var field of the pattern. Where an OWL entity is specified, the label for the OWL entity should be used in the substitution.  An empty var list can be specified simply by leaving this field out.
      * **`annotations` (list)**
         * ...


Note lack of reference names internal to pattern (no printf_owl).  This makes sense - a semantically identical pattern could be written with all of the nested structure explicitly repeated under every primary property.  I don't see any need for quoting.

I may be wrong, but I also think it is sufficient to just list type (string or list) in brackets, inferring that a list of sets of the keys nested underneath are permitted, rather than including an 'items' keyword.

Deal with recursive structure implicitly by using an ellipsis at the first level of recursion.


Release schema + validator (+doc gen) on PyPi
structure


```
src/schema/dosdp_schema.yaml  #  = spec/DOSDP_schema_full.yaml
src/schema/test/
src/schema/test/generic_test # Is it valid JSON schema.
src/schema/test/positive_test_set/ # Some list of passing files to test
src/schema/test/negative test_set/# Some list of failing files to test
src/dosdp/dosdp.py # wrapper script with argparse (or click)
src/dosdp/validator.py # sub module based on existing code Allow setting threshold for hard vs soft fail # based on src/dosdp/simple_pattern_tester.py
src/dosdp/document.py # sub module -  patterns as input and generates markdown doc (@matentzn has code)

# dosdp = package name
```






Discussion / hints:
Lets try to adhere to good CLI practices: https://clig.dev/

Mainly: 
- use flags over args
- use verbs for commands

In case test config is needed: https://github.com/mapping-commons/sssom-py/blob/master/tests/test_config.yaml
Also, can we make sure we can use these as a library in python directly:

```
from dosdp import validate
validate("test.yaml")
```
Lets try to adhere to good CLI practices: https://clig.dev/

Mainly: 
- use flags over args
- use verbs for commands

In case test config is needed: https://github.com/mapping-commons/sssom-py/blob/master/tests/test_config.yaml
Also, can we make sure we can use these as a library in python directly:

```
from dosdp import validate
validate("test.yaml")
```

Lets try to adhere to good CLI practices: https://clig.dev/

Mainly: 
- use flags over args
- use verbs for commands

In case test config is needed: https://github.com/mapping-commons/sssom-py/blob/master/tests/test_config.yaml
Also, can we make sure we can use these as a library in python directly:

```
from dosdp import validate
validate("test.yaml")
```
Lets try to adhere to good CLI practices: https://clig.dev/

Mainly: 
- use flags over args
- use verbs for commands

In case test config is needed: https://github.com/mapping-commons/sssom-py/blob/master/tests/test_config.yaml
Also, can we make sure we can use these as a library in python directly:

```
from dosdp import validate
validate("test.yaml")
```
Lets try to adhere to good CLI practices: https://clig.dev/

Mainly: 
- use flags over args
- use verbs for commands

In case test config is needed: https://github.com/mapping-commons/sssom-py/blob/master/tests/test_config.yaml
Also, can we make sure we can use these as a library in python directly:

```
from dosdp import validate
validate("test.yaml")
```
Lets try to adhere to good CLI practices: https://clig.dev/

Mainly: 
- use flags over args
- use verbs for commands

In case test config is needed: https://github.com/mapping-commons/sssom-py/blob/master/tests/test_config.yaml
Also, can we make sure we can use these as a library in python directly:

```
from dosdp import validate
validate("test.yaml")
```
Lets try to adhere to good CLI practices: https://clig.dev/

Mainly: 
- use flags over args
- use verbs for commands

In case test config is needed: https://github.com/mapping-commons/sssom-py/blob/master/tests/test_config.yaml
Also, can we make sure we can use these as a library in python directly:

```
from dosdp import validate
validate("test.yaml")
```
Lets try to adhere to good CLI practices: https://clig.dev/

Mainly: 
- use flags over args
- use verbs for commands

In case test config is needed: https://github.com/mapping-commons/sssom-py/blob/master/tests/test_config.yaml
Also, can we make sure we can use these as a library in python directly:

```
from dosdp import validate
validate("test.yaml")
```
Lets try to adhere to good CLI practices: https://clig.dev/

Mainly: 
- use flags over args
- use verbs for commands

In case test config is needed: https://github.com/mapping-commons/sssom-py/blob/master/tests/test_config.yaml
Also, can we make sure we can use these as a library in python directly:

```
from dosdp import validate
validate("test.yaml")
```




Lets try to adhere to good CLI practices: https://clig.dev/

Mainly: 
- use flags over args
- use verbs for commands

In case test config is needed: https://github.com/mapping-commons/sssom-py/blob/master/tests/test_config.yaml
Also, can we make sure we can use these as a library in python directly:

```
from dosdp import validate
validate("test.yaml")
```

Fix patch:
diff --git a/.github/workflows/publish_to_pypi.yaml b/.github/workflows/publish_to_pypi.yaml
new file mode 100644
index 0000000..4ad5f7c
--- /dev/null
+++ b/.github/workflows/publish_to_pypi.yaml
@@ -0,0 +1,43 @@
+name: Publish Python distributions to PyPI
+
+on:
+  # Allows you to run this workflow manually from the Actions tab
+  workflow_dispatch:
+  release:
+    types: [created]
+  # For testing purpose, delete this trigger afterwards
+#  push:
+#    paths:
+#      - '.github/workflows/publish_to_pypi.yaml'
+
+jobs:
+  build-and-publish:
+    name: Publish Python distributions to PyPI
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v2
+    - name: Set up Python 3.8
+      uses: actions/setup-python@v2
+      with:
+        python-version: 3.8
+    - name: Install Deployment Tools
+      run: |
+        python -m pip install --upgrade pip
+        pip install setuptools wheel twine
+    - name: Install Project Dependencies
+      run: |
+        pip install -r requirements.txt
+    - name: Auto-Generate schema documentation
+      run: PYTHONPATH=./src:$PYTHONPATH python -m dosdp document --schema -o ./src/schema/
+    - name: Package Distribution
+      run: >-
+        python
+        setup.py
+        sdist
+        bdist_wheel
+    - name: Deploy Package
+      env:
+        TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
+        TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
+      run: |
+        twine upload --repository-url https://test.pypi.org/legacy/ dist/*
\ No newline at end of file
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..a3f9723
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,1 @@
+include src/schema/*.yaml src/schema/*.ini src/schema/*.md
\ No newline at end of file
diff --git a/README.md b/README.md
index 5576a00..e535046 100644
--- a/README.md
+++ b/README.md
@@ -29,67 +29,30 @@ Human readability and editability requires that Manchester syntax be written usi
 
 ## Specification:
 
-[Full specification using JSON-schema](https://github.com/dosumis/dead_simple_owl_design_patterns/tree/master/spec/README.md).
+See [full specification documentation](https://github.com/INCATools/dead_simple_owl_design_patterns/tree/master/src/schema/dosdp_schema.md)
 
-The following is a quick guide to commonly used fields, with an emphasis on OBO-specific (derived) fields:
+## Setup
 
-Manchester syntax expressions use names (labels).  These are always single quoted inside an expression that is double quoted. (Note single quotes in term names must be escaped).
-
-* __pattern\_name__ (string): the name of the pattern. No spaces or special characters allowed.
-* __description__(string): Text describing the pattern and its uses.  For use in documentation - not in OWL files.
-* __classes__ (associative array): hash lookup for OWL classes used in the pattern. key = name, value = ID
-* __relations__ (associative array): hash lookup for OWL object properties (relations) used in the pattern. key = name, value = ID
-* __vars__ (associative array): a hash lookup for vars in the pattern, key = var name, value = range expressed as manchester syntax.
-* __name__  (associative array): 
-  * __text__ (string): sprintf label text
-  * __vars__ (string): list of vars for interpolation of class names into sprintf of text.
-* __def__ (associative array): 
-  * __text__ (string): sprintf definition text.  
-  * __vars__ (array): List of vars for interpolation of class names into sprintf of text. 
-* __comment__(string)
-* __EquivalentTo__ (associative array): 
-  * __text__ (string): Sprintf OWL Manchester syntax string.  All OWL entities must be quoted; %s not quoted).
-  * __vars__ (array): List of vars for interpolation into sprintf owl MS text.
-
-
-Draft yaml example - [import_into_cell](patterns/import_into_cell.yaml)
-
-Draft json example - [import_into_cell](patterns/import_into_cell.json)
+```
+pip install dosdp
+```
 
+See https://pypi.org/project/dosdp/
 
 ## Validator spec
 
-### Basic validation
+See [validator documentation](https://github.com/INCATools/dead_simple_owl_design_patterns/tree/master/src/dosdp/validator.md)
 
-1. test converstion of YAML to JSON
-2. validate against JSON schema (e.g. see [test_schema.py](https://github.com/dosumis/dead_simple_owl_design_patterns/blob/master/spec/test_schema.py))
-
-
-### Recommendations for additional validation outside of JSON schema:
-
-* For all printf fields, 
-  * test length of var array matches number of interpolation slots in string
-  * test that all var names are valid for the pattern
-* For printf_owl fields:
-   * Check quoted names in the printf field correspond to dictionary entries in the pattern.
-  
-* Tests against referenced ontologies:
-   * Are the entities in the dictionaries present and non-obsolete in the latest releases of the relevant ontologies?
-   * Are the readable names up to date ?
-   * For all printf manchester syntax strings: Is a valid Manchester syntax string generated when variable slots are filled using the range for each variable?
-   
-* Validation when creating instances:
-   * Are values for variable slots present and non-obsolete in the latest releases of the relevant ontologies?
-   * Are values for the variable slots in the specified range?
+## Documentation generation spec
 
+See [documentation_generation](https://github.com/INCATools/dead_simple_owl_design_patterns/tree/master/src/dosdp/document/document.md)
 
 ## Implementation
 
 The aim of this project is to specify a simple design pattern system that can easily be consumed, whatever your code base.
 This repository includes a simple Python validator (src/simple_pattern_tester.py).
 
-For implementation, we recommend [dosdp-tools](https://github.com/balhoff/dosdp-tools).
-
+For implementation, we recommend [dosdp-tools](https://github.com/INCATools/dosdp-tools).
 
 ## Uses
 
@@ -97,3 +60,5 @@ For implementation, we recommend [dosdp-tools](https://github.com/balhoff/dosdp-
  * [OBA](http://obofoundry.org/ontology/oba.html): oba [patterns/](https://github.com/obophenotype/bio-attribute-ontology/tree/master/src/ontology/patterns)
  * draft environmental conditions ontology: ecto [patterns/](https://github.com/cmungall/environmental-conditions/tree/master/src/patterns)
  * [Uberon](http://obofoundry.org/ontology/uberon.html): uberon [patterns/](https://github.com/obophenotype/uberon/tree/master/patterns)
+ * [uPheno](https://github.com/obophenotype/upheno)
+ * [Mondo](http://www.obofoundry.org/ontology/mondo.html)
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..b5a3c46
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,6 @@
+[build-system]
+requires = [
+    "setuptools>=42",
+    "wheel"
+]
+build-backend = "setuptools.build_meta"
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
index ab45728..7553fc1 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -3,5 +3,7 @@ jsonschema
 requests
 jsonpath_rw
 ruamel.yaml
-
+jsonschema2md
+pandas
+tabulate
 
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..8e894e1
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,34 @@
+import pathlib
+from setuptools import setup
+
+# The directory containing this file
+HERE = pathlib.Path(__file__).parent
+
+# The text of the README file
+README = (HERE / "README.md").read_text()
+
+# This call to setup() does all the work
+setup(
+    name="dosdp",
+    version="0.1.7.dev1",
+    description="The aim of this project is to specify a simple OWL design pattern system that can easily be consumed, whatever your code base.",
+    long_description=README,
+    long_description_content_type="text/markdown",
+    url="https://github.com/INCATools/dead_simple_owl_design_patterns",
+    author="INCATools",
+    license="GPL-3.0 License",
+    classifiers=[
+        "Programming Language :: Python :: 3",
+        "License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
+        "Operating System :: OS Independent",
+    ],
+    package_dir={'': 'src'},
+    packages=["dosdp", "schema", "dosdp.document", "dosdp.document.pattern", "dosdp.document.schema"],
+    include_package_data=True,
+    install_requires=["PyYAML", "jsonschema", "requests", "jsonpath_rw", "ruamel.yaml", "jsonschema2md", "pandas"],
+    entry_points={
+        "console_scripts": [
+            "dosdp=dosdp.__main__:main",
+        ]
+    },
+)
diff --git a/spec/draft/DOSDP_schema_OBO.yaml b/src/__init__.py
similarity index 100%
rename from spec/draft/DOSDP_schema_OBO.yaml
rename to src/__init__.py
diff --git a/lib/README.md b/src/attic/lib/README.md
similarity index 100%
rename from lib/README.md
rename to src/attic/lib/README.md
diff --git a/patterns/adenine_import_across_plasma_membrane.md b/src/attic/patterns/adenine_import_across_plasma_membrane.md
similarity index 100%
rename from patterns/adenine_import_across_plasma_membrane.md
rename to src/attic/patterns/adenine_import_across_plasma_membrane.md
diff --git a/patterns/anchored_membrane_component.json b/src/attic/patterns/anchored_membrane_component.json
similarity index 100%
rename from patterns/anchored_membrane_component.json
rename to src/attic/patterns/anchored_membrane_component.json
diff --git a/patterns/anchored_membrane_component.md b/src/attic/patterns/anchored_membrane_component.md
similarity index 100%
rename from patterns/anchored_membrane_component.md
rename to src/attic/patterns/anchored_membrane_component.md
diff --git a/patterns/anchored_membrane_component.yaml b/src/attic/patterns/anchored_membrane_component.yaml
similarity index 100%
rename from patterns/anchored_membrane_component.yaml
rename to src/attic/patterns/anchored_membrane_component.yaml
diff --git a/patterns/bounding_membrane_of_organelle.json b/src/attic/patterns/bounding_membrane_of_organelle.json
similarity index 100%
rename from patterns/bounding_membrane_of_organelle.json
rename to src/attic/patterns/bounding_membrane_of_organelle.json
diff --git a/patterns/bounding_membrane_of_organelle.md b/src/attic/patterns/bounding_membrane_of_organelle.md
similarity index 100%
rename from patterns/bounding_membrane_of_organelle.md
rename to src/attic/patterns/bounding_membrane_of_organelle.md
diff --git a/patterns/bounding_membrane_of_organelle.yaml b/src/attic/patterns/bounding_membrane_of_organelle.yaml
similarity index 100%
rename from patterns/bounding_membrane_of_organelle.yaml
rename to src/attic/patterns/bounding_membrane_of_organelle.yaml
diff --git a/patterns/envenomation.json b/src/attic/patterns/envenomation.json
similarity index 100%
rename from patterns/envenomation.json
rename to src/attic/patterns/envenomation.json
diff --git a/patterns/envenomation.md b/src/attic/patterns/envenomation.md
similarity index 100%
rename from patterns/envenomation.md
rename to src/attic/patterns/envenomation.md
diff --git a/patterns/envenomation.yaml b/src/attic/patterns/envenomation.yaml
similarity index 100%
rename from patterns/envenomation.yaml
rename to src/attic/patterns/envenomation.yaml
diff --git a/patterns/export_across_membrane.json b/src/attic/patterns/export_across_membrane.json
similarity index 100%
rename from patterns/export_across_membrane.json
rename to src/attic/patterns/export_across_membrane.json
diff --git a/patterns/export_across_membrane.md b/src/attic/patterns/export_across_membrane.md
similarity index 100%
rename from patterns/export_across_membrane.md
rename to src/attic/patterns/export_across_membrane.md
diff --git a/patterns/export_across_membrane.yaml b/src/attic/patterns/export_across_membrane.yaml
similarity index 100%
rename from patterns/export_across_membrane.yaml
rename to src/attic/patterns/export_across_membrane.yaml
diff --git a/patterns/expression_pattern.json b/src/attic/patterns/expression_pattern.json
similarity index 100%
rename from patterns/expression_pattern.json
rename to src/attic/patterns/expression_pattern.json
diff --git a/patterns/expression_pattern.md b/src/attic/patterns/expression_pattern.md
similarity index 100%
rename from patterns/expression_pattern.md
rename to src/attic/patterns/expression_pattern.md
diff --git a/patterns/expression_pattern.yaml b/src/attic/patterns/expression_pattern.yaml
similarity index 100%
rename from patterns/expression_pattern.yaml
rename to src/attic/patterns/expression_pattern.yaml
diff --git a/patterns/extrinsic_membrane_component.json b/src/attic/patterns/extrinsic_membrane_component.json
similarity index 100%
rename from patterns/extrinsic_membrane_component.json
rename to src/attic/patterns/extrinsic_membrane_component.json
diff --git a/patterns/extrinsic_membrane_component.md b/src/attic/patterns/extrinsic_membrane_component.md
similarity index 100%
rename from patterns/extrinsic_membrane_component.md
rename to src/attic/patterns/extrinsic_membrane_component.md
diff --git a/patterns/extrinsic_membrane_component.yaml b/src/attic/patterns/extrinsic_membrane_component.yaml
similarity index 100%
rename from patterns/extrinsic_membrane_component.yaml
rename to src/attic/patterns/extrinsic_membrane_component.yaml
diff --git a/patterns/generic_go_pattern_outline.yaml b/src/attic/patterns/generic_go_pattern_outline.yaml
similarity index 100%
rename from patterns/generic_go_pattern_outline.yaml
rename to src/attic/patterns/generic_go_pattern_outline.yaml
diff --git a/patterns/import_across_membrane.json b/src/attic/patterns/import_across_membrane.json
similarity index 100%
rename from patterns/import_across_membrane.json
rename to src/attic/patterns/import_across_membrane.json
diff --git a/patterns/import_across_membrane.md b/src/attic/patterns/import_across_membrane.md
similarity index 100%
rename from patterns/import_across_membrane.md
rename to src/attic/patterns/import_across_membrane.md
diff --git a/patterns/import_across_membrane.yaml b/src/attic/patterns/import_across_membrane.yaml
similarity index 100%
rename from patterns/import_across_membrane.yaml
rename to src/attic/patterns/import_across_membrane.yaml
diff --git a/patterns/import_across_plasma_membrane.json b/src/attic/patterns/import_across_plasma_membrane.json
similarity index 100%
rename from patterns/import_across_plasma_membrane.json
rename to src/attic/patterns/import_across_plasma_membrane.json
diff --git a/patterns/import_across_plasma_membrane.md b/src/attic/patterns/import_across_plasma_membrane.md
similarity index 100%
rename from patterns/import_across_plasma_membrane.md
rename to src/attic/patterns/import_across_plasma_membrane.md
diff --git a/patterns/import_across_plasma_membrane.yaml b/src/attic/patterns/import_across_plasma_membrane.yaml
similarity index 100%
rename from patterns/import_across_plasma_membrane.yaml
rename to src/attic/patterns/import_across_plasma_membrane.yaml
diff --git a/patterns/import_into_cell.json b/src/attic/patterns/import_into_cell.json
similarity index 100%
rename from patterns/import_into_cell.json
rename to src/attic/patterns/import_into_cell.json
diff --git a/patterns/import_into_cell.md b/src/attic/patterns/import_into_cell.md
similarity index 100%
rename from patterns/import_into_cell.md
rename to src/attic/patterns/import_into_cell.md
diff --git a/patterns/import_into_cell.yaml b/src/attic/patterns/import_into_cell.yaml
similarity index 100%
rename from patterns/import_into_cell.yaml
rename to src/attic/patterns/import_into_cell.yaml
diff --git a/patterns/integral_membrane_component.json b/src/attic/patterns/integral_membrane_component.json
similarity index 100%
rename from patterns/integral_membrane_component.json
rename to src/attic/patterns/integral_membrane_component.json
diff --git a/patterns/integral_membrane_component.md b/src/attic/patterns/integral_membrane_component.md
similarity index 100%
rename from patterns/integral_membrane_component.md
rename to src/attic/patterns/integral_membrane_component.md
diff --git a/patterns/integral_membrane_component.yaml b/src/attic/patterns/integral_membrane_component.yaml
similarity index 100%
rename from patterns/integral_membrane_component.yaml
rename to src/attic/patterns/integral_membrane_component.yaml
diff --git a/patterns/intrinsic_membrane_component.json b/src/attic/patterns/intrinsic_membrane_component.json
similarity index 100%
rename from patterns/intrinsic_membrane_component.json
rename to src/attic/patterns/intrinsic_membrane_component.json
diff --git a/patterns/intrinsic_membrane_component.md b/src/attic/patterns/intrinsic_membrane_component.md
similarity index 100%
rename from patterns/intrinsic_membrane_component.md
rename to src/attic/patterns/intrinsic_membrane_component.md
diff --git a/patterns/intrinsic_membrane_component.yaml b/src/attic/patterns/intrinsic_membrane_component.yaml
similarity index 100%
rename from patterns/intrinsic_membrane_component.yaml
rename to src/attic/patterns/intrinsic_membrane_component.yaml
diff --git a/patterns/membrane_receptor_activity_draft.yaml b/src/attic/patterns/membrane_receptor_activity_draft.yaml
similarity index 100%
rename from patterns/membrane_receptor_activity_draft.yaml
rename to src/attic/patterns/membrane_receptor_activity_draft.yaml
diff --git a/patterns/membrane_spanning_component.json b/src/attic/patterns/membrane_spanning_component.json
similarity index 100%
rename from patterns/membrane_spanning_component.json
rename to src/attic/patterns/membrane_spanning_component.json
diff --git a/patterns/membrane_spanning_component.md b/src/attic/patterns/membrane_spanning_component.md
similarity index 100%
rename from patterns/membrane_spanning_component.md
rename to src/attic/patterns/membrane_spanning_component.md
diff --git a/patterns/membrane_spanning_component.yaml b/src/attic/patterns/membrane_spanning_component.yaml
similarity index 100%
rename from patterns/membrane_spanning_component.yaml
rename to src/attic/patterns/membrane_spanning_component.yaml
diff --git a/patterns/transmembrane_import_into_cell.json b/src/attic/patterns/transmembrane_import_into_cell.json
similarity index 100%
rename from patterns/transmembrane_import_into_cell.json
rename to src/attic/patterns/transmembrane_import_into_cell.json
diff --git a/patterns/transmembrane_import_into_cell.md b/src/attic/patterns/transmembrane_import_into_cell.md
similarity index 100%
rename from patterns/transmembrane_import_into_cell.md
rename to src/attic/patterns/transmembrane_import_into_cell.md
diff --git a/patterns/transmembrane_import_into_cell.yaml b/src/attic/patterns/transmembrane_import_into_cell.yaml
similarity index 100%
rename from patterns/transmembrane_import_into_cell.yaml
rename to src/attic/patterns/transmembrane_import_into_cell.yaml
diff --git a/patterns/var_spec_examples/glucose_import_into_cell.yaml b/src/attic/patterns/var_spec_examples/glucose_import_into_cell.yaml
similarity index 100%
rename from patterns/var_spec_examples/glucose_import_into_cell.yaml
rename to src/attic/patterns/var_spec_examples/glucose_import_into_cell.yaml
diff --git a/spec/DOSDP_schema_core.yaml b/src/attic/spec/DOSDP_schema_core.yaml
similarity index 100%
rename from spec/DOSDP_schema_core.yaml
rename to src/attic/spec/DOSDP_schema_core.yaml
diff --git a/spec/DOSDP_schema_full.json b/src/attic/spec/DOSDP_schema_full.json
similarity index 100%
rename from spec/DOSDP_schema_full.json
rename to src/attic/spec/DOSDP_schema_full.json
diff --git a/spec/DOSDP_schema_full.yaml b/src/attic/spec/DOSDP_schema_full.yaml
similarity index 100%
rename from spec/DOSDP_schema_full.yaml
rename to src/attic/spec/DOSDP_schema_full.yaml
diff --git a/spec/OBO_import.yml b/src/attic/spec/OBO_import.yml
similarity index 100%
rename from spec/OBO_import.yml
rename to src/attic/spec/OBO_import.yml
diff --git a/spec/README.md b/src/attic/spec/README.md
similarity index 100%
rename from spec/README.md
rename to src/attic/spec/README.md
diff --git a/spec/attic/DOSDP_OBO_fields.json b/src/attic/spec/attic/DOSDP_OBO_fields.json
similarity index 100%
rename from spec/attic/DOSDP_OBO_fields.json
rename to src/attic/spec/attic/DOSDP_OBO_fields.json
diff --git a/spec/attic/DOSDP_OBO_fields.yaml b/src/attic/spec/attic/DOSDP_OBO_fields.yaml
similarity index 100%
rename from spec/attic/DOSDP_OBO_fields.yaml
rename to src/attic/spec/attic/DOSDP_OBO_fields.yaml
diff --git a/spec/dosdp_tsv_schema.md b/src/attic/spec/dosdp_tsv_schema.md
similarity index 100%
rename from spec/dosdp_tsv_schema.md
rename to src/attic/spec/dosdp_tsv_schema.md
diff --git a/spec/draft/DOSDP_schema_core.yaml b/src/attic/spec/draft/DOSDP_schema_OBO.yaml
similarity index 100%
rename from spec/draft/DOSDP_schema_core.yaml
rename to src/attic/spec/draft/DOSDP_schema_OBO.yaml
diff --git a/src/attic/spec/draft/DOSDP_schema_core.yaml b/src/attic/spec/draft/DOSDP_schema_core.yaml
new file mode 100644
index 0000000..e69de29
diff --git a/src/dosdp/__init__.py b/src/dosdp/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/dosdp/__main__.py b/src/dosdp/__main__.py
new file mode 100644
index 0000000..7010ab5
--- /dev/null
+++ b/src/dosdp/__main__.py
@@ -0,0 +1,47 @@
+import argparse
+import sys
+import pathlib
+import os
+import logging
+from dosdp import validator
+from dosdp.document import document
+
+logging.basicConfig(level=logging.INFO)
+
+
+def main():
+    parser = argparse.ArgumentParser(prog="dosdp", description='DOS-DP exchange format cli interface.')
+    subparsers = parser.add_subparsers(help='Available dosdp actions', dest='action')
+
+    parser_validate = subparsers.add_parser("validate",
+                                            description="The validate parser",
+                                            help="Validates given argument if it is a yaml/yaml file. If argument is a "
+                                                 "folder, validates all pattern files located in the given directory.")
+    parser_validate.add_argument('-i', '--input', action='store', type=pathlib.Path, required=True)
+
+    parser_document = subparsers.add_parser("document", add_help=False,
+                                            description="The document generation parser",
+                                            help="Generates documentation for the given YAML schema.")
+    parser_document.add_argument('-s', '--schema', action='store_true')
+    parser_document.add_argument('-i', '--input', action='store', type=pathlib.Path)
+    parser_document.add_argument('-o', '--output', action='store', type=pathlib.Path)
+    parser_document.add_argument('-d', '--data', action='store', type=pathlib.Path)
+
+    args = parser.parse_args()
+
+    if args.action == "validate":
+        is_valid = validator.validate(str(args.input))
+        if not is_valid:
+            sys.exit(1)
+    elif args.action == "document":
+        if 'schema' in args and args.schema:
+            document.generate_schema_documentation(args.output)
+        elif 'input' in args:
+            document.generate_pattern_documentation(str(args.input), args.output, args.data)
+        else:
+            logging.error("Please use '--schema' to generate schema documentation "
+                          "or '--input' for pattern documentation.")
+
+
+if __name__ == "__main__":
+    main()
diff --git a/src/dosdp/document/__init__.py b/src/dosdp/document/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/dosdp/document/document.md b/src/dosdp/document/document.md
new file mode 100644
index 0000000..9fea2ed
--- /dev/null
+++ b/src/dosdp/document/document.md
@@ -0,0 +1,35 @@
+# DOSDP Documentation Generation
+
+DODSP provides automatic documentation generation service for both pattern files and the dosdp schema itself.
+
+### Pattern Documentation
+
+DOSDP provides several CLI interfaces for automatic pattern generation.
+
+```sh
+$ dosdp document -i pattern.yaml
+$ dosdp document -i pattern.yaml -o pattern.md
+$ dosdp document -i pattern.yaml -o 'output folder'
+
+$ dosdp document -i 'pattern folder' -o 'output folder'
+
+$ dosdp document -i pattern.yaml -d 'sample data folder' -o pattern.md
+$ dosdp document -i 'pattern folder' -d 'sample data folder' -o 'output folder'
+```
+
+A [sample dosdp pattern](https://github.com/obophenotype/cell-ontology/blob/master/src/patterns/dosdp-patterns/cellBearerOfQuality.yaml) and its [auto generated documentation](https://github.com/obophenotype/cell-ontology/blob/master/docs/patterns/cellBearerOfQuality.md) can be seen in the cell ontology.
+
+An additional [overview document](https://github.com/obophenotype/cell-ontology/blob/master/docs/patterns/overview.md) generated to present a summary of the patterns, if a directory is provided as input.
+
+If the optional `sample data folder` parameter is provided, a [data preview](https://github.com/obophenotype/cell-ontology/blob/master/docs/patterns/overview.md#data-preview) is generated in both the pattern and overview documents. To achieve this, the pattern file name and the data file must match.
+
+### DOSDP Schema Documentation
+
+With each version release, DOSDP generates and  publishes a schema documentation. 
+
+```sh
+$ dosdp document --schema
+$ dosdp document --schema -o <schema.md>
+```
+
+Generates md formatted documentation for the [schema](https://github.com/INCATools/dead_simple_owl_design_patterns/tree/master/src/schema/dosdp_schema.md) exists in the dosdp package. An output document location can be optionally identified to specify documentation location or current folder (os current working directory) is used by default.
\ No newline at end of file
diff --git a/src/dosdp/document/document.py b/src/dosdp/document/document.py
new file mode 100644
index 0000000..447ecf9
--- /dev/null
+++ b/src/dosdp/document/document.py
@@ -0,0 +1,68 @@
+import os
+import glob
+import logging
+from ruamel.yaml import YAML, YAMLError
+from dosdp.document.schema import schema_create_docs
+from dosdp.document.pattern import patterns_create_docs, patterns_create_overview
+
+
+logging.basicConfig(level=logging.INFO)
+
+
+def generate_schema_documentation(md_location=None):
+    """
+    Generates md formatted documentation for the schema exists in the dosdp package. An output md_location can be
+    optionally identified to specify documentation location or current folder is used by default.
+    """
+    logging.info("Documenting dosdp schema.")
+    if md_location is None:
+        md_location = os.getcwd()
+
+    schema_create_docs.generate_schema_documentation(md_output=md_location)
+
+
+def generate_pattern_documentation(yaml_location, md_location=None, sample_data_dir=None):
+    """
+    Creates md formatted documentation for the given pattern file in the given location.
+
+    Args:
+         yaml_location: pattern file location
+         md_location: If the yaml_location is a directory, expects md_location parameter to be a folder as well
+         and creates documentation in it. If the yaml_location is a file, md_location can be a folder or file.
+         If md_location is not given at all, documentation is generated next to the pattern file.
+         sample_data_dir: Optional parameter for the sample date folder to read sample tabular data.
+    """
+    logging.info("Documenting pattern file: " + yaml_location)
+    if md_location is None:
+        if yaml_location.endswith(".yaml"):
+            md_location = str(yaml_location).split(".yaml")[0] + ".md"
+        else:
+            md_location = str(yaml_location).split(".yml")[0] + ".md"
+
+    if os.path.isdir(yaml_location):
+        if not yaml_location.endswith(os.path.sep):
+            yaml_location += os.path.sep
+        pattern_docs = glob.glob(yaml_location + "*.yaml")
+        pattern_docs.extend(glob.glob(yaml_location + "*.yml"))
+        if not os.path.isdir(md_location):
+            logging.error("If input parameter is a folder, output parameter also should be a folder.", )
+        else:
+            for pattern_doc in pattern_docs:
+                create_indv_pattern_doc(pattern_doc, md_location, sample_data_dir)
+            patterns_create_overview.create_overview(yaml_location,
+                                                     md_file=os.path.join(md_location, "overview.md"),
+                                                     matches_dir=sample_data_dir)
+    elif yaml_location.endswith('.yaml') or yaml_location.endswith('.yml'):
+        create_indv_pattern_doc(yaml_location, md_location, sample_data_dir)
+    else:
+        logging.error("Given path has unsupported file extension:", yaml_location)
+
+
+def create_indv_pattern_doc(yaml_location, md_location, sample_data_dir=None):
+    if patterns_create_docs.is_dosdp_pattern_file(yaml_location):
+        if os.path.isdir(md_location):
+            file_name = os.path.basename(yaml_location)
+            md_location = os.path.join(md_location, (os.path.splitext(file_name)[0] + ".md"))
+        patterns_create_docs.generate_pattern_documentation(yaml_location, md_location, sample_data_dir)
+    else:
+        logging.warning("File is not a pattern file, skipping: " + yaml_location)
diff --git a/src/dosdp/document/pattern/__init__.py b/src/dosdp/document/pattern/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/dosdp/document/pattern/patterns_create_docs.py b/src/dosdp/document/pattern/patterns_create_docs.py
new file mode 100644
index 0000000..691f1a2
--- /dev/null
+++ b/src/dosdp/document/pattern/patterns_create_docs.py
@@ -0,0 +1,187 @@
+#!/usr/bin/env python3
+"""
+Created on Mon Oct 8 14:24:37 2018
+
+@author: Nicolas Matentzoglu
+"""
+
+from pathlib import Path
+import yaml
+import re
+import os
+import logging
+import pandas as pd
+from ruamel.yaml import YAML, YAMLError
+
+ROOT = Path(__file__).resolve().parent.parent.parent
+mkdocs_file = ROOT / "mkdocs.yml"
+pattern_files = (ROOT / "src/patterns/dosdp-patterns").glob("*.yaml")
+pattern_doc_dir = ROOT / "docs/editors-guide/patterns"
+# sample_data_dir = ROOT / "src/patterns/data/matches"
+pattern_matches_location_raw = "https://raw.githubusercontent.com/monarch-initiative/mondo/master/src/patterns/data/matches"
+pattern_matches_location_gh = "https://github.com/monarch-initiative/mondo/blob/master/src/patterns/data/matches"
+
+logging.basicConfig(level=logging.INFO)
+
+
+def curie_to_uri(curie):
+    prefix, identifier = curie.split(":")
+    if prefix == 'owl':
+        return f"http://www.w3.org/2002/07/owl#{identifier}"
+    elif prefix == 'oio':
+        return f"http://www.geneontology.org/formats/oboInOwl#{identifier}"
+    else:
+        return f"http://purl.obolibrary.org/obo/{prefix}_{identifier}"
+
+
+def curie_to_link(curie):
+    return f"[{curie}]({curie_to_uri(curie)})"
+
+
+def is_curie(s):
+    return ":" in s
+
+
+def render_token(var, mapping, pattern):
+    var_str = mapping[var]
+    if is_curie(var_str):
+        return f"[{var}]({curie_to_uri(var_str)})"
+    # check format ''class''
+    p = re.compile(r"'([^']*)'")
+    if p.match(var_str) and len(re.findall(r"'([^']*)'", var_str)) > 1:
+        link_str = re.sub(r"'([^']*)'", lambda m: curie_to_link(pattern["classes"][m.group(1)]), var_str)
+        return f"{var}\({link_str}\)"
+    else:
+        if p.match(var_str):
+            link_str = re.sub(r"'([^']*)'", lambda m: curie_to_uri(pattern["classes"][m.group(1)]), var_str)
+        else:
+            link_str = curie_to_uri(pattern["classes"][var_str])
+
+        return f"[{var}]({link_str})"
+
+
+def render_equivalent(text, vars, pattern):
+    ret = text % tuple("{" + render_token(var, pattern["vars"], pattern) + "}" for var in vars)
+    mapping = {}
+    mapping.update(pattern["classes"])
+    mapping.update(pattern["relations"])
+    p = re.compile(r"'[^']*'")
+    ret = re.sub(r"'([^']*)'", lambda m: "[" + m.group(1) + "](" + curie_to_uri(mapping[m.group(1)]) + ")", ret)
+    return ret
+
+
+def render_str(text, vars, pattern):
+    ret = text % tuple("{" + render_token(var, pattern["vars"], pattern) + "}" for var in vars)
+    return ret
+
+
+def generate_pattern_documentation(pattern_file, md_file_path, sample_data_dir=None):
+    """
+    Creates md formatted documentation for the given pattern file in the given location.
+    """
+    pattern_file = Path(pattern_file)
+    md_file_path = Path(md_file_path)
+    pattern = yaml.load(pattern_file.read_text(), Loader=yaml.FullLoader)
+
+    logging.info("Target is: " + os.path.abspath(md_file_path))
+    with md_file_path.open("w") as fout:
+        fout.write(f"# {pattern['pattern_name']} \n\n")
+        fout.write(f"[{pattern['pattern_iri']}]({pattern['pattern_iri']})\n")
+        fout.write("## Description \n\n")
+        fout.write(pattern["description"].replace("\n", "\n\n") + "\n")
+        if "contributors" in pattern:
+            fout.write("## Contributors \n")
+            for contributor in pattern["contributors"]:
+                fout.write(f"* [{contributor}]({contributor}) \n")
+        if "name" in pattern:
+            fout.write("## Name \n\n")
+            fout.write(render_str(pattern["name"]["text"], pattern["name"]["vars"], pattern))
+            fout.write("\n\n")
+        if "annotations" in pattern:
+            fout.write("## Annotations \n\n")
+            for anno in pattern["annotations"]:
+                fout.write("* ")
+                fout.write(
+                    render_token(anno['annotationProperty'], pattern['annotationProperties'], pattern) + ": " + render_str(
+                        anno["text"], anno["vars"], pattern))
+                fout.write("\n\n")
+        if "def" in pattern:
+            fout.write("## Definition \n\n")
+            fout.write(render_str(pattern["def"]["text"], pattern["def"]["vars"], pattern))
+            fout.write("\n\n")
+        if "equivalentTo" in pattern:
+            fout.write("## Equivalent to \n\n")
+            fout.write(render_equivalent(pattern["equivalentTo"]["text"], pattern["equivalentTo"]["vars"], pattern))
+            fout.write("\n\n")
+
+        # Create sample table
+        sample_file = pattern_file.stem + ".tsv"
+        if sample_data_dir is not None and Path(os.path.join(sample_data_dir, sample_file)).exists():
+            tsv_file = os.path.join(sample_data_dir, sample_file)
+            examples = []
+            try:
+                df = pd.read_csv(tsv_file, sep="\t")
+                ghurl = f"{pattern_matches_location_gh}/{pattern_file.stem}.tsv"
+                if not df.empty:
+                    examples.append('[mondo]({})'.format(ghurl))
+                    example = ghurl
+                    dfh = df.head()
+                    sample_table = dfh.to_markdown(index=False)
+                    fout.write("## Data preview \n")
+                    oboiri = "http://purl.obolibrary.org/obo/"
+                    fout.write(re.sub(r"http://purl.obolibrary.org/obo/([^_]+)_([^\s]+)",
+                                      lambda m: f"[{m.group(1)}:{m.group(2)}]({m.group(0)})", sample_table))
+                    fout.write("\n\n")
+                    fout.write(f"See full table [here]({example}) \n")
+                else:
+                    print("No matches!")
+            except Exception as e:
+                logging.error("Error processing the tsv file!", e)
+        else:
+            logging.warning("Data dir: '" + os.path.join(str(sample_data_dir),
+                                                         sample_file) + "' does not exist to provide sample data!")
+
+    return pattern
+
+
+def is_dosdp_pattern_file(yaml_path):
+    """
+    Checks if given file is a dosdp pattern file.
+
+    Return: True if given file is a dosdp pattern file, otherwise False.
+    """
+    is_dosdp_pattern = False
+    ryaml = YAML(typ='safe')
+    with open(yaml_path, "r") as stream:
+        try:
+            content = ryaml.load(stream)
+
+            if "pattern_name" in content or "pattern_iri" in content:
+                is_dosdp_pattern = True
+
+        except YAMLError as exc:
+            logging.error('Failed to load pattern file: ' + yaml_path)
+
+    return is_dosdp_pattern
+
+# We might need to make changes to the mkdocs.yaml file
+# mkdocs_yaml = yaml.load(mkdocs_file.read_text(), Loader=yaml.FullLoader)
+
+# pattern_lst = []
+#
+# for pattern_file in pattern_files:
+#     pattern = generate_documentation(pattern_file)
+#     pattern_lst.append((pattern_file.stem, pattern))
+#
+# # create the index.md file
+# pattern_lst.sort(key=lambda x: x[1]['pattern_name'].lower())
+# index_md_path = pattern_doc_dir / "index.md"
+# with index_md_path.open("w") as fout:
+#     fout.write(f"# Design Patterns \n\n")
+#     fout.write(f"\n")
+#     fout.write("| Pattern | Description | \n")
+#     fout.write("|:---|:---|\n")
+#     for pattern_file_name, pattern in pattern_lst:
+#         description = pattern['description'].replace("\n", "<br/>")
+#         description = re.sub(r"(http://purl[^\s]*\.[yaml|sparql]+)", lambda m: f"[{m.group(1).split('/')[-1]}]({m.group(1)})", description)
+#         fout.write(f"| [{pattern['pattern_name']}]({pattern_file_name}/) | {description} | \n")
diff --git a/src/dosdp/document/pattern/patterns_create_overview.py b/src/dosdp/document/pattern/patterns_create_overview.py
new file mode 100644
index 0000000..b52b871
--- /dev/null
+++ b/src/dosdp/document/pattern/patterns_create_overview.py
@@ -0,0 +1,127 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+Created on Mon Oct 8 14:24:37 2018
+
+@author: Nicolas Matentzoglu
+"""
+
+import os, sys
+import yaml
+import re
+import logging
+import pandas as pd
+from dosdp.document.pattern import patterns_create_docs as pattern_doc
+
+logging.basicConfig(level=logging.INFO)
+
+pattern_matches_location = "https://raw.githubusercontent.com/monarch-initiative/mondo/master/src/patterns/data/matches"
+pattern_matches_location_gh = "https://github.com/monarch-initiative/mondo/blob/master/src/patterns/data/matches"
+
+
+def create_overview(pattern_dirs, matches_dir=None, md_file=None):
+    """
+    Creates an overview document for the pattern files located in the given folder.
+    Args:
+    pattern_dir: Director or directories (split by '|') that contains pattern files.
+    matches_dir: Directory of pattern sample tsv files
+    md_file: Overview document path.
+    """
+    logging.info("Creating overview documentation.")
+    lines = []
+    lines.append("# Pattern directory")
+    lines.append("This is a listing of all the patterns hosted as part of this directory")
+    lines.append("")
+    i = 0
+    for pattern_dir in pattern_dirs.split("|"):
+        lines.append("## Patterns in {}".format(os.path.basename(pattern_dir)))
+        files = os.listdir(pattern_dir)
+        files.sort()
+
+        for filename in files:
+            logging.info("Processing %s" % filename)
+            f_path = os.path.join(pattern_dir, filename)
+            if filename.endswith(".yaml") and pattern_doc.is_dosdp_pattern_file(f_path):
+                f = open(f_path)
+                try:
+                    y = yaml.load(f, Loader=yaml.FullLoader)
+                    fn = os.path.basename(filename)
+                    splitted = " ".join(
+                        re.sub('([A-Z][a-z]+)', r' \1', re.sub('([A-Z]+)', r' \1', fn)).split()).replace(
+                        ".yaml", "")
+                    splitted = splitted.lower().capitalize()
+                    variables = ""
+                    classes = ""
+                    contributors = ""
+
+                    for v in y['vars']:
+                        # vs = re.sub("[^0-9a-zA-Z _]", "", y['vars'][v])
+                        vsv = re.sub("[']", "", y['vars'][v])
+                        variables = variables + v
+                        if vsv in y['classes']:
+                            variables = variables + " (" + y['classes'][vsv] + ")"
+                        variables = variables + ", "
+
+                    for v in y['classes']:
+                        cid = y['classes'][v]
+                        classes = classes + cid + ", "
+
+                    if 'contributors' in y:
+                        for v in y['contributors']:
+                            contributors = contributors + "[" + re.sub("https[:][/][/]orcid[.]org[/]", "",
+                                                                       v) + "](" + v + "), "
+
+                    examples = []
+                    fn_yaml = fn.replace(".yaml", ".tsv")
+                    url = "{}/{}".format(pattern_matches_location, fn_yaml)
+                    ghurl = "{}/{}".format(pattern_matches_location_gh, fn_yaml)
+                    sample_table = ""
+                    example = ""
+                    if matches_dir is not None and os.path.exists(os.path.join(matches_dir, fn_yaml)):
+                        tsv = os.path.join(matches_dir, fn_yaml)
+                        try:
+                            df = pd.read_csv(tsv, sep="\t")
+                            if not df.empty:
+                                examples.append('[mondo]({})'.format(ghurl))
+                                example = '{}'.format(ghurl)
+                                dfh = df.head()
+                                sample_table = dfh.to_markdown(index=False)
+                                i = i + 1
+                            else:
+                                logging.warning("No matches for overview!")
+                        except Exception as e:
+                            logging.error("No matches for overview!", e)
+                    else:
+                        logging.warning(str(matches_dir) + "/" + fn_yaml + " does not exist!")
+
+                    lines.append("### " + splitted.replace("_", " "))
+                    lines.append("*" + y['description'].strip() + "*")
+                    lines.append("")
+                    lines.append("| Attribute | Info |")
+                    lines.append("|----------|----------|")
+                    lines.append("| IRI | " + y['pattern_iri'] + " |")
+                    lines.append("| Name | " + y['pattern_name'] + " |")
+                    lines.append("| Classes | " + classes + " |")
+                    lines.append("| Variables | " + variables + " |")
+                    lines.append("| Contributors | " + contributors + " |")
+                    lines.append("| Examples | " + ' '.join(examples) + " |")
+                    lines.append("")
+                    if sample_table:
+                        lines.append("#### Data preview: ")
+                        oboiri = "http://purl.obolibrary.org/obo/"
+                        lines.append(sample_table.replace(oboiri, "").replace("_", ":"))
+                        lines.append("")
+                        lines.append("See full table [here]({})".format(example))
+
+                    f.close()
+                except yaml.YAMLError as exc:
+                    f.close()
+                    logging.error("Error occurred while processing yaml!", exc)
+
+    with open(md_file, 'w') as f:
+        for item in lines:
+            f.write("%s\n" % item)
+
+    logging.info("Overview documentation created: " + str(md_file))
+
+
diff --git a/src/dosdp/document/schema/__init__.py b/src/dosdp/document/schema/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/dosdp/document/schema/schema_create_docs.py b/src/dosdp/document/schema/schema_create_docs.py
new file mode 100644
index 0000000..6a87214
--- /dev/null
+++ b/src/dosdp/document/schema/schema_create_docs.py
@@ -0,0 +1,339 @@
+import jsonschema2md
+import os
+import logging
+import yaml
+import configparser
+from ruamel.yaml import YAML, YAMLError
+
+NESTED_REFERENCE_LIMIT = 3
+
+DEFINITION_PREFIX = "def_"
+
+CROSS_REF_TERM = "Refer to *#/definitions/"
+CROSS_REF_TERM_ALT = "Values from *"
+
+logging.basicConfig(level=logging.INFO)
+
+DOSDP_SCHEMA = os.path.join(os.path.dirname(os.path.realpath(__file__)), "../../../schema/dosdp_schema.yaml")
+DOSDP_SCHEMA_MD = os.path.join(os.path.dirname(os.path.realpath(__file__)), "../../../schema/dosdp_schema.md")
+DOSDP_DOCUMENTATION_CONF = os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                        "../../../schema/dosdp_schema_doc.ini")
+
+
+def generate_plain_documentation(yaml_schema, mapping_definitions):
+    """
+    Generates a plain documentation using jsonschema2md library.
+
+    Returns: Dictionary of element documentations. Key is element name and value is list of documentation lines.
+    """
+    parser = jsonschema2md.Parser()
+
+    with open(yaml_schema, 'r') as yaml_in:
+        dosdp = yaml.safe_load(yaml_in)
+        md_lines = parser.parse_schema(dosdp)
+
+        plain_documentation = dict()
+        element_lines = []
+        element_name = ""
+
+        is_definition = False
+        element_path = list()
+        prev_indent = 0
+        curr_indent = 0
+        for line in md_lines:
+            if line.startswith("## Definitions"):
+                is_definition = True
+            elif line.startswith("## Properties"):
+                is_definition = False
+
+            line = line.replace("\n", "")
+
+            if line.startswith("- **`"):
+                if element_name:
+                    plain_documentation[element_name] = element_lines
+                element_name = get_element_name(is_definition, line)
+                element_path = [element_name]
+                line = insert_mapping_definition(element_path, line, mapping_definitions)
+                element_lines = [line]
+                curr_indent = 0
+            elif line and not line.startswith("#"):
+                prev_indent = curr_indent
+                curr_indent = len(line) - len(line.lstrip(' '))
+                if curr_indent == prev_indent:
+                    element_path.remove(element_path[-1])
+                elif curr_indent <= prev_indent:
+                    element_path.remove(element_path[-1])
+                    element_path.remove(element_path[-1])
+                element_path.append(get_element_name(False, line))
+
+                line = insert_mapping_definition(element_path, line, mapping_definitions)
+                element_lines.append(line)
+
+        plain_documentation[element_name] = element_lines
+        handle_one_of_definitions(plain_documentation)
+
+        return plain_documentation
+
+
+def insert_mapping_definition(element_path, line, mapping_definitions):
+    """
+    Inserts mapping definition to the element description if required. Otherwise, returns the line as is.
+    """
+    if "$".join(element_path).replace("def_", "").lower() in mapping_definitions:
+        if "*:" in line:
+            parts = line.split("*:")
+            line = parts[0] + "*:" + " Mapped to `" + \
+                   mapping_definitions["$".join(element_path).replace("def_", "").lower()] + "`. " + parts[1]
+    return line
+
+
+def get_element_name(is_definition, line):
+    """
+    Extract element name from jsonschema2md generated documentation line.
+    """
+    first_occur = line.index("**")
+    second_occur = line.index("**", first_occur + 1)
+    element_name = line[first_occur + 2:second_occur]
+    if "`" in element_name:
+        element_name = element_name.replace("`", "").strip()
+    if is_definition:
+        element_name = DEFINITION_PREFIX + element_name
+
+    return element_name
+
+
+def find_mapping_definitions():
+    """
+    'Mapping' definitions are not handled by the jsonschema2md. To manually add documentation for these definitions,
+    searches for all mapping definitions in the schema.
+
+    Return: Dictionary of mapping definitions. Key is path of the mapping element,
+    value is value of the mapping definition.
+    """
+    mapping_definitions = dict()
+    ryaml = YAML(typ='safe')
+    with open(DOSDP_SCHEMA, "r") as stream:
+        try:
+            content = ryaml.load(stream)
+            path = list()
+            scan_element_for_mapping(content, mapping_definitions, path)
+        except YAMLError:
+            logging.error('Failed to load pattern file: ' + DOSDP_SCHEMA)
+
+    return mapping_definitions
+
+
+def scan_element_for_mapping(element, mapping_definitions, path):
+    """
+    Recursively scans schema to identify mapping definitions. Fills the mapping definitions dictionary.
+    """
+    for key in element.keys():
+        path.append(key)
+        if "mapping" == key:
+            path_refine = path[0:len(path) - 1]
+            if "definitions" in path_refine: path_refine.remove("definitions")
+            if "properties" in path_refine: path_refine.remove("properties")
+            mapping_definitions["$".join(path_refine)] = element["mapping"]
+        elif isinstance(element[key], dict):
+            scan_element_for_mapping(element[key], mapping_definitions, path)
+        path.remove(key)
+
+
+def handle_one_of_definitions(plain_documentation):
+    """
+    'OneOf' definitions are not handled by the jsonschema2md. Manually adding documentation for these definitions.
+    """
+    ryaml = YAML(typ='safe')
+    with open(DOSDP_SCHEMA, "r") as stream:
+        try:
+            content = ryaml.load(stream)
+            definitions = content["definitions"]
+            for key in definitions.keys():
+                if "oneOf" in definitions[key]:
+                    one_of_defs = definitions[key]["oneOf"]
+                    if "$ref" in one_of_defs[0]:
+                        element_lines = ["- **`annotations`** *(array)*: One of the followings:"]
+                        for one_of_item in one_of_defs:
+                            element_lines.append("  - **Items**: Refer to *" + one_of_item["$ref"] + "*.")
+                        plain_documentation[DEFINITION_PREFIX + key] = element_lines
+        except YAMLError as exc:
+            logging.error('Failed to load pattern file: ' + DOSDP_SCHEMA)
+
+
+def print_documentation_header(doc_type_elements, config, md_out):
+    """
+    Adds headings and table of contents to the documentation.
+    """
+    md_out.write("# %s\n" % doc_type_elements["doc_title"])
+    md_out.write("\n")
+
+    print_documentation_toc(config, md_out)
+
+    md_out.write("\n")
+    md_out.write("## %s\n" % "Properties")
+    md_out.write("\n")
+
+
+def print_documentation_toc(config, md_out):
+    """
+    Prints table of contents based on documentation config.
+    """
+    sections = config.sections()
+    for section in sections:
+        section_config = config[section]
+        if section == "root":
+            md_out.write("- [" + section_config["title"] + "](#" +
+                         section_config["title"].lower().replace(" ", "-") + ")\n")
+        else:
+            md_out.write("  * [" + section_config["title"] + "](#" +
+                         section_config["title"].lower().replace(" ", "-") + ")\n")
+
+
+def print_element(element, md_out, plain_doc, prefix="", nesting_list=[], in_reference=False):
+    """
+    Retrieves plain element documentation generated by the jsonschema2md and writes to the document.
+    Additionally expands 'definitions' references to a specified depth (see NESTED_REFERENCE_LIMIT)
+    in order to prevent indefinite circular references.
+    """
+    # prefix = ">" * (len(nesting_list))
+    indentation = prefix + ("  " * (len(nesting_list) % 2))
+    lines = plain_doc[element]
+
+    # postpone writing references (as last items) to make it more readable
+    reference_defs = list()
+    reference_elements = dict()
+    is_annotation = False
+    for count, line in enumerate(lines):
+        line = customize_doc_content(line)
+
+        if "**`annotations`** *(list)*" in line:
+            reference_definition = indentation + line
+            reference_defs.append(reference_definition)
+            reference_elements[reference_definition] = list()
+            is_annotation = True
+        elif CROSS_REF_TERM_ALT in line:
+            nesting_list.append(element)
+            ref_term_start = line.index(CROSS_REF_TERM_ALT) + len(CROSS_REF_TERM_ALT)
+            referred_element = line[ref_term_start:len(line) - 2]
+            if is_annotation:
+                reference_elements[reference_defs[-1]].append(DEFINITION_PREFIX + referred_element)
+            else:
+                if "- **Items**" not in line:
+                    md_out.write("%s\n" % (indentation + line.split(CROSS_REF_TERM_ALT)[0].rstrip()))
+                    # md_out.write("%s\n" % (indentation + line))
+                if (DEFINITION_PREFIX + referred_element) not in nesting_list:
+                    print_element(DEFINITION_PREFIX + referred_element, md_out, plain_doc,
+                                  "" if len(nesting_list) == 1 else indentation + "  ", nesting_list)
+                else:
+                    # ellipsis to indicate recursion
+                    md_out.write("%s\n" % (indentation + " " * (len(line) - len(line.lstrip())) + "  - ..."))
+        else:
+            is_annotation = False
+            if not (element.startswith(DEFINITION_PREFIX) and count == 0 and not in_reference) \
+                    and "- **Items**" not in line and "_annotation`** *(object)*:" not in line:
+                md_out.write("%s\n" % (indentation + line))
+
+    for count, reference in enumerate(reference_defs):
+        if "**`annotations`** *(list)*: One of the followings:" in reference:
+            md_out.write("%s\n" % (indentation + "*Use one of the followings:*"))
+        else:
+            md_out.write("%s\n" % reference)
+        for element in reference_elements[reference]:
+            if element not in nesting_list:
+                md_out.write("\n")
+                print_element(element, md_out, plain_doc,
+                              prefix if prefix.startswith(">") else ">" + prefix, nesting_list, in_reference=True)
+            else:
+                # ellipsis to indicate recursion
+                line_content = reference.replace(">", "")
+                md_out.write("%s\n" % (indentation + " " * (len(line_content) - len(line_content.lstrip())) + "  - ..."))
+
+
+def customize_doc_content(line):
+    """
+    Place content customizations to here. Such as don't display "array", use "list instead"
+    """
+    if "*(array)*" in line:
+        line = line.replace("*(array)*", "*(list)*")
+
+    if "*(array)*" in line:
+        line = line.replace("*(object)*", "*(dict)*")
+
+    if CROSS_REF_TERM in line:
+        line = line.replace(CROSS_REF_TERM, CROSS_REF_TERM_ALT)
+
+    return line
+
+
+def print_section_header(config, md_out, section):
+    """
+    Adds a section title and description for doc_type.
+    'root' doc_type is a special type and doesn't have a title and description.
+    """
+    if not section == "root":
+        section_config = config[section]
+        md_out.write("### %s\n" % section_config["title"])
+        md_out.write("\n")
+        md_out.write("%s\n" % section_config["description"])
+        md_out.write("\n")
+
+
+def get_doc_type_elements():
+    """
+    Reads the schema file and builds a list of doc_type elements.
+
+    Returns: dictionary of schema elements. Key is doc_type (as defined in the ini config)
+    and value is list of element names.
+    """
+    doc_type_elements = dict()
+
+    ryaml = YAML(typ='safe')
+    with open(DOSDP_SCHEMA, "r") as stream:
+        try:
+            content = ryaml.load(stream)
+            doc_type_elements["doc_title"] = content["title"]
+            properties = content["properties"]
+
+            for key in properties.keys():
+                doc_type = properties[key]["doc_type"]
+                if doc_type in doc_type_elements:
+                    doc_type_elements[doc_type].append(key)
+                else:
+                    doc_type_elements[doc_type] = [key]
+        except YAMLError as exc:
+            logging.error('Failed to load pattern file: ' + DOSDP_SCHEMA)
+    return doc_type_elements
+
+
+def generate_schema_documentation(yaml_schema=DOSDP_SCHEMA, md_output=DOSDP_SCHEMA_MD):
+    """
+    Generates documentation for the given YAML schema. Uses jsonschema2md to generate a plain documentation,
+    then decorates generated documentation through using the documentation config (.ini file)
+    """
+    mapping_definitions = find_mapping_definitions()
+    plain_doc = generate_plain_documentation(yaml_schema, mapping_definitions)
+
+    config = configparser.ConfigParser()
+    config.read(DOSDP_DOCUMENTATION_CONF)
+    sections = config.sections()
+
+    doc_type_elements = get_doc_type_elements()
+
+    if os.path.isdir(md_output):
+        file_name = os.path.basename(yaml_schema)
+        md_output = os.path.join(md_output, (os.path.splitext(file_name)[0] + ".md"))
+
+    logging.info("Target is: " + os.path.abspath(md_output))
+    with open(md_output, "w") as md_out:
+        print_documentation_header(doc_type_elements, config, md_out)
+
+        for section in sections:
+            elements = doc_type_elements[section]
+            print_section_header(config, md_out, section)
+
+            for element in elements:
+                print_element(element, md_out, plain_doc, nesting_list=[])
+                md_out.write("\n\n")
+
+
+# generate_schema_documentation(DOSDP_SCHEMA)
diff --git a/src/dosdp/validator.md b/src/dosdp/validator.md
new file mode 100644
index 0000000..bc93486
--- /dev/null
+++ b/src/dosdp/validator.md
@@ -0,0 +1,26 @@
+# DOSDP Validator
+
+DOSDP provides a validation interface for both CLI and Python.
+
+### From the CLI
+
+```sh
+$ dosdp validate -i <test.yaml or 'test folder'>
+```
+
+### From Python
+
+```python
+from dosdp import validator
+
+validator.validate("test.yaml")
+```
+
+DOSDP validates given argument if it is a yaml/yml file. If argument is a folder, validates all pattern files located in the given directory.
+
+### Validation Steps
+
+1. Test converstion of YAML to JSON
+2. Validate against JSON schema (e.g. see [dosdp_schema.md](https://github.com/INCATools/dead_simple_owl_design_patterns/tree/master/src/schema/dosdp_schema.md) and [dosdp_schema.yaml](https://github.com/INCATools/dead_simple_owl_design_patterns/tree/master/src/schema/dosdp_schema.yaml))
+3. Test that all var names in printf statements are valid (declared) for the pattern
+4. Checks quoted names in the printf_owl field correspond to dictionary entries in the pattern.
\ No newline at end of file
diff --git a/src/dosdp/validator.py b/src/dosdp/validator.py
new file mode 100755
index 0000000..ac8e8c0
--- /dev/null
+++ b/src/dosdp/validator.py
@@ -0,0 +1,264 @@
+#!/usr/bin/env python3
+import glob
+import re
+import warnings
+import os
+import logging
+from jsonschema import Draft7Validator
+from jsonpath_rw import parse
+from ruamel.yaml import YAML, YAMLError
+
+SCHEMA_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "../schema/dosdp_schema.yaml")
+
+logging.basicConfig(level=logging.INFO)
+
+
+def test_jschema(validator, pattern):
+    """
+    Validates if given schema is a valid json schema.
+    Args:
+        validator: sjon schema validator
+        pattern: schema in yaml format to validate
+
+    Returns: True if schema is valid, False otherwise.
+    """
+    is_valid = True
+
+    if not validator.is_valid(pattern):
+        es = validator.iter_errors(pattern)
+        for e in es:
+            warnings.warn(" => ".join([str(e.schema_path), str(e.message), str(e.context)]))
+            is_valid = False
+
+    return is_valid
+
+
+def test_vars(pattern):
+    """
+    Tests whether variable names in any field with key 'vars' is in the vars list for the pattern
+    Args:
+         pattern: schema in yaml format to validate
+
+    Returns: True if schema vars are valid, False otherwise.
+    """
+    if 'vars' in pattern.keys():
+        vars = set(pattern['vars'].keys())
+    else:
+        warnings.warn("Pattern has no vars")
+        return True ## If this is to be compulsory, should be spec'd as such in json_schema
+    if 'list_vars' in pattern.keys():
+        vars.update(set(pattern['list_vars'].keys()))
+    if 'data_vars' in pattern.keys():
+        vars.update(set(pattern['data_vars'].keys()))
+    if 'data_list_vars' in pattern.keys():
+        vars.update(set(pattern['data_list_vars'].keys()))
+    if 'substitutions' in pattern.keys():
+        subvars = [X['out'] for X in pattern['substitutions']]
+        vars.update(set(subvars))
+    if 'internal_vars' in pattern.keys():
+        internal_vars = [X['var_name'] for X in pattern['internal_vars']]
+        vars.update(set(internal_vars))
+    expr = parse('*..vars')
+    var_fields = [match for match in expr.find(pattern)]
+    stat = True
+    if var_fields:
+        for field in var_fields:
+            val = set(field.value)
+            if not vars.issuperset(val):
+                warnings.warn("%s has values (%s) not found in pattern variable list (%s): "
+                  % (field.full_path, str(val.difference(vars)), str(vars)))
+                stat = False
+    else:
+        warnings.warn("Pattern has no var fields")
+    return stat
+
+
+def test_text_fields(pattern):
+    """
+    Structurally tests whether quotations match and declared values exist in owl entity dictionaries.
+     Args:
+         pattern: schema in yaml format to validate
+
+    Returns: True if schema text fields are valid, False otherwise.
+    """
+    owl_entities = set()
+    if 'classes' in pattern.keys(): owl_entities.update(set(pattern['classes'].keys()))
+    if 'relations' in pattern.keys(): owl_entities.update(set(pattern['relations'].keys()))
+    expr = parse('logical_axioms.[*].text')
+    ms_fields = [match for match in expr.find(pattern)]
+    expr = parse('logical_axioms.[*].multi_clause.clauses.[*].text')
+    ms_fields.extend(match for match in expr.find(pattern))
+    expr = parse('equivalentTo|subClassOf|GCI|disjointWith.text')
+    ms_fields.extend([match for match in expr.find(pattern)])
+    expr = parse('equivalentTo|subClassOf|GCI|disjointWith.multi_clause.clauses.[*].text')
+    ms_fields.extend([match for match in expr.find(pattern)])
+    stat=True
+    if ms_fields:
+        for field in ms_fields:
+            # Test for even number single quotes
+            val = field.value
+            m = re.findall("'", val)
+            if divmod(len(m), 2)[1]:
+                warnings.warn("text field '%s' has an odd number of single quotes." % val)
+                stat = False
+            # Test that single quoted strings are OWL entities in dict.
+            m = re.findall("'(.+?)'", val)
+            quoted = set(m)
+            if not owl_entities.issuperset(quoted):
+                warnings.warn("%s has values (%s) not found in owl entity dictionaries t (%s): "
+                  % (field.full_path, str(quoted.difference(owl_entities)), str(owl_entities)))
+                stat = False
+    else:
+        warnings.warn("Pattern has no text fields")
+    return stat
+
+
+def test_clause_nesting(pattern):
+    """
+     Structurally tests that logical axioms and annotations don't use nested multi_clause structures.
+     Only one level of multi_clause allowed (clauses of the multi_clause cannot have sub_clauses).
+     Args:
+         pattern: schema in yaml format to validate
+
+    Returns: True if multi_clause nesting is valid, False otherwise.
+    """
+    expr = parse('logical_axioms.[*].multi_clause.clauses.[*].sub_clauses')
+    ms_fields = [match for match in expr.find(pattern)]
+    expr = parse('equivalentTo|subClassOf|GCI|disjointWith.multi_clause.clauses.[*].sub_clauses')
+    ms_fields.extend([match for match in expr.find(pattern)])
+
+    stat = True
+    if ms_fields:
+        stat = False
+        warnings.warn("Detected multi_clause nesting in expressions. "
+                      "Logical axioms and annotations cannot have sub_clauses.")
+    return stat
+
+
+def test_axiom_separator(pattern):
+    """
+     Structurally tests that logical axioms can only have ' and ' and ' or ' as value.
+     Args:
+         pattern: schema in yaml format to validate
+
+    Returns: True if axiom separators are valid, False otherwise.
+    """
+    expr = parse('logical_axioms.[*].multi_clause.sep')
+    ms_fields = [match for match in expr.find(pattern)]
+    expr = parse('equivalentTo|subClassOf|GCI|disjointWith.multi_clause.sep')
+    ms_fields.extend([match for match in expr.find(pattern)])
+
+    stat = True
+    if ms_fields:
+        for field in ms_fields:
+            val = field.value
+            if val != " and " and val != " or ":
+                warnings.warn("Logical axioms can only have ' and ' and ' or ' as value. '%s' is not a valid value." % val)
+                stat = False
+    return stat
+
+
+def test_multi_clause_multi_list(pattern):
+    """
+     Multi_clauses can have at most one list variable.
+     Args:
+         pattern: schema in yaml format to validate
+
+    Returns: True if multi_clause list variable usage is valid, False otherwise.
+    """
+    list_vars = set()
+    if 'list_vars' in pattern.keys():
+        list_vars.update(pattern['list_vars'].keys())
+    if 'data_list_vars' in pattern.keys():
+        list_vars.update(set(pattern['data_list_vars'].keys()))
+
+    expr = parse('$..multi_clause')
+    multi_clause_matches = [match for match in expr.find(pattern)]
+    multi_clauses = [str(i.full_path) for i in multi_clause_matches]
+
+    mc_vars = {}
+    for mc in multi_clauses:
+        mc_vars[mc] = list()
+
+    expr = parse('$..multi_clause..vars')
+    var_fields = [match for match in expr.find(pattern)]
+
+    for field in var_fields:
+        var_path = str(field.full_path)
+        parent_mc = str(var_path[0: (var_path.find("multi_clause") + len("multi_clause"))])
+        new_list = list(mc_vars[parent_mc])
+        new_list.extend(list(field.value))
+        mc_vars[parent_mc] = new_list
+
+    stat = True
+    for mc_var in mc_vars:
+        list_references = [var for var in mc_vars[mc_var] if var in list_vars]
+        if len(list_references) > 1:
+            warnings.warn("Multi_clauses can have at most one list variable. But has multiple list references: %s"
+                          % str(list_references))
+            stat = False
+    return stat
+
+
+def format_warning(message, category, filename, lineno, line=None):
+    return '%s:%s: %s:%s\n' % (filename, lineno, category.__name__, message)
+
+
+def validate(pattern):
+    """
+    If given parameter is a yaml/yaml file, validates it. If parameter is a folder, validates all pattern files located
+    in the given path.
+    Args:
+    pattern: path to a yaml file or path to directory with pattern files in yaml. Files must have the extension .yaml or .yml.
+    All files in directory with these extensions are assumed to be dosdp pattern files.
+
+    Returns: True if patterns are valid, False otherwise.
+    """
+    schema_path = SCHEMA_PATH
+    ryaml = YAML(typ='safe')
+
+    dosdp = None
+    with open(schema_path) as stream:
+        try:
+            dosdp = ryaml.load(stream)
+        except YAMLError as exc:
+            logging.error('Failed to open dosdp schema: ' + schema_path)
+
+    validator = Draft7Validator(dosdp)
+
+    warnings.formatwarning = format_warning
+    if os.path.isdir(pattern):
+        if not pattern.endswith(os.path.sep):
+            pattern += os.path.sep
+        pattern_docs = glob.glob(pattern + "*.yaml")
+        pattern_docs.extend(glob.glob(pattern + "*.yml"))
+    elif pattern.endswith('.yaml') or pattern.endswith('.yaml'):
+        pattern_docs = [os.path.abspath(pattern)]
+    else:
+        logging.error("Given path has unsupported file extension.", )
+
+    stat = True
+    for pattern_doc in pattern_docs:
+        logging.info("Checking %s" % pattern_doc)
+        with open(pattern_doc, "r") as stream:
+            try:
+                pattern = ryaml.load(stream)
+                if not test_jschema(validator, pattern): stat = False
+                if not test_vars(pattern): stat = False
+                if not test_text_fields(pattern): stat = False
+                if not test_clause_nesting(pattern): stat = False
+                if not test_axiom_separator(pattern): stat = False
+                if not test_multi_clause_multi_list(pattern): stat = False
+            except YAMLError as exc:
+                stat = False
+                logging.error('Failed to load pattern file: ' + pattern_doc)
+            except AttributeError as exc:
+                stat = False
+                logging.error('Unexpected pattern file content: ' + pattern_doc)
+                logging.error(exc)
+    if stat:
+        logging.info("Validation completed without any issues to report.")
+    else:
+        logging.info("Validation completed with issues to be fixed.")
+
+    return stat
diff --git a/src/schema/__init__.py b/src/schema/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/schema/dosdp_schema.md b/src/schema/dosdp_schema.md
new file mode 100644
index 0000000..d4f8088
--- /dev/null
+++ b/src/schema/dosdp_schema.md
@@ -0,0 +1,612 @@
+# DOSDP
+
+- [Properties](#properties)
+  * [OWL Entity Dictionaries](#owl-entity-dictionaries)
+  * [Var Types](#var-types)
+  * [Var Munging](#var-munging)
+  * [Axioms](#axioms)
+  * [Logical Convenience Fields](#logical-convenience-fields)
+  * [OBO fields](#obo-fields)
+  * [Instance Graph Spec](#instance-graph-spec)
+
+## Properties
+
+- **`pattern_name`** *(string)*: The name of the pattern.  This must be an ASCII string with no spaces. The only special characters allowed are '_' and '-'. By convention, this is used as the file name of the pattern - with an appropriate extension.
+
+
+- **`pattern_iri`** *(string)*: A global identifier for the pattern. This can be a full IRI or a CURIE, using the same prefix mappings as other CURIEs in the pattern.
+
+
+- **`base_IRI`** *(string)*: Specifies the base IRI to be used to generate new classes.
+
+
+- **`contributors`** *(list)*: A list of authors of a pattern. Each author must be specified using a URL or Curie - we recommend ORCID. We do not recommend that this list is instantiated in terms generated using a pattern, but where it is it should be instantiated as a set of annotation axioms using dc:contributor.
+
+
+- **`description`** *(string)*: A free text description of the pattern.  Must be UTF-8 encoded.
+
+
+- **`examples`** *(list)*: A list of example terms implementing this pattern.
+
+
+- **`status`** *(string)*: Implementation status of pattern. Must be one of: `['development', 'published']`.
+
+
+- **`tags`** *(list)*: A list of strings used to tag a pattern for the purposes of arbitrary, cross-cutting grouping of patterns.
+
+
+- **`readable_identifiers`** *(list)*: A list of annotation properties used as naming fields, in order of preference.
+
+
+### OWL Entity Dictionaries
+
+
+
+- **`classes`** *(object)*: A dictionary of OWL classes. key :label; value : short form id.
+
+
+- **`objectProperties`** *(object)*: A dictionary of OWL object properties. key : label; value : short form id.
+
+
+- **`relations`** *(object)*: A dictionary of OWL object properties. key : label; value : short form id.
+
+
+- **`dataProperties`** *(object)*: A dictionary of OWL data properties key : label; value : short form id.
+
+
+- **`annotationProperties`** *(object)*: A dictionary of OWL annotation properties key : label; value : short form id.
+
+
+### Var Types
+
+
+
+- **`vars`** *(object)*: A dictionary of variables ranging over OWL classes. Key = variable name, value = variable range as manchester syntax string.
+
+
+- **`list_vars`** *(object)*: A dictionary of variables refering to lists of owl classes. Key = variable name, value = variable range of items in list specified as a valid OWL data-type.
+
+
+- **`data_vars`** *(object)*: A dictionary of variables ranging over OWL data-types. Key = variable name, value = variable range specified as a valid OWL data-type.
+
+
+- **`data_list_vars`** *(object)*: A dictionary of variables rrefering to lists of some specified OWL data-types. Key = variable name, value = variable range of all items in list, specified as a valid OWL data-type.
+
+
+- **`internal_vars`** *(list)*: List of internal variable construction definitions. Given function is applied to the given multi value input and the result is defined as a new internal variable.
+  - **`var_name`** *(string)*: Name of the internal variable to be defined. Expected naming pattern is ^[A-Za-z_][A-Za-z0-9_]*$.
+  - **`input`** *(string)*: A list_vars or data_list_vars variable to which the given function applied.
+  - **`apply`**:
+  *Use one of the followings:*
+
+>  - **`join`**
+>    - **`sep`** *(string)*: A string used as value separator while joining list type (multi value) variables.
+
+>  - **`regex_sub`** *(object)*: Cannot contain additional properties.
+>    - **`in`** *(string)*: name of input var.
+>    - **`out`** *(string)*: Name of output var.  If input var specified an OWL entity then readable identifier is used as input to substitution.
+>    - **`match`** *(string)*: perl style regex match.
+>    - **`sub`** *(string)*: perl style regex sub.  May include backreferences.
+
+
+### Var Munging
+
+
+
+- **`substitutions`** *(list)*
+    - **`in`** *(string)*: name of input var.
+    - **`out`** *(string)*: Name of output var.  If input var specified an OWL entity then readable identifier is used as input to substitution.
+    - **`match`** *(string)*: perl style regex match.
+    - **`sub`** *(string)*: perl style regex sub.  May include backreferences.
+
+
+### Axioms
+
+
+
+- **`annotations`** *(list)*
+
+>  *Use one of the followings:*
+
+>  - **`annotationProperty`** *(string)*: A string corresponding to the rdfs:label of an owl annotation property. If the annotation property has no label, the shortForm ID should be used. The annotation property must be listed in the annotation property dictionary.'.
+>  - **`text`** *(string)*: A print format string.
+>  - **`vars`** *(list)*: An ordered list of variables for substitution into the accompanying print format string. Each entry must correspond to the name of a variable specified in either the 'vars' field or the data_var field of the pattern. Where an OWL entity is specified, the label for the OWL entity should be used in the substitution.  An empty var list can be specified simply by leaving this field out.
+>  - **`multi_clause`**
+>    - **`sep`** *(string)*: A string used as clause separator while aggregating multiple clauses.
+>    - **`clauses`** *(list)*: List of optional clauses.  Rules for optional clauses:  list_vars may be passed, but only one list_var per multi-clause printf is permitted. If an empty list_var is passed, the clause, and any subclauses, are omitted. If list_var with length n is passed, the clause is repeated n times, using the specified separtor to join clauses.  There is no effect on the number of subclauses in this case.
+>        - **`text`** *(string)*: A print format string.
+>        - **`vars`** *(list)*: An ordered list of variables for substitution into the accompanying print format string. Each entry must correspond to the name of a variable specified in either the 'vars', 'internal_vars' or the data_var field of the pattern. Where an OWL entity is specified, the label for the OWL entity should be used in the substitution. SPECIAL RULES FOR multi_clause_printf context: In this context, list_vars are permitted. If an list is empty, the clause and any subclauses must not be added.  for lists of length > 1, mutiple clauses should be added, using the specified separator.
+>        - **`sub_clauses`** *(list)*: List of clauses that depends on this clause. If parent clause cannot be printed, all sub_clauses are also discarded.
+>            - ...
+>  - **`annotations`** *(list)*
+>    - ...
+
+>  - **`annotationProperty`** *(string)*: A string corresponding to the rdfs:label of an owl annotation property. If the annotation property has no label, the shortForm ID should be used. The annotation property must be listed in the annotation property dictionary.'.
+>  - **`value`** *(string)*: A single list variable (list_var or data_list_var).  Each item in this list should be used to generate a separate annotation axiom.
+
+>  - **`annotationProperty`** *(string)*: A string corresponding to a key in the annotation property dictionary.
+>  - **`var`** *(string)*: The name of a variable specified in the 'vars' field. The IRI of the variable value will be the object of the annotation axiom.
+>  - **`annotations`** *(list)*
+>    - ...
+
+
+- **`logical_axioms`** *(list)*
+    - **`axiom_type`** *(string)*: OWL axiom type expressed as manchester syntax: equivalentTo, subClassOf, disjointWith. GCI  - for general class inclusion axioms, is also valid (although missing from manchester syntax.) This specifies the axiom type to be generated from the text following substitution.'. Must be one of: `['equivalentTo', 'subClassOf', 'disjointWith', 'GCI']`.
+    - **`text`** *(string)*: A print format string in OWL Manchester syntax. Each entry must correspond to an entry in o the name of a var in the var field of the pattern. Entries in single quotes must correspond to the labels of entries in owl_entity dictionaries (classes, relations, dataProperties).
+    - **`vars`** *(list)*: An ordered list of variables for substitution into the accompanying print format string. Each entry must correspond to the name of a variable specified in either the 'vars' field or the data_var field of the pattern. An empty var list can be specified simply by leaving this field out.
+    - **`multi_clause`**
+        - **`sep`** *(string)*: A string used as clause separator while aggregating multiple clauses.
+        - **`clauses`** *(list)*: List of optional clauses.  Rules for optional clauses:  list_vars may be passed, but only one list_var per multi-clause printf is permitted. If an empty list_var is passed, the clause, and any subclauses, are omitted. If list_var with length n is passed, the clause is repeated n times, using the specified separtor to join clauses.  There is no effect on the number of subclauses in this case.
+          - **`text`** *(string)*: A print format string.
+          - **`vars`** *(list)*: An ordered list of variables for substitution into the accompanying print format string. Each entry must correspond to the name of a variable specified in either the 'vars', 'internal_vars' or the data_var field of the pattern. Where an OWL entity is specified, the label for the OWL entity should be used in the substitution. SPECIAL RULES FOR multi_clause_printf context: In this context, list_vars are permitted. If an list is empty, the clause and any subclauses must not be added.  for lists of length > 1, mutiple clauses should be added, using the specified separator.
+          - **`sub_clauses`** *(list)*: List of clauses that depends on this clause. If parent clause cannot be printed, all sub_clauses are also discarded.
+              - ...
+    - **`annotations`** *(list)*
+
+>  *Use one of the followings:*
+
+>  - **`annotationProperty`** *(string)*: A string corresponding to the rdfs:label of an owl annotation property. If the annotation property has no label, the shortForm ID should be used. The annotation property must be listed in the annotation property dictionary.'.
+>  - **`text`** *(string)*: A print format string.
+>  - **`vars`** *(list)*: An ordered list of variables for substitution into the accompanying print format string. Each entry must correspond to the name of a variable specified in either the 'vars' field or the data_var field of the pattern. Where an OWL entity is specified, the label for the OWL entity should be used in the substitution.  An empty var list can be specified simply by leaving this field out.
+>  - **`multi_clause`**
+>      - ...
+>  - **`annotations`** *(list)*
+>    - ...
+
+>  - **`annotationProperty`** *(string)*: A string corresponding to the rdfs:label of an owl annotation property. If the annotation property has no label, the shortForm ID should be used. The annotation property must be listed in the annotation property dictionary.'.
+>  - **`value`** *(string)*: A single list variable (list_var or data_list_var).  Each item in this list should be used to generate a separate annotation axiom.
+
+>  - **`annotationProperty`** *(string)*: A string corresponding to a key in the annotation property dictionary.
+>  - **`var`** *(string)*: The name of a variable specified in the 'vars' field. The IRI of the variable value will be the object of the annotation axiom.
+>  - **`annotations`** *(list)*
+>    - ...
+
+
+### Logical Convenience Fields
+
+Where only one of any OWL axiom type is present, these convenience fields may be used.
+
+- **`equivalentTo`**:
+    - **`text`** *(string)*: A print format string in OWL Manchester syntax. Each entry must correspond to an entry in o the name of a var in the var field of the pattern. Entries in single quotes must correspond to the labels of entries in owl_entity dictionaries (classes, relations, dataProperties).
+    - **`vars`** *(list)*: An ordered list of variables for substitution into the accompanying print format string. Each entry must correspond to the name of a variable specified in either the 'vars' field or the data_var field of the pattern.
+    - **`multi_clause`**
+        - **`sep`** *(string)*: A string used as clause separator while aggregating multiple clauses.
+        - **`clauses`** *(list)*: List of optional clauses.  Rules for optional clauses:  list_vars may be passed, but only one list_var per multi-clause printf is permitted. If an empty list_var is passed, the clause, and any subclauses, are omitted. If list_var with length n is passed, the clause is repeated n times, using the specified separtor to join clauses.  There is no effect on the number of subclauses in this case.
+          - **`text`** *(string)*: A print format string.
+          - **`vars`** *(list)*: An ordered list of variables for substitution into the accompanying print format string. Each entry must correspond to the name of a variable specified in either the 'vars', 'internal_vars' or the data_var field of the pattern. Where an OWL entity is specified, the label for the OWL entity should be used in the substitution. SPECIAL RULES FOR multi_clause_printf context: In this context, list_vars are permitted. If an list is empty, the clause and any subclauses must not be added.  for lists of length > 1, mutiple clauses should be added, using the specified separator.
+          - **`sub_clauses`** *(list)*: List of clauses that depends on this clause. If parent clause cannot be printed, all sub_clauses are also discarded.
+              - ...
+    - **`annotations`** *(list)*
+
+>  *Use one of the followings:*
+
+>  - **`annotationProperty`** *(string)*: A string corresponding to the rdfs:label of an owl annotation property. If the annotation property has no label, the shortForm ID should be used. The annotation property must be listed in the annotation property dictionary.'.
+>  - **`text`** *(string)*: A print format string.
+>  - **`vars`** *(list)*: An ordered list of variables for substitution into the accompanying print format string. Each entry must correspond to the name of a variable specified in either the 'vars' field or the data_var field of the pattern. Where an OWL entity is specified, the label for the OWL entity should be used in the substitution.  An empty var list can be specified simply by leaving this field out.
+>  - **`multi_clause`**
+>      - ...
+>  - **`annotations`** *(list)*
+>    - ...
+
+>  - **`annotationProperty`** *(string)*: A string corresponding to the rdfs:label of an owl annotation property. If the annotation property has no label, the shortForm ID should be used. The annotation property must be listed in the annotation property dictionary.'.
+>  - **`value`** *(string)*: A single list variable (list_var or data_list_var).  Each item in this list should be used to generate a separate annotation axiom.
+
+>  - **`annotationProperty`** *(string)*: A string corresponding to a key in the annotation property dictionary.
+>  - **`var`** *(string)*: The name of a variable specified in the 'vars' field. The IRI of the variable value will be the object of the annotation axiom.
+>  - **`annotations`** *(list)*
+>    - ...
+
+
+- **`subClassOf`**:
+    - **`text`** *(string)*: A print format string in OWL Manchester syntax. Each entry must correspond to an entry in o the name of a var in the var field of the pattern. Entries in single quotes must correspond to the labels of entries in owl_entity dictionaries (classes, relations, dataProperties).
+    - **`vars`** *(list)*: An ordered list of variables for substitution into the accompanying print format string. Each entry must correspond to the name of a variable specified in either the 'vars' field or the data_var field of the pattern.
+    - **`multi_clause`**
+        - **`sep`** *(string)*: A string used as clause separator while aggregating multiple clauses.
+        - **`clauses`** *(list)*: List of optional clauses.  Rules for optional clauses:  list_vars may be passed, but only one list_var per multi-clause printf is permitted. If an empty list_var is passed, the clause, and any subclauses, are omitted. If list_var with length n is passed, the clause is repeated n times, using the specified separtor to join clauses.  There is no effect on the number of subclauses in this case.
+          - **`text`** *(string)*: A print format string.
+          - **`vars`** *(list)*: An ordered list of variables for substitution into the accompanying print format string. Each entry must correspond to the name of a variable specified in either the 'vars', 'internal_vars' or the data_var field of the pattern. Where an OWL entity is specified, the label for the OWL entity should be used in the substitution. SPECIAL RULES FOR multi_clause_printf context: In this context, list_vars are permitted. If an list is empty, the clause and any subclauses must not be added.  for lists of length > 1, mutiple clauses should be added, using the specified separator.
+          - **`sub_clauses`** *(list)*: List of clauses that depends on this clause. If parent clause cannot be printed, all sub_clauses are also discarded.
+              - ...
+    - **`annotations`** *(list)*
+
+>  *Use one of the followings:*
+
+>  - **`annotationProperty`** *(string)*: A string corresponding to the rdfs:label of an owl annotation property. If the annotation property has no label, the shortForm ID should be used. The annotation property must be listed in the annotation property dictionary.'.
+>  - **`text`** *(string)*: A print format string.
+>  - **`vars`** *(list)*: An ordered list of variables for substitution into the accompanying print format string. Each entry must correspond to the name of a variable specified in either the 'vars' field or the data_var field of the pattern. Where an OWL entity is specified, the label for the OWL entity should be used in the substitution.  An empty var list can be specified simply by leaving this field out.
+>  - **`multi_clause`**
+>      - ...
+>  - **`annotations`** *(list)*
+>    - ...
+
+>  - **`annotationProperty`** *(string)*: A string corresponding to the rdfs:label of an owl annotation property. If the annotation property has no label, the shortForm ID should be used. The annotation property must be listed in the annotation property dictionary.'.
+>  - **`value`** *(string)*: A single list variable (list_var or data_list_var).  Each item in this list should be used to generate a separate annotation axiom.
+
+>  - **`annotationProperty`** *(string)*: A string corresponding to a key in the annotation property dictionary.
+>  - **`var`** *(string)*: The name of a variable specified in the 'vars' field. The IRI of the variable value will be the object of the annotation axiom.
+>  - **`annotations`** *(list)*
+>    - ...
+
+
+- **`GCI`**:
+    - **`text`** *(string)*: A print format string in OWL Manchester syntax. Each entry must correspond to an entry in o the name of a var in the var field of the pattern. Entries in single quotes must correspond to the labels of entries in owl_entity dictionaries (classes, relations, dataProperties).
+    - **`vars`** *(list)*: An ordered list of variables for substitution into the accompanying print format string. Each entry must correspond to the name of a variable specified in either the 'vars' field or the data_var field of the pattern.
+    - **`multi_clause`**
+        - **`sep`** *(string)*: A string used as clause separator while aggregating multiple clauses.
+        - **`clauses`** *(list)*: List of optional clauses.  Rules for optional clauses:  list_vars may be passed, but only one list_var per multi-clause printf is permitted. If an empty list_var is passed, the clause, and any subclauses, are omitted. If list_var with length n is passed, the clause is repeated n times, using the specified separtor to join clauses.  There is no effect on the number of subclauses in this case.
+          - **`text`** *(string)*: A print format string.
+          - **`vars`** *(list)*: An ordered list of variables for substitution into the accompanying print format string. Each entry must correspond to the name of a variable specified in either the 'vars', 'internal_vars' or the data_var field of the pattern. Where an OWL entity is specified, the label for the OWL entity should be used in the substitution. SPECIAL RULES FOR multi_clause_printf context: In this context, list_vars are permitted. If an list is empty, the clause and any subclauses must not be added.  for lists of length > 1, mutiple clauses should be added, using the specified separator.
+          - **`sub_clauses`** *(list)*: List of clauses that depends on this clause. If parent clause cannot be printed, all sub_clauses are also discarded.
+              - ...
+    - **`annotations`** *(list)*
+
+>  *Use one of the followings:*
+
+>  - **`annotationProperty`** *(string)*: A string corresponding to the rdfs:label of an owl annotation property. If the annotation property has no label, the shortForm ID should be used. The annotation property must be listed in the annotation property dictionary.'.
+>  - **`text`** *(string)*: A print format string.
+>  - **`vars`** *(list)*: An ordered list of variables for substitution into the accompanying print format string. Each entry must correspond to the name of a variable specified in either the 'vars' field or the data_var field of the pattern. Where an OWL entity is specified, the label for the OWL entity should be used in the substitution.  An empty var list can be specified simply by leaving this field out.
+>  - **`multi_clause`**
+>      - ...
+>  - **`annotations`** *(list)*
+>    - ...
+
+>  - **`annotationProperty`** *(string)*: A string corresponding to the rdfs:label of an owl annotation property. If the annotation property has no label, the shortForm ID should be used. The annotation property must be listed in the annotation property dictionary.'.
+>  - **`value`** *(string)*: A single list variable (list_var or data_list_var).  Each item in this list should be used to generate a separate annotation axiom.
+
+>  - **`annotationProperty`** *(string)*: A string corresponding to a key in the annotation property dictionary.
+>  - **`var`** *(string)*: The name of a variable specified in the 'vars' field. The IRI of the variable value will be the object of the annotation axiom.
+>  - **`annotations`** *(list)*
+>    - ...
+
+
+- **`disjointWith`**:
+    - **`text`** *(string)*: A print format string in OWL Manchester syntax. Each entry must correspond to an entry in o the name of a var in the var field of the pattern. Entries in single quotes must correspond to the labels of entries in owl_entity dictionaries (classes, relations, dataProperties).
+    - **`vars`** *(list)*: An ordered list of variables for substitution into the accompanying print format string. Each entry must correspond to the name of a variable specified in either the 'vars' field or the data_var field of the pattern.
+    - **`multi_clause`**
+        - **`sep`** *(string)*: A string used as clause separator while aggregating multiple clauses.
+        - **`clauses`** *(list)*: List of optional clauses.  Rules for optional clauses:  list_vars may be passed, but only one list_var per multi-clause printf is permitted. If an empty list_var is passed, the clause, and any subclauses, are omitted. If list_var with length n is passed, the clause is repeated n times, using the specified separtor to join clauses.  There is no effect on the number of subclauses in this case.
+          - **`text`** *(string)*: A print format string.
+          - **`vars`** *(list)*: An ordered list of variables for substitution into the accompanying print format string. Each entry must correspond to the name of a variable specified in either the 'vars', 'internal_vars' or the data_var field of the pattern. Where an OWL entity is specified, the label for the OWL entity should be used in the substitution. SPECIAL RULES FOR multi_clause_printf context: In this context, list_vars are permitted. If an list is empty, the clause and any subclauses must not be added.  for lists of length > 1, mutiple clauses should be added, using the specified separator.
+          - **`sub_clauses`** *(list)*: List of clauses that depends on this clause. If parent clause cannot be printed, all sub_clauses are also discarded.
+              - ...
+    - **`annotations`** *(list)*
+
+>  *Use one of the followings:*
+
+>  - **`annotationProperty`** *(string)*: A string corresponding to the rdfs:label of an owl annotation property. If the annotation property has no label, the shortForm ID should be used. The annotation property must be listed in the annotation property dictionary.'.
+>  - **`text`** *(string)*: A print format string.
+>  - **`vars`** *(list)*: An ordered list of variables for substitution into the accompanying print format string. Each entry must correspond to the name of a variable specified in either the 'vars' field or the data_var field of the pattern. Where an OWL entity is specified, the label for the OWL entity should be used in the substitution.  An empty var list can be specified simply by leaving this field out.
+>  - **`multi_clause`**
+>      - ...
+>  - **`annotations`** *(list)*
+>    - ...
+
+>  - **`annotationProperty`** *(string)*: A string corresponding to the rdfs:label of an owl annotation property. If the annotation property has no label, the shortForm ID should be used. The annotation property must be listed in the annotation property dictionary.'.
+>  - **`value`** *(string)*: A single list variable (list_var or data_list_var).  Each item in this list should be used to generate a separate annotation axiom.
+
+>  - **`annotationProperty`** *(string)*: A string corresponding to a key in the annotation property dictionary.
+>  - **`var`** *(string)*: The name of a variable specified in the 'vars' field. The IRI of the variable value will be the object of the annotation axiom.
+>  - **`annotations`** *(list)*
+>    - ...
+
+
+### OBO fields
+
+
+
+- **`name`**: Mapped to `rdfs:label`.
+    - **`xrefs`** *(string)*: Mapped to `oboInOwl:hasDbXref`.  Takes the name of a single data_list_var specifying a list of database cross references.
+    - **`text`** *(string)*: A print format string.
+    - **`vars`** *(list)*: An ordered list of variables for substitution into the accompanying print format string. Each entry must correspond to the name of a variable specified in either the 'vars' field or the data_var field of the pattern. Where an OWL entity is specified, the label for the OWL entity should be used in the substitution.
+    - **`multi_clause`**
+        - **`sep`** *(string)*: A string used as clause separator while aggregating multiple clauses.
+        - **`clauses`** *(list)*: List of optional clauses.  Rules for optional clauses:  list_vars may be passed, but only one list_var per multi-clause printf is permitted. If an empty list_var is passed, the clause, and any subclauses, are omitted. If list_var with length n is passed, the clause is repeated n times, using the specified separtor to join clauses.  There is no effect on the number of subclauses in this case.
+          - **`text`** *(string)*: A print format string.
+          - **`vars`** *(list)*: An ordered list of variables for substitution into the accompanying print format string. Each entry must correspond to the name of a variable specified in either the 'vars', 'internal_vars' or the data_var field of the pattern. Where an OWL entity is specified, the label for the OWL entity should be used in the substitution. SPECIAL RULES FOR multi_clause_printf context: In this context, list_vars are permitted. If an list is empty, the clause and any subclauses must not be added.  for lists of length > 1, mutiple clauses should be added, using the specified separator.
+          - **`sub_clauses`** *(list)*: List of clauses that depends on this clause. If parent clause cannot be printed, all sub_clauses are also discarded.
+              - ...
+    - **`annotations`** *(list)*
+
+>  *Use one of the followings:*
+
+>  - **`annotationProperty`** *(string)*: A string corresponding to the rdfs:label of an owl annotation property. If the annotation property has no label, the shortForm ID should be used. The annotation property must be listed in the annotation property dictionary.'.
+>  - **`text`** *(string)*: A print format string.
+>  - **`vars`** *(list)*: An ordered list of variables for substitution into the accompanying print format string. Each entry must correspond to the name of a variable specified in either the 'vars' field or the data_var field of the pattern. Where an OWL entity is specified, the label for the OWL entity should be used in the substitution.  An empty var list can be specified simply by leaving this field out.
+>  - **`multi_clause`**
+>      - ...
+>  - **`annotations`** *(list)*
+>    - ...
+
+>  - **`annotationProperty`** *(string)*: A string corresponding to the rdfs:label of an owl annotation property. If the annotation property has no label, the shortForm ID should be used. The annotation property must be listed in the annotation property dictionary.'.
+>  - **`value`** *(string)*: A single list variable (list_var or data_list_var).  Each item in this list should be used to generate a separate annotation axiom.
+
+>  - **`annotationProperty`** *(string)*: A string corresponding to a key in the annotation property dictionary.
+>  - **`var`** *(string)*: The name of a variable specified in the 'vars' field. The IRI of the variable value will be the object of the annotation axiom.
+>  - **`annotations`** *(list)*
+>    - ...
+
+
+- **`comment`**: Mapped to `rdfs:comment`.
+    - **`xrefs`** *(string)*: Mapped to `oboInOwl:hasDbXref`.  Takes the name of a single data_list_var specifying a list of database cross references.
+    - **`text`** *(string)*: A print format string.
+    - **`vars`** *(list)*: An ordered list of variables for substitution into the accompanying print format string. Each entry must correspond to the name of a variable specified in either the 'vars' field or the data_var field of the pattern. Where an OWL entity is specified, the label for the OWL entity should be used in the substitution.
+    - **`multi_clause`**
+        - **`sep`** *(string)*: A string used as clause separator while aggregating multiple clauses.
+        - **`clauses`** *(list)*: List of optional clauses.  Rules for optional clauses:  list_vars may be passed, but only one list_var per multi-clause printf is permitted. If an empty list_var is passed, the clause, and any subclauses, are omitted. If list_var with length n is passed, the clause is repeated n times, using the specified separtor to join clauses.  There is no effect on the number of subclauses in this case.
+          - **`text`** *(string)*: A print format string.
+          - **`vars`** *(list)*: An ordered list of variables for substitution into the accompanying print format string. Each entry must correspond to the name of a variable specified in either the 'vars', 'internal_vars' or the data_var field of the pattern. Where an OWL entity is specified, the label for the OWL entity should be used in the substitution. SPECIAL RULES FOR multi_clause_printf context: In this context, list_vars are permitted. If an list is empty, the clause and any subclauses must not be added.  for lists of length > 1, mutiple clauses should be added, using the specified separator.
+          - **`sub_clauses`** *(list)*: List of clauses that depends on this clause. If parent clause cannot be printed, all sub_clauses are also discarded.
+              - ...
+    - **`annotations`** *(list)*
+
+>  *Use one of the followings:*
+
+>  - **`annotationProperty`** *(string)*: A string corresponding to the rdfs:label of an owl annotation property. If the annotation property has no label, the shortForm ID should be used. The annotation property must be listed in the annotation property dictionary.'.
+>  - **`text`** *(string)*: A print format string.
+>  - **`vars`** *(list)*: An ordered list of variables for substitution into the accompanying print format string. Each entry must correspond to the name of a variable specified in either the 'vars' field or the data_var field of the pattern. Where an OWL entity is specified, the label for the OWL entity should be used in the substitution.  An empty var list can be specified simply by leaving this field out.
+>  - **`multi_clause`**
+>      - ...
+>  - **`annotations`** *(list)*
+>    - ...
+
+>  - **`annotationProperty`** *(string)*: A string corresponding to the rdfs:label of an owl annotation property. If the annotation property has no label, the shortForm ID should be used. The annotation property must be listed in the annotation property dictionary.'.
+>  - **`value`** *(string)*: A single list variable (list_var or data_list_var).  Each item in this list should be used to generate a separate annotation axiom.
+
+>  - **`annotationProperty`** *(string)*: A string corresponding to a key in the annotation property dictionary.
+>  - **`var`** *(string)*: The name of a variable specified in the 'vars' field. The IRI of the variable value will be the object of the annotation axiom.
+>  - **`annotations`** *(list)*
+>    - ...
+
+
+- **`def`**: Mapped to `obo:IAO_0000115`.
+    - **`xrefs`** *(string)*: Mapped to `oboInOwl:hasDbXref`.  Takes the name of a single data_list_var specifying a list of database cross references.
+    - **`text`** *(string)*: A print format string.
+    - **`vars`** *(list)*: An ordered list of variables for substitution into the accompanying print format string. Each entry must correspond to the name of a variable specified in either the 'vars' field or the data_var field of the pattern. Where an OWL entity is specified, the label for the OWL entity should be used in the substitution.
+    - **`multi_clause`**
+        - **`sep`** *(string)*: A string used as clause separator while aggregating multiple clauses.
+        - **`clauses`** *(list)*: List of optional clauses.  Rules for optional clauses:  list_vars may be passed, but only one list_var per multi-clause printf is permitted. If an empty list_var is passed, the clause, and any subclauses, are omitted. If list_var with length n is passed, the clause is repeated n times, using the specified separtor to join clauses.  There is no effect on the number of subclauses in this case.
+          - **`text`** *(string)*: A print format string.
+          - **`vars`** *(list)*: An ordered list of variables for substitution into the accompanying print format string. Each entry must correspond to the name of a variable specified in either the 'vars', 'internal_vars' or the data_var field of the pattern. Where an OWL entity is specified, the label for the OWL entity should be used in the substitution. SPECIAL RULES FOR multi_clause_printf context: In this context, list_vars are permitted. If an list is empty, the clause and any subclauses must not be added.  for lists of length > 1, mutiple clauses should be added, using the specified separator.
+          - **`sub_clauses`** *(list)*: List of clauses that depends on this clause. If parent clause cannot be printed, all sub_clauses are also discarded.
+              - ...
+    - **`annotations`** *(list)*
+
+>  *Use one of the followings:*
+
+>  - **`annotationProperty`** *(string)*: A string corresponding to the rdfs:label of an owl annotation property. If the annotation property has no label, the shortForm ID should be used. The annotation property must be listed in the annotation property dictionary.'.
+>  - **`text`** *(string)*: A print format string.
+>  - **`vars`** *(list)*: An ordered list of variables for substitution into the accompanying print format string. Each entry must correspond to the name of a variable specified in either the 'vars' field or the data_var field of the pattern. Where an OWL entity is specified, the label for the OWL entity should be used in the substitution.  An empty var list can be specified simply by leaving this field out.
+>  - **`multi_clause`**
+>      - ...
+>  - **`annotations`** *(list)*
+>    - ...
+
+>  - **`annotationProperty`** *(string)*: A string corresponding to the rdfs:label of an owl annotation property. If the annotation property has no label, the shortForm ID should be used. The annotation property must be listed in the annotation property dictionary.'.
+>  - **`value`** *(string)*: A single list variable (list_var or data_list_var).  Each item in this list should be used to generate a separate annotation axiom.
+
+>  - **`annotationProperty`** *(string)*: A string corresponding to a key in the annotation property dictionary.
+>  - **`var`** *(string)*: The name of a variable specified in the 'vars' field. The IRI of the variable value will be the object of the annotation axiom.
+>  - **`annotations`** *(list)*
+>    - ...
+
+
+- **`namespace`**: Mapped to `oboInOwl:hasOBONamespace`.
+    - **`xrefs`** *(string)*: Mapped to `oboInOwl:hasDbXref`.  Takes the name of a single data_list_var specifying a list of database cross references.
+    - **`text`** *(string)*: A print format string.
+    - **`vars`** *(list)*: An ordered list of variables for substitution into the accompanying print format string. Each entry must correspond to the name of a variable specified in either the 'vars' field or the data_var field of the pattern. Where an OWL entity is specified, the label for the OWL entity should be used in the substitution.
+    - **`multi_clause`**
+        - **`sep`** *(string)*: A string used as clause separator while aggregating multiple clauses.
+        - **`clauses`** *(list)*: List of optional clauses.  Rules for optional clauses:  list_vars may be passed, but only one list_var per multi-clause printf is permitted. If an empty list_var is passed, the clause, and any subclauses, are omitted. If list_var with length n is passed, the clause is repeated n times, using the specified separtor to join clauses.  There is no effect on the number of subclauses in this case.
+          - **`text`** *(string)*: A print format string.
+          - **`vars`** *(list)*: An ordered list of variables for substitution into the accompanying print format string. Each entry must correspond to the name of a variable specified in either the 'vars', 'internal_vars' or the data_var field of the pattern. Where an OWL entity is specified, the label for the OWL entity should be used in the substitution. SPECIAL RULES FOR multi_clause_printf context: In this context, list_vars are permitted. If an list is empty, the clause and any subclauses must not be added.  for lists of length > 1, mutiple clauses should be added, using the specified separator.
+          - **`sub_clauses`** *(list)*: List of clauses that depends on this clause. If parent clause cannot be printed, all sub_clauses are also discarded.
+              - ...
+    - **`annotations`** *(list)*
+
+>  *Use one of the followings:*
+
+>  - **`annotationProperty`** *(string)*: A string corresponding to the rdfs:label of an owl annotation property. If the annotation property has no label, the shortForm ID should be used. The annotation property must be listed in the annotation property dictionary.'.
+>  - **`text`** *(string)*: A print format string.
+>  - **`vars`** *(list)*: An ordered list of variables for substitution into the accompanying print format string. Each entry must correspond to the name of a variable specified in either the 'vars' field or the data_var field of the pattern. Where an OWL entity is specified, the label for the OWL entity should be used in the substitution.  An empty var list can be specified simply by leaving this field out.
+>  - **`multi_clause`**
+>      - ...
+>  - **`annotations`** *(list)*
+>    - ...
+
+>  - **`annotationProperty`** *(string)*: A string corresponding to the rdfs:label of an owl annotation property. If the annotation property has no label, the shortForm ID should be used. The annotation property must be listed in the annotation property dictionary.'.
+>  - **`value`** *(string)*: A single list variable (list_var or data_list_var).  Each item in this list should be used to generate a separate annotation axiom.
+
+>  - **`annotationProperty`** *(string)*: A string corresponding to a key in the annotation property dictionary.
+>  - **`var`** *(string)*: The name of a variable specified in the 'vars' field. The IRI of the variable value will be the object of the annotation axiom.
+>  - **`annotations`** *(list)*
+>    - ...
+
+
+- **`exact_synonym`**: Mapped to `oboInOwl:hasExactSynonym`.
+    - **`value`** *(string)*: A single list variable (list_var or data_list_var).  Each item in this list should be used to generate a separate annotation axiom.
+    - **`xrefs`** *(string)*: Mapped to `oboInOwl:hasDbXref`.  Takes the name of a single data_list_var specifying a list of database cross references. Use of this field should add the same xref set to all annotation axioms generated.
+
+
+- **`narrow_synonym`**: Mapped to `oboInOwl:hasNarrowSynonym`.
+    - **`value`** *(string)*: A single list variable (list_var or data_list_var).  Each item in this list should be used to generate a separate annotation axiom.
+    - **`xrefs`** *(string)*: Mapped to `oboInOwl:hasDbXref`.  Takes the name of a single data_list_var specifying a list of database cross references. Use of this field should add the same xref set to all annotation axioms generated.
+
+
+- **`related_synonym`**: Mapped to `oboInOwl:hasRelatedSynonym`.
+    - **`value`** *(string)*: A single list variable (list_var or data_list_var).  Each item in this list should be used to generate a separate annotation axiom.
+    - **`xrefs`** *(string)*: Mapped to `oboInOwl:hasDbXref`.  Takes the name of a single data_list_var specifying a list of database cross references. Use of this field should add the same xref set to all annotation axioms generated.
+
+
+- **`broad_synonym`**: Mapped to `oboInOwl:hasBroadSynonym`.
+    - **`value`** *(string)*: A single list variable (list_var or data_list_var).  Each item in this list should be used to generate a separate annotation axiom.
+    - **`xrefs`** *(string)*: Mapped to `oboInOwl:hasDbXref`.  Takes the name of a single data_list_var specifying a list of database cross references. Use of this field should add the same xref set to all annotation axioms generated.
+
+
+- **`xref`**: Mapped to `oboInOwl:hasDbXref`.
+    - **`value`** *(string)*: A single list variable (list_var or data_list_var).  Each item in this list should be used to generate a separate annotation axiom.
+    - **`xrefs`** *(string)*: Mapped to `oboInOwl:hasDbXref`.  Takes the name of a single data_list_var specifying a list of database cross references. Use of this field should add the same xref set to all annotation axioms generated.
+
+
+- **`generated_synonyms`** *(list)*: An OBO convenience field to allow the specification of exact synonyms generated by interpolation of OWL entity names into printf text. Each entry may be annotated.
+    - **`xrefs`** *(string)*: Mapped to `oboInOwl:hasDbXref`.  Takes the name of a single data_list_var specifying a list of database cross references.
+    - **`text`** *(string)*: A print format string.
+    - **`vars`** *(list)*: An ordered list of variables for substitution into the accompanying print format string. Each entry must correspond to the name of a variable specified in either the 'vars' field or the data_var field of the pattern. Where an OWL entity is specified, the label for the OWL entity should be used in the substitution.
+    - **`multi_clause`**
+        - **`sep`** *(string)*: A string used as clause separator while aggregating multiple clauses.
+        - **`clauses`** *(list)*: List of optional clauses.  Rules for optional clauses:  list_vars may be passed, but only one list_var per multi-clause printf is permitted. If an empty list_var is passed, the clause, and any subclauses, are omitted. If list_var with length n is passed, the clause is repeated n times, using the specified separtor to join clauses.  There is no effect on the number of subclauses in this case.
+          - **`text`** *(string)*: A print format string.
+          - **`vars`** *(list)*: An ordered list of variables for substitution into the accompanying print format string. Each entry must correspond to the name of a variable specified in either the 'vars', 'internal_vars' or the data_var field of the pattern. Where an OWL entity is specified, the label for the OWL entity should be used in the substitution. SPECIAL RULES FOR multi_clause_printf context: In this context, list_vars are permitted. If an list is empty, the clause and any subclauses must not be added.  for lists of length > 1, mutiple clauses should be added, using the specified separator.
+          - **`sub_clauses`** *(list)*: List of clauses that depends on this clause. If parent clause cannot be printed, all sub_clauses are also discarded.
+              - ...
+    - **`annotations`** *(list)*
+
+>  *Use one of the followings:*
+
+>  - **`annotationProperty`** *(string)*: A string corresponding to the rdfs:label of an owl annotation property. If the annotation property has no label, the shortForm ID should be used. The annotation property must be listed in the annotation property dictionary.'.
+>  - **`text`** *(string)*: A print format string.
+>  - **`vars`** *(list)*: An ordered list of variables for substitution into the accompanying print format string. Each entry must correspond to the name of a variable specified in either the 'vars' field or the data_var field of the pattern. Where an OWL entity is specified, the label for the OWL entity should be used in the substitution.  An empty var list can be specified simply by leaving this field out.
+>  - **`multi_clause`**
+>      - ...
+>  - **`annotations`** *(list)*
+>    - ...
+
+>  - **`annotationProperty`** *(string)*: A string corresponding to the rdfs:label of an owl annotation property. If the annotation property has no label, the shortForm ID should be used. The annotation property must be listed in the annotation property dictionary.'.
+>  - **`value`** *(string)*: A single list variable (list_var or data_list_var).  Each item in this list should be used to generate a separate annotation axiom.
+
+>  - **`annotationProperty`** *(string)*: A string corresponding to a key in the annotation property dictionary.
+>  - **`var`** *(string)*: The name of a variable specified in the 'vars' field. The IRI of the variable value will be the object of the annotation axiom.
+>  - **`annotations`** *(list)*
+>    - ...
+
+
+- **`generated_narrow_synonyms`** *(list)*: An OBO convenience field to allow the specification of narrow synonyms generated by interpolation of OWL entity names into printf text. Each entry may be annotated.
+    - **`xrefs`** *(string)*: Mapped to `oboInOwl:hasDbXref`.  Takes the name of a single data_list_var specifying a list of database cross references.
+    - **`text`** *(string)*: A print format string.
+    - **`vars`** *(list)*: An ordered list of variables for substitution into the accompanying print format string. Each entry must correspond to the name of a variable specified in either the 'vars' field or the data_var field of the pattern. Where an OWL entity is specified, the label for the OWL entity should be used in the substitution.
+    - **`multi_clause`**
+        - **`sep`** *(string)*: A string used as clause separator while aggregating multiple clauses.
+        - **`clauses`** *(list)*: List of optional clauses.  Rules for optional clauses:  list_vars may be passed, but only one list_var per multi-clause printf is permitted. If an empty list_var is passed, the clause, and any subclauses, are omitted. If list_var with length n is passed, the clause is repeated n times, using the specified separtor to join clauses.  There is no effect on the number of subclauses in this case.
+          - **`text`** *(string)*: A print format string.
+          - **`vars`** *(list)*: An ordered list of variables for substitution into the accompanying print format string. Each entry must correspond to the name of a variable specified in either the 'vars', 'internal_vars' or the data_var field of the pattern. Where an OWL entity is specified, the label for the OWL entity should be used in the substitution. SPECIAL RULES FOR multi_clause_printf context: In this context, list_vars are permitted. If an list is empty, the clause and any subclauses must not be added.  for lists of length > 1, mutiple clauses should be added, using the specified separator.
+          - **`sub_clauses`** *(list)*: List of clauses that depends on this clause. If parent clause cannot be printed, all sub_clauses are also discarded.
+              - ...
+    - **`annotations`** *(list)*
+
+>  *Use one of the followings:*
+
+>  - **`annotationProperty`** *(string)*: A string corresponding to the rdfs:label of an owl annotation property. If the annotation property has no label, the shortForm ID should be used. The annotation property must be listed in the annotation property dictionary.'.
+>  - **`text`** *(string)*: A print format string.
+>  - **`vars`** *(list)*: An ordered list of variables for substitution into the accompanying print format string. Each entry must correspond to the name of a variable specified in either the 'vars' field or the data_var field of the pattern. Where an OWL entity is specified, the label for the OWL entity should be used in the substitution.  An empty var list can be specified simply by leaving this field out.
+>  - **`multi_clause`**
+>      - ...
+>  - **`annotations`** *(list)*
+>    - ...
+
+>  - **`annotationProperty`** *(string)*: A string corresponding to the rdfs:label of an owl annotation property. If the annotation property has no label, the shortForm ID should be used. The annotation property must be listed in the annotation property dictionary.'.
+>  - **`value`** *(string)*: A single list variable (list_var or data_list_var).  Each item in this list should be used to generate a separate annotation axiom.
+
+>  - **`annotationProperty`** *(string)*: A string corresponding to a key in the annotation property dictionary.
+>  - **`var`** *(string)*: The name of a variable specified in the 'vars' field. The IRI of the variable value will be the object of the annotation axiom.
+>  - **`annotations`** *(list)*
+>    - ...
+
+
+- **`generated_broad_synonyms`** *(list)*: An OBO convenience field to allow the specification of broad synonyms generated by interpolation of OWL entity names into printf text. Each entry may be annotated.
+    - **`xrefs`** *(string)*: Mapped to `oboInOwl:hasDbXref`.  Takes the name of a single data_list_var specifying a list of database cross references.
+    - **`text`** *(string)*: A print format string.
+    - **`vars`** *(list)*: An ordered list of variables for substitution into the accompanying print format string. Each entry must correspond to the name of a variable specified in either the 'vars' field or the data_var field of the pattern. Where an OWL entity is specified, the label for the OWL entity should be used in the substitution.
+    - **`multi_clause`**
+        - **`sep`** *(string)*: A string used as clause separator while aggregating multiple clauses.
+        - **`clauses`** *(list)*: List of optional clauses.  Rules for optional clauses:  list_vars may be passed, but only one list_var per multi-clause printf is permitted. If an empty list_var is passed, the clause, and any subclauses, are omitted. If list_var with length n is passed, the clause is repeated n times, using the specified separtor to join clauses.  There is no effect on the number of subclauses in this case.
+          - **`text`** *(string)*: A print format string.
+          - **`vars`** *(list)*: An ordered list of variables for substitution into the accompanying print format string. Each entry must correspond to the name of a variable specified in either the 'vars', 'internal_vars' or the data_var field of the pattern. Where an OWL entity is specified, the label for the OWL entity should be used in the substitution. SPECIAL RULES FOR multi_clause_printf context: In this context, list_vars are permitted. If an list is empty, the clause and any subclauses must not be added.  for lists of length > 1, mutiple clauses should be added, using the specified separator.
+          - **`sub_clauses`** *(list)*: List of clauses that depends on this clause. If parent clause cannot be printed, all sub_clauses are also discarded.
+              - ...
+    - **`annotations`** *(list)*
+
+>  *Use one of the followings:*
+
+>  - **`annotationProperty`** *(string)*: A string corresponding to the rdfs:label of an owl annotation property. If the annotation property has no label, the shortForm ID should be used. The annotation property must be listed in the annotation property dictionary.'.
+>  - **`text`** *(string)*: A print format string.
+>  - **`vars`** *(list)*: An ordered list of variables for substitution into the accompanying print format string. Each entry must correspond to the name of a variable specified in either the 'vars' field or the data_var field of the pattern. Where an OWL entity is specified, the label for the OWL entity should be used in the substitution.  An empty var list can be specified simply by leaving this field out.
+>  - **`multi_clause`**
+>      - ...
+>  - **`annotations`** *(list)*
+>    - ...
+
+>  - **`annotationProperty`** *(string)*: A string corresponding to the rdfs:label of an owl annotation property. If the annotation property has no label, the shortForm ID should be used. The annotation property must be listed in the annotation property dictionary.'.
+>  - **`value`** *(string)*: A single list variable (list_var or data_list_var).  Each item in this list should be used to generate a separate annotation axiom.
+
+>  - **`annotationProperty`** *(string)*: A string corresponding to a key in the annotation property dictionary.
+>  - **`var`** *(string)*: The name of a variable specified in the 'vars' field. The IRI of the variable value will be the object of the annotation axiom.
+>  - **`annotations`** *(list)*
+>    - ...
+
+
+- **`generated_related_synonyms`** *(list)*: An OBO convenience field to allow the specification of related synonyms generated by interpolation of OWL entity names into printf text. Each entry may be annotated.
+    - **`xrefs`** *(string)*: Mapped to `oboInOwl:hasDbXref`.  Takes the name of a single data_list_var specifying a list of database cross references.
+    - **`text`** *(string)*: A print format string.
+    - **`vars`** *(list)*: An ordered list of variables for substitution into the accompanying print format string. Each entry must correspond to the name of a variable specified in either the 'vars' field or the data_var field of the pattern. Where an OWL entity is specified, the label for the OWL entity should be used in the substitution.
+    - **`multi_clause`**
+        - **`sep`** *(string)*: A string used as clause separator while aggregating multiple clauses.
+        - **`clauses`** *(list)*: List of optional clauses.  Rules for optional clauses:  list_vars may be passed, but only one list_var per multi-clause printf is permitted. If an empty list_var is passed, the clause, and any subclauses, are omitted. If list_var with length n is passed, the clause is repeated n times, using the specified separtor to join clauses.  There is no effect on the number of subclauses in this case.
+          - **`text`** *(string)*: A print format string.
+          - **`vars`** *(list)*: An ordered list of variables for substitution into the accompanying print format string. Each entry must correspond to the name of a variable specified in either the 'vars', 'internal_vars' or the data_var field of the pattern. Where an OWL entity is specified, the label for the OWL entity should be used in the substitution. SPECIAL RULES FOR multi_clause_printf context: In this context, list_vars are permitted. If an list is empty, the clause and any subclauses must not be added.  for lists of length > 1, mutiple clauses should be added, using the specified separator.
+          - **`sub_clauses`** *(list)*: List of clauses that depends on this clause. If parent clause cannot be printed, all sub_clauses are also discarded.
+              - ...
+    - **`annotations`** *(list)*
+
+>  *Use one of the followings:*
+
+>  - **`annotationProperty`** *(string)*: A string corresponding to the rdfs:label of an owl annotation property. If the annotation property has no label, the shortForm ID should be used. The annotation property must be listed in the annotation property dictionary.'.
+>  - **`text`** *(string)*: A print format string.
+>  - **`vars`** *(list)*: An ordered list of variables for substitution into the accompanying print format string. Each entry must correspond to the name of a variable specified in either the 'vars' field or the data_var field of the pattern. Where an OWL entity is specified, the label for the OWL entity should be used in the substitution.  An empty var list can be specified simply by leaving this field out.
+>  - **`multi_clause`**
+>      - ...
+>  - **`annotations`** *(list)*
+>    - ...
+
+>  - **`annotationProperty`** *(string)*: A string corresponding to the rdfs:label of an owl annotation property. If the annotation property has no label, the shortForm ID should be used. The annotation property must be listed in the annotation property dictionary.'.
+>  - **`value`** *(string)*: A single list variable (list_var or data_list_var).  Each item in this list should be used to generate a separate annotation axiom.
+
+>  - **`annotationProperty`** *(string)*: A string corresponding to a key in the annotation property dictionary.
+>  - **`var`** *(string)*: The name of a variable specified in the 'vars' field. The IRI of the variable value will be the object of the annotation axiom.
+>  - **`annotations`** *(list)*
+>    - ...
+
+
+### Instance Graph Spec
+
+
+
+- **`instance_graph`** *(object)*: Cannot contain additional properties.
+  - **`nodes`** *(object)*: Key = name of individual within this pattern doc Value = Type of individual specified using either the quoted name of a class in the class dictionary of this pattern or a var name.  This field does not support typing via anonymous class expressions.
+  - **`edges`** *(list)*
+    - **`edge`** *(list)*: A triple specified as an ordered array with 3 elements [subject, rel, object] * rel must be the quoted name of a relation from the relations (object property) dictionary. * subject and object must be the name of an individual specified in the nodes field.
+    - **`not`** *(boolean)*: Optional field for negated OPAs.
+    - **`annotations`** *(list)*
+
+>*Use one of the followings:*
+
+>    - **`annotationProperty`** *(string)*: A string corresponding to the rdfs:label of an owl annotation property. If the annotation property has no label, the shortForm ID should be used. The annotation property must be listed in the annotation property dictionary.'.
+>    - **`text`** *(string)*: A print format string.
+>    - **`vars`** *(list)*: An ordered list of variables for substitution into the accompanying print format string. Each entry must correspond to the name of a variable specified in either the 'vars' field or the data_var field of the pattern. Where an OWL entity is specified, the label for the OWL entity should be used in the substitution.  An empty var list can be specified simply by leaving this field out.
+>    - **`multi_clause`**
+>        - **`sep`** *(string)*: A string used as clause separator while aggregating multiple clauses.
+>        - **`clauses`** *(list)*: List of optional clauses.  Rules for optional clauses:  list_vars may be passed, but only one list_var per multi-clause printf is permitted. If an empty list_var is passed, the clause, and any subclauses, are omitted. If list_var with length n is passed, the clause is repeated n times, using the specified separtor to join clauses.  There is no effect on the number of subclauses in this case.
+>          - **`text`** *(string)*: A print format string.
+>          - **`vars`** *(list)*: An ordered list of variables for substitution into the accompanying print format string. Each entry must correspond to the name of a variable specified in either the 'vars', 'internal_vars' or the data_var field of the pattern. Where an OWL entity is specified, the label for the OWL entity should be used in the substitution. SPECIAL RULES FOR multi_clause_printf context: In this context, list_vars are permitted. If an list is empty, the clause and any subclauses must not be added.  for lists of length > 1, mutiple clauses should be added, using the specified separator.
+>          - **`sub_clauses`** *(list)*: List of clauses that depends on this clause. If parent clause cannot be printed, all sub_clauses are also discarded.
+>              - ...
+>    - **`annotations`** *(list)*
+>        - ...
+
+>    - **`annotationProperty`** *(string)*: A string corresponding to the rdfs:label of an owl annotation property. If the annotation property has no label, the shortForm ID should be used. The annotation property must be listed in the annotation property dictionary.'.
+>    - **`value`** *(string)*: A single list variable (list_var or data_list_var).  Each item in this list should be used to generate a separate annotation axiom.
+
+>    - **`annotationProperty`** *(string)*: A string corresponding to a key in the annotation property dictionary.
+>    - **`var`** *(string)*: The name of a variable specified in the 'vars' field. The IRI of the variable value will be the object of the annotation axiom.
+>    - **`annotations`** *(list)*
+>        - ...
+
+
diff --git a/src/schema/dosdp_schema.yaml b/src/schema/dosdp_schema.yaml
new file mode 100644
index 0000000..f6a10ae
--- /dev/null
+++ b/src/schema/dosdp_schema.yaml
@@ -0,0 +1,593 @@
+$schema: "http://json-schema.org/draft-07/schema#"
+#id: 'https://github.com/dosumis/dead_simple_owl_design_patterns/edit/master/spec/DOSDP_spec.json' # Use Purl?
+title: DOSDP
+type: object
+additionalProperties: False
+
+
+definitions:
+
+  multi_clause_printf:
+    required: [ 'clauses' ]
+    additionalProperties: False
+    properties:
+      sep:
+        type: string
+        description: >
+          A string used as clause separator while aggregating multiple clauses.
+      clauses:
+        type: array
+        description: >
+          List of optional clauses.  Rules for optional clauses:  list_vars may be passed,
+          but only one list_var per multi-clause printf is permitted. If an empty list_var is passed,
+          the clause, and any subclauses, are omitted. If list_var with length n is passed, the
+          clause is repeated n times, using the specified separtor to join clauses.  There is no
+          effect on the number of subclauses in this case.
+        items: { $ref : '#/definitions/printf_clause' }
+
+  printf_clause:
+    required: [ 'text', 'vars' ]
+    additionalProperties: False
+    properties:
+      text:
+        description: A print format string.
+        type: string
+      vars:
+        description: >
+          An ordered list of variables for substitution into the accompanying
+          print format string. Each entry must correspond to the name of a variable
+          specified in either the 'vars', 'internal_vars' or the data_var field of the pattern.
+          Where an OWL entity is specified, the label for the OWL entity should be
+          used in the substitution.
+          SPECIAL RULES FOR multi_clause_printf context: In this context, list_vars are permitted.
+          If an list is empty, the clause and any subclauses must not be added.  for lists of
+          length > 1, mutiple clauses should be added, using the specified separator
+        type: array
+        items: { type: string }
+      sub_clauses:
+        description: >
+          List of clauses that depends on this clause. If parent clause cannot be printed,
+          all sub_clauses are also discarded.
+        type: array
+        items: { $ref : '#/definitions/multi_clause_printf' }
+
+  function:
+    oneOf:
+      - { $ref : "#/definitions/join" }
+      - { $ref : "#/definitions/regex_sub" }
+
+  join:
+    properties:
+      sep:
+        type: string
+        description: >
+          A string used as value separator while joining list type (multi value) variables.
+
+  printf_annotation:
+    type: object
+    additionalProperties: False
+    oneOf:
+      - required: [ annotationProperty, text ]
+      - required: [ multi_clause ]
+    properties:
+      annotationProperty:
+        description: >
+         A string corresponding to the rdfs:label
+         of an owl annotation property. If the annotation property has no label,
+         the shortForm ID should be used. The annotation property must be listed
+         in the annotation property dictionary.'
+        type: string
+      annotations:
+        items: { $ref: "#/definitions/annotations" }
+        type: array
+      text:
+        description: A print format string.
+        type: string
+      vars:
+        description: >
+         An ordered list of variables for substitution into the accompanying
+         print format string. Each entry must correspond to the name of a variable
+         specified in either the 'vars' field or the data_var field of the pattern.
+         Where an OWL entity is specified, the label for the OWL entity should be
+         used in the substitution.  An empty var list can be specified simply by
+         leaving this field out.
+        items: {type: string}
+        type: array
+      multi_clause:
+        items: { $ref: '#/definitions/multi_clause_printf' }
+
+
+  list_annotation:
+    type: object
+    additionalProperties: False
+    required: [annotationProperty, value]
+    properties:
+      annotationProperty:
+        description: >
+         A string corresponding to the rdfs:label
+         of an owl annotation property. If the annotation property has no label,
+         the shortForm ID should be used. The annotation property must be listed
+         in the annotation property dictionary.'
+        type: string
+      value:
+        description: >
+         A single list variable (list_var or data_list_var).  Each item in this list
+         should be used to generate a separate annotation axiom.
+        type: string
+
+  iri_value_annotation:
+    type: object
+    additionalProperties: False
+    required: [annotationProperty, var]
+    properties:
+      annotationProperty:
+        description: A string corresponding to a key in the annotation property dictionary.
+        type: string
+      var:
+        description: The name of a variable specified in the 'vars' field. The IRI of the variable value will be the object of the annotation axiom.
+        type: string
+      annotations:
+        items: { $ref: "#/definitions/annotations" }
+        type: array
+
+  annotations:
+   oneOf:
+     - { $ref: "#/definitions/printf_annotation" }
+     - { $ref: "#/definitions/list_annotation" }
+     - { $ref: "#/definitions/iri_value_annotation" }
+
+  printf_owl:
+    type: object
+    additionalProperties: False
+    oneOf:
+      - required: [ axiom_type, text, vars ]
+      - required: [ multi_clause ]
+    properties:
+      annotations:
+        items: {$ref: '#/definitions/annotations'}
+        type: array
+      axiom_type:
+        description: >
+         OWL axiom type expressed as manchester syntax: equivalentTo,
+         subClassOf, disjointWith. GCI  - for general class inclusion axioms, is
+         also valid (although missing from manchester syntax.) This specifies the
+         axiom type to be generated from the text following substitution.'
+        enum: [equivalentTo, subClassOf, disjointWith, GCI]
+        type: string
+      text:
+        type: string
+        description: >
+         A print format string in OWL Manchester syntax. Each entry
+         must correspond to an entry in o the name of a var in the var field of the
+         pattern. Entries in single quotes must correspond to the labels of entries
+         in owl_entity dictionaries (classes, relations, dataProperties)
+      vars:
+        description: >
+         An ordered list of variables for substitution into the accompanying
+         print format string. Each entry must correspond to the name of a variable
+         specified in either the 'vars' field or the data_var field of the pattern.
+         An empty var list can be specified simply by leaving this field out.
+        items: {type: string}
+        type: array
+      multi_clause:
+        items: { $ref: '#/definitions/multi_clause_printf' }
+
+  printf_owl_convenience:
+    type: object
+    additionalProperties: False
+    oneOf:
+      - required: [ text, vars ]
+      - required: [ multi_clause ]
+    properties:
+      annotations:
+        items: {$ref: '#/definitions/annotations'}
+        type: array
+      text:
+        type: string
+        description: >
+         A print format string in OWL Manchester syntax. Each entry
+         must correspond to an entry in o the name of a var in the var field of the
+         pattern. Entries in single quotes must correspond to the labels of entries
+         in owl_entity dictionaries (classes, relations, dataProperties)
+      vars:
+        description: >
+         An ordered list of variables for substitution into the accompanying
+         print format string. Each entry must correspond to the name of a variable
+          specified in either the 'vars' field or the data_var field of the pattern.
+        items: {type: string}
+        type: array
+      multi_clause:
+        items: { $ref: '#/definitions/multi_clause_printf' }
+
+  regex_sub:
+    additionalProperties: False
+    required: [in, out, match, sub]
+    type: object
+    properties:
+      in:
+        type: string
+        description: name of input var
+      out:
+        type: string
+        description: >
+          Name of output var.  If input var specified an OWL entity then
+          readable identifier is used as input to substitution
+      match:
+        type: string
+        description: perl style regex match
+      sub:
+        type: string
+        description: perl style regex sub.  May include backreferences.
+
+  opa:
+    type: object
+    additionalProperties: False
+    required: [edge]
+    properties:
+      edge:
+        description: >
+          A triple specified as an ordered array with 3 elements
+          [subject, rel, object]
+          * rel must be the quoted name of a relation from the relations
+          (object property) dictionary.
+          * subject and object must be the name of an individual
+          specified in the nodes field.
+        type: array
+        items: { type: string }
+        minItems: 3
+        maxItems: 3
+      annotations:
+        type: array
+        items: { $ref: '#/definitions/annotations' }
+      not:
+        description: "Optional field for negated OPAs"
+        type: boolean
+
+  # TODO - split into separate schema file
+  printf_annotation_obo:
+    type: object
+    additionalProperties: False
+    oneOf:
+      - required: [ text, vars ]
+      - required: [ multi_clause ]
+    properties:
+      annotations:
+        items: { $ref: "#/definitions/annotations" }
+        type: array
+      xrefs:
+        description: >
+         Takes the name of a single data_list_var specifying a list of database
+         cross references.
+        type: string
+        mapping: "oboInOwl:hasDbXref"
+      text:
+        description: A print format string.
+        type: string
+      vars:
+        description: >
+         An ordered list of variables for substitution into the accompanying
+         print format string. Each entry must correspond to the name of a variable
+         specified in either the 'vars' field or the data_var field of the pattern.
+         Where an OWL entity is specified, the label for the OWL entity should be
+         used in the substitution.
+        items: {type: string}
+        type: array
+      multi_clause:
+        items: { $ref: '#/definitions/multi_clause_printf' }
+
+  list_annotation_obo:
+    type: object
+    additionalProperties: False
+    required: [value]
+    properties:
+      value:
+        description: >
+         A single list variable (list_var or data_list_var).  Each item in this list
+         should be used to generate a separate annotation axiom.
+        type: string
+      xrefs:
+        description: >
+         Takes the name of a single data_list_var specifying a list of database
+         cross references. Use of this field should add the same xref set to all
+         annotation axioms generated.
+        type: string
+        mapping: "oboInOwl:hasDbXref"
+
+
+# Generic properties
+
+properties:
+  pattern_name:
+    type: string  # possible to specify ASCII?
+    description: >
+      The name of the pattern.  This must be an ASCII string with
+      no spaces. The only special characters allowed are '_' and '-'.
+      By convention, this is used as the file name of the
+      pattern - with an appropriate extension.
+    doc_type: root
+
+  pattern_iri:
+    type: string
+    description: >
+       A global identifier for the pattern. This can be a full IRI or a CURIE, using
+       the same prefix mappings as other CURIEs in the pattern.
+    doc_type: root
+
+  base_IRI:  # not rqd, give JSON-LD base.
+    type: string # how to spec IRI?
+    description: "Specifies the base IRI to be used to generate new classes."
+    doc_type: root
+
+  contributors:
+    type: array
+    items: { type: string }
+    description: >
+       A list of authors of a pattern.
+       Each author must be specified using a URL or Curie - we recommend ORCID.
+       We do not recommend that this list is instantiated in terms generated using a pattern,
+       but where it is it should be instantiated as a set of annotation axioms using dc:contributor.
+    doc_type: root
+
+  description:
+    type: string # specify UTF-8 string?
+    description: "A free text description of the pattern.  Must be UTF-8 encoded."
+    doc_type: root
+
+  examples:
+    type: list
+    items: { type: string }
+    description:  "A list of example terms implementing this pattern."
+    doc_type: root
+
+  status:
+    type: string
+    description: "Implementation status of pattern."
+    enum:
+       - development
+       - published
+    doc_type: root
+
+  tags:
+    type: list
+    items: { type: string }
+    description: >
+       A list of strings used to tag a pattern for the purposes of arbitrary,
+       cross-cutting grouping of patterns.
+    doc_type: root
+
+  readable_identifiers:
+    type: array
+    items:  { type: string }
+    description: "A list of annotation properties used as naming fields, in order of preference."
+    doc_type: root
+
+#### owl_entity Dictionaries
+
+  classes:
+    type: object
+    description: "A dictionary of OWL classes. key :label; value : short form id"
+    doc_type: owl_entity_dict
+  objectProperties:
+    type: object
+    description: "A dictionary of OWL object properties. key : label; value : short form id"
+    doc_type: owl_entity_dict
+  relations:
+    # Just an alternative name for the ObjectProperties dict
+    type: object
+    description: "A dictionary of OWL object properties. key : label; value : short form id"
+    doc_type: owl_entity_dict
+  dataProperties:
+    type: object
+    description: "A dictionary of OWL data properties key : label; value : short form id"
+    doc_type: owl_entity_dict
+  annotationProperties:
+    type: object
+    description: "A dictionary of OWL annotation properties key : label; value : short form id"
+    doc_type: owl_entity_dict
+
+# Var types
+
+  vars:
+    type: object
+    propertyNames:
+       pattern: "^[A-Za-z_][A-Za-z0-9_]*$"
+    description: >
+     A dictionary of variables ranging over OWL classes.
+     Key = variable name, value = variable range as manchester syntax string.
+    doc_type: var_types
+
+  list_vars:
+    type: object
+    propertyNames:
+       pattern: "^[A-Za-z_][A-Za-z0-9_]*$"
+    description: >
+     A dictionary of variables refering to lists of owl classes.
+     Key = variable name, value = variable range of items in list specified as a valid OWL
+     data-type.
+    doc_type: var_types
+
+  data_vars:
+    type: object
+    propertyNames:
+       pattern: "^[A-Za-z_][A-Za-z0-9_]*$"
+    description: >
+     A dictionary of variables ranging over OWL data-types.
+     Key = variable name, value = variable range specified as a valid OWL
+     data-type.
+    doc_type: var_types
+
+  data_list_vars:
+    type: object
+    propertyNames:
+       pattern: "^[A-Za-z_][A-Za-z0-9_]*$"
+    description: >
+        A dictionary of variables rrefering to lists of some specified OWL data-types.
+        Key = variable name, value = variable range of all items in list,
+        specified as a valid OWL data-type.
+    doc_type: var_types
+
+  internal_vars:
+    type: array
+    properties:
+      var_name:
+        pattern: "^[A-Za-z_][A-Za-z0-9_]*$"
+        description: >
+          Name of the internal variable to be defined. Expected naming pattern is ^[A-Za-z_][A-Za-z0-9_]*$
+        type: string
+      input:
+        description: >
+          A list_vars or data_list_vars variable to which the given function applied.
+        type: string
+      apply: { $ref: "#/definitions/function" }
+    description: >
+      List of internal variable construction definitions. Given function is applied to the given multi value input
+      and the result is defined as a new internal variable.
+    doc_type: var_types
+
+# Var munging
+
+  substitutions:
+    type: array
+    items: { $ref : '#/definitions/regex_sub' }
+    doc_type: var_munging
+
+# Specifying axioms:
+
+  annotations:
+    items: { $ref: "#/definitions/annotations" }
+    type: array
+    doc_type: axioms
+
+  logical_axioms:
+    items: { $ref: '#/definitions/printf_owl'}
+    type: array
+    doc_type: axioms
+
+# logical convenience fields.
+# Where only one of any OWL axiom type is present, these convenience fields may be used.
+
+  equivalentTo:
+    $ref: '#/definitions/printf_owl_convenience'
+    doc_type: convenience
+
+  subClassOf:
+    $ref: '#/definitions/printf_owl_convenience'
+    doc_type: convenience
+
+  GCI:
+    $ref: '#/definitions/printf_owl_convenience'
+    doc_type: convenience
+
+  disjointWith:
+    $ref: '#/definitions/printf_owl_convenience'
+    doc_type: convenience
+
+# OBO fields - TODO split to a separate file with import statement.
+
+  name:
+    $ref: '#/definitions/printf_annotation_obo'
+    mapping: "rdfs:label"
+    doc_type: obo
+  comment:
+    $ref: '#/definitions/printf_annotation_obo'
+    mapping: "rdfs:comment"
+    doc_type: obo
+  def:
+    $ref: '#/definitions/printf_annotation_obo'
+    mapping: "obo:IAO_0000115"
+    doc_type: obo
+  namespace:
+    $ref: '#/definitions/printf_annotation_obo'
+    mapping: "oboInOwl:hasOBONamespace"
+    doc_type: obo
+
+  # TODO: make all these into list_annotations
+  exact_synonym:
+    $ref: '#/definitions/list_annotation_obo'
+    mapping: "oboInOwl:hasExactSynonym"
+    doc_type: obo
+  narrow_synonym:
+    $ref: '#/definitions/list_annotation_obo'
+    mapping: "oboInOwl:hasNarrowSynonym"
+    doc_type: obo
+  related_synonym:
+    $ref: '#/definitions/list_annotation_obo'
+    mapping: "oboInOwl:hasRelatedSynonym"
+    doc_type: obo
+  broad_synonym:
+    $ref: '#/definitions/list_annotation_obo'
+    mapping: "oboInOwl:hasBroadSynonym"
+    doc_type: obo
+  xref:
+    $ref: '#/definitions/list_annotation_obo'
+    mapping: "oboInOwl:hasDbXref"
+    doc_type: obo
+
+  generated_synonyms:
+    description: >
+      An OBO convenience field to allow the specification of exact synonyms
+      generated by interpolation of OWL entity names into printf text.
+      Each entry may be annotated.
+    type: array
+    items:
+      $ref: '#/definitions/printf_annotation_obo'
+      mapping: "oboInOwl:hasExactSynonym"
+    doc_type: obo
+
+  generated_narrow_synonyms:
+    description: >
+      An OBO convenience field to allow the specification of narrow synonyms
+      generated by interpolation of OWL entity names into printf text.
+      Each entry may be annotated.
+    type: array
+    items:
+      $ref: '#/definitions/printf_annotation_obo'
+      mapping: "oboInOwl:hasNarrowSynonym"
+    doc_type: obo
+
+  generated_broad_synonyms:
+    description: >
+      An OBO convenience field to allow the specification of broad synonyms
+      generated by interpolation of OWL entity names into printf text.
+      Each entry may be annotated.
+    type: array
+    items:
+      $ref: '#/definitions/printf_annotation_obo'
+      mapping: "oboInOwl:hasBroadSynonym"
+    doc_type: obo
+
+  generated_related_synonyms:
+    description: >
+      An OBO convenience field to allow the specification of related synonyms
+      generated by interpolation of OWL entity names into printf text.
+      Each entry may be annotated.
+    type: array
+    items:
+      $ref: '#/definitions/printf_annotation_obo'
+      mapping: "oboInOwl:hasBroadSynonym"
+    doc_type: obo
+
+# instance graph spec - TODO be split to a separate file
+
+  instance_graph:
+    type: object
+    additionalProperties: False
+    required: [nodes, edges]
+    properties:
+      nodes:
+        description: >
+                       Key = name of individual within this pattern doc
+                       Value = Type of individual specified using either
+                       the quoted name of a class in the class dictionary of this pattern
+                       or a var name.  This field does not support typing via
+                       anonymous class expressions
+        type: object
+      edges:
+        type: array
+        items: { $ref : '#/definitions/opa' }
+    doc_type: instance_graph
+
+
+
+
+
diff --git a/src/schema/dosdp_schema_doc.ini b/src/schema/dosdp_schema_doc.ini
new file mode 100644
index 0000000..f4cf7a1
--- /dev/null
+++ b/src/schema/dosdp_schema_doc.ini
@@ -0,0 +1,31 @@
+[root]
+title = Properties
+description =
+
+[owl_entity_dict]
+title = OWL Entity Dictionaries
+description =
+
+[var_types]
+title = Var Types
+description =
+
+[var_munging]
+title = Var Munging
+description =
+
+[axioms]
+title = Axioms
+description =
+
+[convenience]
+title = Logical Convenience Fields
+description = Where only one of any OWL axiom type is present, these convenience fields may be used.
+
+[obo]
+title = OBO fields
+description =
+
+[instance_graph]
+title = Instance Graph Spec
+description =
\ No newline at end of file


Test patch:
diff --git a/.github/workflows/tester.yaml b/.github/workflows/tester.yaml
index 3120818..14d22ce 100644
--- a/.github/workflows/tester.yaml
+++ b/.github/workflows/tester.yaml
@@ -26,4 +26,4 @@ jobs:
             python -m pip install --upgrade pip
             pip install -r requirements.txt
       - name: Run Test
-        run: PYTHONPATH=./src/scripts:$PYTHONPATH python -m unittest discover -s src -p '*_test.py'
+        run: PYTHONPATH=./src:$PYTHONPATH python -m unittest discover -s src -p '*_test.py'
diff --git a/spec/test/cellPartOfAnatomicalEntity.yaml b/src/attic/spec/test/cellPartOfAnatomicalEntity.yaml
similarity index 100%
rename from spec/test/cellPartOfAnatomicalEntity.yaml
rename to src/attic/spec/test/cellPartOfAnatomicalEntity.yaml
diff --git a/spec/test/test_positive.yaml b/src/attic/spec/test/test_positive.yaml
similarity index 100%
rename from spec/test/test_positive.yaml
rename to src/attic/spec/test/test_positive.yaml
diff --git a/spec/test_schema.py b/src/attic/spec/test_schema.py
similarity index 100%
rename from spec/test_schema.py
rename to src/attic/spec/test_schema.py
diff --git a/src/schema/test/__init__.py b/src/schema/test/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/schema/test/generic_test/__init__.py b/src/schema/test/generic_test/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/schema/test/generic_test/document_test.py b/src/schema/test/generic_test/document_test.py
new file mode 100644
index 0000000..b73de4f
--- /dev/null
+++ b/src/schema/test/generic_test/document_test.py
@@ -0,0 +1,158 @@
+import unittest
+import os
+from dosdp.document.schema import schema_create_docs
+from dosdp.document.pattern import patterns_create_docs as pattern_doc
+from dosdp.document import document
+
+
+class DocumentGenerationCase(unittest.TestCase):
+
+    def tearDown(self):
+        """
+        Delete files generated during tests
+        """
+        if os.path.exists(os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                       "../positive_test_set/patterns/data/acute.md")):
+            os.remove(os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                   "../positive_test_set/patterns/data/acute.md"))
+
+        if os.path.exists(os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                       "../positive_test_set/patterns/data/acute2.md")):
+            os.remove(os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                   "../positive_test_set/patterns/data/acute2.md"))
+
+        if os.path.exists(os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                       "../positive_test_set/patterns/data/generated/acute.md")):
+            os.remove(os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                   "../positive_test_set/patterns/data/generated/acute.md"))
+
+        if os.path.exists(os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                       "../positive_test_set/patterns/data/generated/acute2.md")):
+            os.remove(os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                   "../positive_test_set/patterns/data/generated/acute2.md"))
+
+        if os.path.exists(os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                       "../positive_test_set/patterns/data/generated/overview.md")):
+            os.remove(os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                   "../positive_test_set/patterns/data/generated/overview.md"))
+
+        if os.path.exists(os.path.join(os.getcwd(), "./dosdp_schema.md")):
+            os.remove(os.path.join(os.getcwd(), "./dosdp_schema.md"))
+
+        if os.path.exists(os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                       "../positive_test_set/patterns/data/generated/schema.md")):
+            os.remove(os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                   "../positive_test_set/patterns/data/generated/schema.md"))
+
+    def test_mapping_definition_search(self):
+        mappings = schema_create_docs.find_mapping_definitions()
+
+        self.assertTrue("printf_annotation_obo$xrefs" in mappings.keys())
+        self.assertEqual("oboInOwl:hasDbXref", mappings["printf_annotation_obo$xrefs"])
+
+        self.assertTrue("name" in mappings.keys())
+        self.assertEqual("rdfs:label", mappings["name"])
+
+        self.assertTrue("generated_narrow_synonyms$items" in mappings.keys())
+        self.assertEqual("oboInOwl:hasNarrowSynonym", mappings["generated_narrow_synonyms$items"])
+
+        self.assertTrue("generated_synonyms$items" in mappings.keys())
+        self.assertEqual("oboInOwl:hasExactSynonym", mappings["generated_synonyms$items"])
+
+    def test_is_schema(self):
+        self.assertTrue(pattern_doc.is_dosdp_pattern_file(os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                                                    "../positive_test_set/patterns/anchored_membrane_component.yaml")))
+        self.assertFalse(pattern_doc.is_dosdp_pattern_file(os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                                                     "../../dosdp_schema.yaml")))
+        self.assertTrue(pattern_doc.is_dosdp_pattern_file(os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                                                    "../positive_test_set/patterns/expression_pattern.yaml")))
+        self.assertFalse(pattern_doc.is_dosdp_pattern_file(os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                                                     "../negative_test_set/dummy.yaml")))
+
+    def test_pattern_interface_single_param(self):
+        document.generate_pattern_documentation(os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                                             "../positive_test_set/patterns/data/acute.yaml"))
+        self.assertTrue(os.path.exists(os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                                    "../positive_test_set/patterns/data/acute.md")))
+
+    def test_pattern_interface_two_param(self):
+        document.generate_pattern_documentation(os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                                             "../positive_test_set/patterns/data/acute.yaml"),
+                                                os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                                             "../positive_test_set/patterns/data/acute2.md"))
+        self.assertTrue(os.path.exists(os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                                    "../positive_test_set/patterns/data/acute2.md")))
+
+        self.assertFalse(os.path.exists(os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                                     "../positive_test_set/patterns/data/overview.md")))
+
+    def test_pattern_interface_folder_input(self):
+        document.generate_pattern_documentation(os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                                             "../positive_test_set/patterns/data/"),
+                                                os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                                             "../positive_test_set/patterns/data/generated"))
+        self.assertTrue(os.path.exists(os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                                    "../positive_test_set/patterns/data/generated/acute.md")))
+        self.assertTrue(os.path.exists(os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                                    "../positive_test_set/patterns/data/generated/acute2.md")))
+        self.assertFalse(os.path.exists(os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                                     "../positive_test_set/patterns/data/generated/dummy.md")))
+
+    def test_pattern_interface_non_pattern(self):
+        document.generate_pattern_documentation(os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                                             "../positive_test_set/patterns/data/dummy.yaml"))
+        self.assertFalse(os.path.exists(os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                                     "./dummy.md")))
+
+    def test_pattern_interface_folder_none_params(self):
+        document.generate_pattern_documentation(os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                                             "../positive_test_set/patterns/data/"))
+        self.assertFalse(os.path.exists(os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                                     "../positive_test_set/patterns/data/generated/acute.md")))
+
+    def test_patterns_interface_overview(self):
+        document.generate_pattern_documentation(os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                                             "../positive_test_set/patterns/data/"),
+                                                os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                                             "../positive_test_set/patterns/data/generated"))
+
+        self.assertTrue(os.path.exists(os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                                    "../positive_test_set/patterns/data/generated/overview.md")))
+
+    def test_pattern_interface_with_sample_data(self):
+        document.generate_pattern_documentation(os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                                             "../positive_test_set/patterns/data/acute.yaml"),
+                                                md_location=os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                                                        "../positive_test_set/patterns/data/acute.md"),
+                                                sample_data_dir=os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                                                            "../positive_test_set/patterns/data/sample_data"))
+        self.assertFalse(os.path.exists(os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                                     "../positive_test_set/patterns/data/generated/acute.md")))
+
+    def test_pattern_interface_folder_with_sample_data(self):
+        document.generate_pattern_documentation(os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                                             "../positive_test_set/patterns/data/"),
+                                                os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                                             "../positive_test_set/patterns/data/generated"),
+                                                sample_data_dir=os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                                                             "../positive_test_set/patterns/data/sample_data"))
+        self.assertTrue(os.path.exists(os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                                    "../positive_test_set/patterns/data/generated/acute.md")))
+        self.assertTrue(os.path.exists(os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                                    "../positive_test_set/patterns/data/generated/acute2.md")))
+        self.assertFalse(os.path.exists(os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                                     "../positive_test_set/patterns/data/generated/dummy.md")))
+
+    def test_schema_interface(self):
+        document.generate_schema_documentation()
+        self.assertTrue(os.path.exists(os.path.join(os.getcwd(), "./dosdp_schema.md")))
+
+    def test_schema_interface_single_param(self):
+        document.generate_schema_documentation(os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                                            "../positive_test_set/patterns/data/generated/schema.md"))
+        self.assertTrue(os.path.exists(os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                                    "../positive_test_set/patterns/data/generated/schema.md")))
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/src/schema/test/generic_test/schema_test.py b/src/schema/test/generic_test/schema_test.py
index 1c66af4..ae14c3a 100644
--- a/src/schema/test/generic_test/schema_test.py
+++ b/src/schema/test/generic_test/schema_test.py
@@ -5,7 +5,7 @@
 from jsonschema import Draft4Validator
 
 SCHEMA = os.path.join(os.path.dirname(os.path.realpath(__file__)),
-                      "../../../../spec/DOSDP_schema_full.yaml")
+                      "../../dosdp_schema.yaml")
 POSITIVE_PATTERN_1 = os.path.join(os.path.dirname(os.path.realpath(__file__)),
                                   "../positive_test_set/patterns/acute.yaml")
 POSITIVE_PATTERN_2 = os.path.join(os.path.dirname(os.path.realpath(__file__)),
diff --git a/src/schema/test/generic_test/test_suite.py b/src/schema/test/generic_test/test_suite.py
new file mode 100644
index 0000000..836adf4
--- /dev/null
+++ b/src/schema/test/generic_test/test_suite.py
@@ -0,0 +1,7 @@
+import unittest
+
+loader = unittest.TestLoader()
+suite = loader.discover(start_dir='./', pattern='*_test.py')
+
+runner = unittest.TextTestRunner()
+runner.run(suite)
diff --git a/src/schema/test/generic_test/validator_test.py b/src/schema/test/generic_test/validator_test.py
new file mode 100644
index 0000000..037b682
--- /dev/null
+++ b/src/schema/test/generic_test/validator_test.py
@@ -0,0 +1,62 @@
+import unittest
+import os
+
+
+from dosdp.validator import validate
+
+POSITIVE_PATTERNS_FOLDER = os.path.join(os.path.dirname(os.path.realpath(__file__)), "../positive_test_set/patterns")
+POSITIVE_PATTERN_1 = os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                  "../positive_test_set/export_across_membrane.yaml")
+
+NEGATIVE_PATTERN_INDENTATION = os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                            "../negative_test_set/wrong_indentation.yaml")
+NEGATIVE_PATTERN_QUOTES = os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                       "../negative_test_set/quotes_miss_match.yaml")
+NEGATIVE_PATTERN_UNDECLARED_VAR = os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                               "../negative_test_set/undeclared_var.yaml")
+NEGATIVE_PATTERN_UNDECLARED_CLASS = os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                                 "../negative_test_set/undeclared_class.yaml")
+NEGATIVE_PATTERN_UNDECLARED_REL = os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                               "../negative_test_set/undeclared_relation.yaml")
+NEGATIVE_PATTERN_UNDECLARED_REL_MC = os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                                  "../negative_test_set/undeclared_relation_multi_clause.yaml")
+NEGATIVE_PATTERN_AXIOM_CLAUSE_NESTING = os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                                     "../negative_test_set/axiom_multi_clause_nesting.yaml")
+NEGATIVE_PATTERN_AXIOM_SEPARATOR = os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                                "../negative_test_set/axiom_separator.yaml")
+NEGATIVE_PATTERN_MULTI_CLAUSE_MULTI_LIST = os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                                        "../negative_test_set/multi_clause_with_multi_list.yaml")
+NEGATIVE_PATTERN_MULTI_CLAUSE_MULTI_LIST2 = os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                                         "../negative_test_set/multi_clause_with_multi_list2.yaml")
+
+
+class ValidatorTest(unittest.TestCase):
+
+    def test_positive_folder(self):
+        self.assertTrue(validate(POSITIVE_PATTERNS_FOLDER))
+
+    def test_positive_file(self):
+        self.assertTrue(validate(POSITIVE_PATTERN_1))
+
+    def test_wrong_indentation(self):
+        self.assertFalse(validate(NEGATIVE_PATTERN_INDENTATION))
+
+    def test_undeclared_var(self):
+        self.assertFalse(validate(NEGATIVE_PATTERN_UNDECLARED_VAR))
+
+    def test_undeclared_class(self):
+        self.assertFalse(validate(NEGATIVE_PATTERN_UNDECLARED_CLASS))
+
+    def test_undeclared_relation(self):
+        self.assertFalse(validate(NEGATIVE_PATTERN_UNDECLARED_REL))
+        self.assertFalse(validate(NEGATIVE_PATTERN_UNDECLARED_REL_MC))
+
+    def test_axiom_clause_nesting(self):
+        self.assertFalse(validate(NEGATIVE_PATTERN_AXIOM_CLAUSE_NESTING))
+
+    def test_axiom_separator(self):
+        self.assertFalse(validate(NEGATIVE_PATTERN_AXIOM_SEPARATOR))
+
+    def test_single_list_per_multi_clause(self):
+        self.assertFalse(validate(NEGATIVE_PATTERN_MULTI_CLAUSE_MULTI_LIST))
+        self.assertFalse(validate(NEGATIVE_PATTERN_MULTI_CLAUSE_MULTI_LIST2))
diff --git a/src/schema/test/negative_test_set/__init__.py b/src/schema/test/negative_test_set/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/schema/test/negative_test_set/axiom_multi_clause_nesting.yaml b/src/schema/test/negative_test_set/axiom_multi_clause_nesting.yaml
new file mode 100644
index 0000000..be44981
--- /dev/null
+++ b/src/schema/test/negative_test_set/axiom_multi_clause_nesting.yaml
@@ -0,0 +1,62 @@
+pattern_name: brainCellClasses
+pattern_iri: http://purl.obolibrary.org/obo/odk/brainCellClasses.yaml
+description: "Class template for BDS."
+
+classes:
+  "cell": "CL:0000000"
+  "thing": "owl:Thing"
+
+relations:
+  expresses: "RO:0002292"
+
+annotationProperties:
+  hasExactSynonym: "oboInOwl:hasExactSynonym"
+  rdfsComment: "rdfs:comment"
+  hasDbXref: "oboInOwl:hasDbXref"
+
+list_vars:
+  Expresses: "'thing'"
+  Expresses2: "'thing'"
+
+vars:
+  Classification: "'cell'"
+
+data_list_vars:
+  Curated_synonyms: "xsd:string"
+  Expresses_comment: "xsd:string"
+  Expresses_pub: "xsd:string"
+
+data_vars:
+  Comment: "xsd:string"
+
+comment:
+   text: "%s"
+   vars:
+      - Comment
+
+logical_axioms:
+   - axiom_type: subClassOf
+     multi_clause:
+      sep: " and "
+      clauses:
+        - text: "'expresses' some %s"
+          vars:
+            - Expresses
+          sub_clauses:
+            - sep: " and "
+            - clauses:
+                - text: '%s'
+                  vars:
+                    - Expresses
+     annotations:
+       - annotationProperty: rdfsComment
+         text: "%s"
+         vars:
+           - Expresses_comment
+       - annotationProperty: hasDbXref
+         multi_clause:
+           sep: " "
+           clauses:
+             - text: '%s'
+               vars:
+                 - Expresses_pub
diff --git a/src/schema/test/negative_test_set/axiom_separator.yaml b/src/schema/test/negative_test_set/axiom_separator.yaml
new file mode 100644
index 0000000..7dd7592
--- /dev/null
+++ b/src/schema/test/negative_test_set/axiom_separator.yaml
@@ -0,0 +1,56 @@
+pattern_name: brainCellClasses
+pattern_iri: http://purl.obolibrary.org/obo/odk/brainCellClasses.yaml
+description: "Class template for BDS."
+
+classes:
+  "cell": "CL:0000000"
+  "thing": "owl:Thing"
+
+relations:
+  expresses: "RO:0002292"
+
+annotationProperties:
+  hasExactSynonym: "oboInOwl:hasExactSynonym"
+  rdfsComment: "rdfs:comment"
+  hasDbXref: "oboInOwl:hasDbXref"
+
+list_vars:
+  Expresses: "'thing'"
+  Expresses2: "'thing'"
+
+vars:
+  Classification: "'cell'"
+
+data_list_vars:
+  Curated_synonyms: "xsd:string"
+  Expresses_comment: "xsd:string"
+  Expresses_pub: "xsd:string"
+
+data_vars:
+  Comment: "xsd:string"
+
+comment:
+   text: "%s"
+   vars:
+      - Comment
+
+logical_axioms:
+   - axiom_type: subClassOf
+     multi_clause:
+      sep: " may "
+      clauses:
+        - text: "'expresses' some %s"
+          vars:
+            - Expresses
+     annotations:
+       - annotationProperty: rdfsComment
+         text: "%s"
+         vars:
+           - Expresses_comment
+       - annotationProperty: hasDbXref
+         multi_clause:
+           sep: " "
+           clauses:
+             - text: '%s'
+               vars:
+                 - Expresses_pub
diff --git a/src/schema/test/negative_test_set/dummy.yaml b/src/schema/test/negative_test_set/dummy.yaml
new file mode 100644
index 0000000..6eafc69
--- /dev/null
+++ b/src/schema/test/negative_test_set/dummy.yaml
@@ -0,0 +1,15 @@
+# An unrelated yaml file
+- martin:
+    name: Martin D'vloper
+    job: Developer
+    skills:
+      - python
+      - perl
+      - pascal
+- tabitha:
+    name: Tabitha Bitumen
+    job: Developer
+    skills:
+      - lisp
+      - fortran
+      - erlang
\ No newline at end of file
diff --git a/src/schema/test/negative_test_set/generic_go_pattern_outline.yaml b/src/schema/test/negative_test_set/generic_go_pattern_outline.yaml
new file mode 100644
index 0000000..e79a190
--- /dev/null
+++ b/src/schema/test/negative_test_set/generic_go_pattern_outline.yaml
@@ -0,0 +1,53 @@
+pattern_name: .
+
+classes:
+  '.':'.'
+  
+relations:
+  '.':'.'
+  
+vars: 
+  '.':'.'
+  
+data_list_vars:
+  def_xrefs: "xsd:string"
+  cross_references: "xsd:string"
+  exact_syn: "xsd:string"
+  narrow_syn: "xsd:string"
+  broad_syn: "xsd:string"
+  related_syn: "xsd:string"
+  
+name:
+  text: '.'
+  vars: 
+    - '.'
+    
+def: 
+  text: '.'
+  vars:
+    - '.'
+  xrefs: 
+     value: def_xrefs
+  
+xrefs: 
+   value: cross_references
+
+namespace: 
+  text: '.'
+
+equivalentTo: 
+  text: '.'
+  vars:
+    - '.'
+    
+exact_synonyms: 
+   value: exact_syn
+narrow_synonyms: 
+   value: narrow_syn
+broad_synonyms: 
+   value: broad_syn
+related_synonyms: 
+   value: related_syn
+
+ 
+   
diff --git a/src/schema/test/negative_test_set/membrane_receptor_activity_draft.yaml b/src/schema/test/negative_test_set/membrane_receptor_activity_draft.yaml
new file mode 100644
index 0000000..418b7f8
--- /dev/null
+++ b/src/schema/test/negative_test_set/membrane_receptor_activity_draft.yaml
@@ -0,0 +1,34 @@
+pattern_name: membrane_receptor_activity
+
+classes:
+    'receptor activity': GO_
+    'membrane': GO_
+    'binding activity': GO_
+    'catalytic activity': GO_
+    'transporter activity': GO_
+    
+
+relations:
+    'has part': BFO_
+    'directly activates': RO_
+    'occurs in': RO_
+
+vars:
+    'effector activity': "'catalytic activity' or 'transporter activity'"  # may be others..
+    'ligand': '"chemical entity" or "protein"' # chemical entity should be sufficient given Pro links to ChEBI - are they in place?
+    
+subClassOf:
+    "receptor activity' and (
+        'has_part' some (
+            'binding activity' that has_input some %s and 
+                directly_activates some %s))
+        and ('has_part' some 'effector activity') 
+        and (occurs_in some membrane)"  # Strictly, this has co-reference issues, e.g. the regulation could be of another 
+  vars:  
+    - 'ligand'
+    - 'effector activity'
+    - 'effector activity'
+
+    
+    
+# Could we make a has_ligand shortcut relation?  Problem is, it needs two slots.
\ No newline at end of file
diff --git a/src/schema/test/negative_test_set/multi_clause_with_multi_list.yaml b/src/schema/test/negative_test_set/multi_clause_with_multi_list.yaml
new file mode 100644
index 0000000..460f9b2
--- /dev/null
+++ b/src/schema/test/negative_test_set/multi_clause_with_multi_list.yaml
@@ -0,0 +1,108 @@
+pattern_name: multiClauseTest
+pattern_iri: http://purl.obolibrary.org/obo/odk/brainCellRegionMinimalMarkers.yaml
+description: "Naming scheme and axiomatisation for brain cells from BDS."
+
+classes:
+  "animal cell": "CL:0000548"
+  "Vertebrata <vertebrates>": "NCBITaxon:7742"
+  "regional part of brain": "UBERON:0002616"
+  "cell": "CL:0000000"
+  "sequence_feature": "SO:0000110"
+  "layer of neocortex": "UBERON:0002301"
+  "projection type": "PATO:..."
+
+relations:
+  part_of: "BFO:0000050"
+  in_taxon: "RO:0002162"
+  has_soma_location: "RO:0002100"
+  bearer_of: "RO:0002162"
+
+vars:
+  gross_cell_type: "'cell'"
+  taxon: "'Vertebrata <vertebrates>'"
+  brain_region: "'regional part of brain'"
+  projection_type: "'projection type'"
+
+data_vars:
+  cell_set_preferred_alias: "xsd:string"
+
+list_vars:
+  minimal_markers: "'sequence_feature'"
+  allen_markers: "'sequence_feature'"
+  layers: "'layer of neocortex'"
+
+internal_vars:
+  - var_name: minimal_markers_cat
+    input: minimal_markers
+    apply:
+      join:
+        sep: ','
+  - var_name: allen_markers_cat
+    input: allen_markers
+    apply:
+      join:
+        sep: ','
+  - var_name: cortical_layer_cat
+    input: layers
+    apply:
+      join:
+        sep: ' or '
+
+name:
+  multi_clause:
+    sep: " "
+    clauses:
+      - text: "%s %s %s (%s)"
+        vars:
+          - cell_set_preferred_alias
+          - brain_region
+          - gross_cell_type
+          - taxon
+
+def:
+  multi_clause:
+    sep: " "
+    clauses:
+      - text: 'A %s of the %s %s. These cells can be distinguished from other cells in the %s by their expression of %s.'
+        vars:
+          - gross_cell_type
+          - taxon
+          - brain_region
+          - brain_region
+          - minimal_markers_cat
+        sub_clauses:
+          - sep: ' '
+          - clauses:
+              - text: 'These cells also express %s %s.'
+                vars:
+                  - allen_markers_cat
+                  - minimal_markers
+              - text: 'These cells have projection type %s %s.'
+                vars:
+                  - projection_type
+                  - allen_markers
+              - text: 'The soma of these cells in located in: %s.'
+                vars:
+                  - cortical_layer_cat
+
+generated_synonyms:
+   - text: "%s expressing %s of %s (%s)"
+     vars:
+        - minimal_markers_cat
+        - gross_cell_type
+        - brain_region
+        - taxon
+
+logical_axioms:
+   - axiom_type: subClassOf
+     text: "'in_taxon' some %s"
+     vars:
+        - taxon
+   - axiom_type: subClassOf
+     text: "%s"
+     vars:
+       - gross_cell_type
+   - axiom_type: subClassOf
+     text: "'bearer_of' some %s"
+     vars:
+       - projection_type
diff --git a/src/schema/test/negative_test_set/multi_clause_with_multi_list2.yaml b/src/schema/test/negative_test_set/multi_clause_with_multi_list2.yaml
new file mode 100644
index 0000000..0f09814
--- /dev/null
+++ b/src/schema/test/negative_test_set/multi_clause_with_multi_list2.yaml
@@ -0,0 +1,105 @@
+pattern_name: multiClauseTest
+pattern_iri: http://purl.obolibrary.org/obo/odk/brainCellRegionMinimalMarkers.yaml
+description: "Naming scheme and axiomatisation for brain cells from BDS."
+
+classes:
+  "animal cell": "CL:0000548"
+  "Vertebrata <vertebrates>": "NCBITaxon:7742"
+  "regional part of brain": "UBERON:0002616"
+  "cell": "CL:0000000"
+  "sequence_feature": "SO:0000110"
+  "layer of neocortex": "UBERON:0002301"
+  "projection type": "PATO:..."
+
+relations:
+  part_of: "BFO:0000050"
+  in_taxon: "RO:0002162"
+  has_soma_location: "RO:0002100"
+  bearer_of: "RO:0002162"
+
+vars:
+  gross_cell_type: "'cell'"
+  taxon: "'Vertebrata <vertebrates>'"
+  brain_region: "'regional part of brain'"
+  projection_type: "'projection type'"
+
+data_vars:
+  cell_set_preferred_alias: "xsd:string"
+
+list_vars:
+  minimal_markers: "'sequence_feature'"
+  allen_markers: "'sequence_feature'"
+  layers: "'layer of neocortex'"
+
+internal_vars:
+  - var_name: minimal_markers_cat
+    input: minimal_markers
+    apply:
+      join:
+        sep: ','
+  - var_name: allen_markers_cat
+    input: allen_markers
+    apply:
+      join:
+        sep: ','
+  - var_name: cortical_layer_cat
+    input: layers
+    apply:
+      join:
+        sep: ' or '
+
+name:
+   text: "%s %s %s (%s)"
+   vars:
+      - cell_set_preferred_alias
+      - brain_region
+      - gross_cell_type
+      - taxon
+
+def:
+  multi_clause:
+    sep: " "
+    clauses:
+      - text: 'A %s of the %s %s. These cells can be distinguished from other cells in the %s by their expression of %s.'
+        vars:
+          - gross_cell_type
+          - taxon
+          - brain_region
+          - brain_region
+          - minimal_markers_cat
+        sub_clauses:
+          - sep: ' '
+          - clauses:
+              - text: 'These cells also express %s %s.'
+                vars:
+                  - allen_markers_cat
+                  - minimal_markers
+              - text: 'These cells have projection type %s %s.'
+                vars:
+                  - projection_type
+                  - minimal_markers
+              - text: 'The soma of these cells in located in: %s.'
+                vars:
+                  - cortical_layer_cat
+
+generated_synonyms:
+   - text: "%s expressing %s of %s (%s)"
+     vars:
+        - minimal_markers_cat
+        - gross_cell_type
+        - brain_region
+        - taxon
+
+logical_axioms:
+   - axiom_type: subClassOf
+     text: "'in_taxon' some %s"
+     vars:
+        - taxon
+   - axiom_type: subClassOf
+     text: "%s"
+     vars:
+       - gross_cell_type
+   - axiom_type: subClassOf
+     text: "'bearer_of' some %s"
+     vars:
+       - projection_type
diff --git a/src/schema/test/negative_test_set/quotes_miss_match.yaml b/src/schema/test/negative_test_set/quotes_miss_match.yaml
new file mode 100644
index 0000000..858a133
--- /dev/null
+++ b/src/schema/test/negative_test_set/quotes_miss_match.yaml
@@ -0,0 +1,56 @@
+pattern_name: import_across_membrane
+
+classes: 
+  membrane: GO_0016020
+  cellular_component: GO_0005575
+  chemical entity: CHEBI_24431
+  macromolecular complex: GO_0032991
+  transcript: SO_0000673
+  transport: GO_0006810
+
+relations:
+  transports or maintains localization of: RO_0002313
+  has target start location: RO_0002338
+  has target end location: RO_0002339
+  results in transport across: RO_0002342
+  exports: RO_0002345
+
+vars: 
+  membrane: "'membrane"
+  cargo: "'chemical entity' or 'macromolecular complex' or 'transcript'"
+  start: "'cellular_component'"
+  end: "'cellular_component'"
+
+name: 
+  text: "%s export across %s"
+  vars: 
+    - cargo
+    - membrane
+
+def: 
+  text: "The directed import of %s from %s, across the %s and into the %s."
+  vars:
+    - cargo
+    - start
+    - membrane
+    - end
+
+comment: 
+  text: "This term covers %s *across* the %s  through a channel or pore.  It does not cover export via vesicle fusion with %s, as in this case transport does not involve crossing the membrane."
+  vars:
+    - cargo
+    - membrane
+    - membrane
+
+equivalentTo: 
+  text: "'transport' that
+    and ('has target start location' some %s)
+    and ('has target end location' some %s)
+    and ('exports' some %s)
+    and ('results in transport across' some %s)"
+    
+  vars:
+    - start
+    - end
+    - cargo
+    - membrane
diff --git a/src/schema/test/negative_test_set/undeclared_class.yaml b/src/schema/test/negative_test_set/undeclared_class.yaml
new file mode 100644
index 0000000..c876079
--- /dev/null
+++ b/src/schema/test/negative_test_set/undeclared_class.yaml
@@ -0,0 +1,56 @@
+pattern_name: import_across_membrane
+
+classes: 
+  membrane: GO_0016020
+  cellular_component: GO_0005575
+  chemical entity: CHEBI_24431
+  macromolecular complex: GO_0032991
+  transcript: SO_0000673
+  transport: GO_0006810
+
+relations:
+  transports or maintains localization of: RO_0002313
+  has target start location: RO_0002338
+  has target end location: RO_0002339
+  results in transport across: RO_0002342
+  exports: RO_0002345
+
+vars: 
+  membrane: "'membrane'"
+  cargo: "'chemical entity' or 'macromolecular complex' or 'transcript'"
+  start: "'cellular_component'"
+  end: "'cellular_component'"
+
+name: 
+  text: "%s export across %s"
+  vars: 
+    - cargo
+    - membrane
+
+def: 
+  text: "The directed import of %s from %s, across the %s and into the %s."
+  vars:
+    - cargo
+    - start
+    - membrane
+    - end
+
+comment: 
+  text: "This term covers %s *across* the %s  through a channel or pore.  It does not cover export via vesicle fusion with %s, as in this case transport does not involve crossing the membrane."
+  vars:
+    - cargo
+    - membrane
+    - membrane
+
+equivalentTo: 
+  text: "'NOT_EXISTING_CLASS' that
+    and ('has target start location' some %s)
+    and ('has target end location' some %s)
+    and ('exports' some %s)
+    and ('results in transport across' some %s)"
+    
+  vars:
+    - start
+    - end
+    - cargo
+    - membrane
diff --git a/src/schema/test/negative_test_set/undeclared_relation.yaml b/src/schema/test/negative_test_set/undeclared_relation.yaml
new file mode 100644
index 0000000..318ed06
--- /dev/null
+++ b/src/schema/test/negative_test_set/undeclared_relation.yaml
@@ -0,0 +1,56 @@
+pattern_name: import_across_membrane
+
+classes: 
+  membrane: GO_0016020
+  cellular_component: GO_0005575
+  chemical entity: CHEBI_24431
+  macromolecular complex: GO_0032991
+  transcript: SO_0000673
+  transport: GO_0006810
+
+relations:
+  transports or maintains localization of: RO_0002313
+  has target start location: RO_0002338
+  has target end location: RO_0002339
+  results in transport across: RO_0002342
+  exports: RO_0002345
+
+vars: 
+  membrane: "'membrane'"
+  cargo: "'chemical entity' or 'macromolecular complex' or 'transcript'"
+  start: "'cellular_component'"
+  end: "'cellular_component'"
+
+name: 
+  text: "%s export across %s"
+  vars: 
+    - cargo
+    - membrane
+
+def: 
+  text: "The directed import of %s from %s, across the %s and into the %s."
+  vars:
+    - cargo
+    - start
+    - membrane
+    - end
+
+comment: 
+  text: "This term covers %s *across* the %s  through a channel or pore.  It does not cover export via vesicle fusion with %s, as in this case transport does not involve crossing the membrane."
+  vars:
+    - cargo
+    - membrane
+    - membrane
+
+equivalentTo: 
+  text: "'transport' that
+    and ('NOT_DECLARED_RELATION' some %s)
+    and ('has target end location' some %s)
+    and ('exports' some %s)
+    and ('results in transport across' some %s)"
+    
+  vars:
+    - start
+    - end
+    - cargo
+    - membrane
diff --git a/src/schema/test/negative_test_set/undeclared_relation_multi_clause.yaml b/src/schema/test/negative_test_set/undeclared_relation_multi_clause.yaml
new file mode 100644
index 0000000..2f3462e
--- /dev/null
+++ b/src/schema/test/negative_test_set/undeclared_relation_multi_clause.yaml
@@ -0,0 +1,56 @@
+pattern_name: brainCellClasses
+pattern_iri: http://purl.obolibrary.org/obo/odk/brainCellClasses.yaml
+description: "Class template for BDS."
+
+classes:
+  "cell": "CL:0000000"
+  "thing": "owl:Thing"
+
+#relations:
+#  expresses: "RO:0002292"
+
+annotationProperties:
+  hasExactSynonym: "oboInOwl:hasExactSynonym"
+  rdfsComment: "rdfs:comment"
+  hasDbXref: "oboInOwl:hasDbXref"
+
+list_vars:
+  Expresses: "'thing'"
+  Expresses2: "'thing'"
+
+vars:
+  Classification: "'cell'"
+
+data_list_vars:
+  Curated_synonyms: "xsd:string"
+  Expresses_comment: "xsd:string"
+  Expresses_pub: "xsd:string"
+
+data_vars:
+  Comment: "xsd:string"
+
+comment:
+   text: "%s"
+   vars:
+      - Comment
+
+logical_axioms:
+   - axiom_type: subClassOf
+     multi_clause:
+      sep: " and "
+      clauses:
+        - text: "'expresses' some %s"
+          vars:
+            - Expresses
+     annotations:
+       - annotationProperty: rdfsComment
+         text: "%s"
+         vars:
+           - Expresses_comment
+       - annotationProperty: hasDbXref
+         multi_clause:
+           sep: " "
+           clauses:
+             - text: '%s'
+               vars:
+                 - Expresses_pub
diff --git a/src/schema/test/negative_test_set/undeclared_var.yaml b/src/schema/test/negative_test_set/undeclared_var.yaml
new file mode 100644
index 0000000..5e34441
--- /dev/null
+++ b/src/schema/test/negative_test_set/undeclared_var.yaml
@@ -0,0 +1,56 @@
+pattern_name: import_across_membrane
+
+classes: 
+  membrane: GO_0016020
+  cellular_component: GO_0005575
+  chemical entity: CHEBI_24431
+  macromolecular complex: GO_0032991
+  transcript: SO_0000673
+  transport: GO_0006810
+
+relations:
+  transports or maintains localization of: RO_0002313
+  has target start location: RO_0002338
+  has target end location: RO_0002339
+  results in transport across: RO_0002342
+  exports: RO_0002345
+
+vars: 
+  membrane: "'membrane'"
+  cargo: "'chemical entity' or 'macromolecular complex' or 'transcript'"
+  start: "'cellular_component'"
+  end: "'cellular_component'"
+
+name: 
+  text: "%s export across %s"
+  vars: 
+    - cargo
+    - membrane
+
+def: 
+  text: "The directed import of %s from %s, across the %s and into the %s."
+  vars:
+    - not_existing_var
+    - start
+    - membrane
+    - end
+
+comment: 
+  text: "This term covers %s *across* the %s  through a channel or pore.  It does not cover export via vesicle fusion with %s, as in this case transport does not involve crossing the membrane."
+  vars:
+    - cargo
+    - membrane
+    - membrane
+
+equivalentTo: 
+  text: "'transport' that
+    and ('has target start location' some %s)
+    and ('has target end location' some %s)
+    and ('exports' some %s)
+    and ('results in transport across' some %s)"
+    
+  vars:
+    - start
+    - end
+    - cargo
+    - membrane
diff --git a/src/schema/test/negative_test_set/wrong_indentation.yaml b/src/schema/test/negative_test_set/wrong_indentation.yaml
new file mode 100644
index 0000000..1dc6734
--- /dev/null
+++ b/src/schema/test/negative_test_set/wrong_indentation.yaml
@@ -0,0 +1,56 @@
+pattern_name: import_across_membrane
+
+classes: 
+ membrane: GO_0016020
+  cellular_component: GO_0005575
+  chemical entity: CHEBI_24431
+  macromolecular complex: GO_0032991
+  transcript: SO_0000673
+  transport: GO_0006810
+
+relations:
+  transports or maintains localization of: RO_0002313
+  has target start location: RO_0002338
+  has target end location: RO_0002339
+  results in transport across: RO_0002342
+  exports: RO_0002345
+
+vars: 
+  membrane: "'membrane'"
+  cargo: "'chemical entity' or 'macromolecular complex' or 'transcript'"
+  start: "'cellular_component'"
+  end: "'cellular_component'"
+
+name: 
+  text: "%s export across %s"
+  vars: 
+    - cargo
+    - membrane
+
+def: 
+  text: "The directed import of %s from %s, across the %s and into the %s."
+  vars:
+    - cargo
+    - start
+    - membrane
+    - end
+
+comment: 
+  text: "This term covers %s *across* the %s  through a channel or pore.  It does not cover export via vesicle fusion with %s, as in this case transport does not involve crossing the membrane."
+  vars:
+    - cargo
+    - membrane
+    - membrane
+
+equivalentTo: 
+  text: "'transport' that
+    and ('has target start location' some %s)
+    and ('has target end location' some %s)
+    and ('exports' some %s)
+    and ('results in transport across' some %s)"
+    
+  vars:
+    - start
+    - end
+    - cargo
+    - membrane
diff --git a/src/schema/test/positive_test_set/__init__.py b/src/schema/test/positive_test_set/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/schema/test/positive_test_set/export_across_membrane.yaml b/src/schema/test/positive_test_set/export_across_membrane.yaml
new file mode 100644
index 0000000..70162ee
--- /dev/null
+++ b/src/schema/test/positive_test_set/export_across_membrane.yaml
@@ -0,0 +1,56 @@
+pattern_name: import_across_membrane
+
+classes: 
+  membrane: GO_0016020
+  cellular_component: GO_0005575
+  chemical entity: CHEBI_24431
+  macromolecular complex: GO_0032991
+  transcript: SO_0000673
+  transport: GO_0006810
+
+relations:
+  transports or maintains localization of: RO_0002313
+  has target start location: RO_0002338
+  has target end location: RO_0002339
+  results in transport across: RO_0002342
+  exports: RO_0002345
+
+vars: 
+  membrane: "'membrane'"
+  cargo: "'chemical entity' or 'macromolecular complex' or 'transcript'"
+  start: "'cellular_component'"
+  end: "'cellular_component'"
+
+name: 
+  text: "%s export across %s"
+  vars: 
+    - cargo
+    - membrane
+
+def: 
+  text: "The directed import of %s from %s, across the %s and into the %s."
+  vars:
+    - cargo
+    - start
+    - membrane
+    - end
+
+comment: 
+  text: "This term covers %s *across* the %s  through a channel or pore.  It does not cover export via vesicle fusion with %s, as in this case transport does not involve crossing the membrane."
+  vars:
+    - cargo
+    - membrane
+    - membrane
+
+equivalentTo: 
+  text: "'transport' that
+    and ('has target start location' some %s)
+    and ('has target end location' some %s)
+    and ('exports' some %s)
+    and ('results in transport across' some %s)"
+    
+  vars:
+    - start
+    - end
+    - cargo
+    - membrane
diff --git a/src/schema/test/positive_test_set/patterns/adenine_import_across_plasma_membrane.md b/src/schema/test/positive_test_set/patterns/adenine_import_across_plasma_membrane.md
new file mode 100644
index 0000000..ddf0073
--- /dev/null
+++ b/src/schema/test/positive_test_set/patterns/adenine_import_across_plasma_membrane.md
@@ -0,0 +1,8 @@
+## Template: import_across_plasma_membrane
+### imported: [adenine](http://purl.obolibrary.org/obo/CHEBI_16708)
+
+__label:__ adenine import across plasma membrane
+
+__def:__ The directed movement of adenine from outside of a cell, across the plasma membrane and into the cytoplasmic compartment.
+
+__equivalentTo:__ ['transport'](http://purl.obolibrary.org/obo/GO_0006810) _and_ (__['has target start location'](http://purl.obolibrary.org/obo/RO_0002338)__ _some_ ['extracellular region'](http://purl.obolibrary.org/obo/GO_0005576)) _and_ (__['has target end location'](http://purl.obolibrary.org/obo/RO_0002339)__ _some_ ['cytosol'](http://purl.obolibrary.org/obo/GO_0005829)) _and_ (__['results in transport across'](http://purl.obolibrary.org/obo/RO_0002342)__ _some_ ['plasma membrane'](http://purl.obolibrary.org/obo/GO_0005886)) _and_ (__['imports'](http://purl.obolibrary.org/obo/RO_0002340)__ _some_ [adenine](http://purl.obolibrary.org/obo/CHEBI_16708))
diff --git a/src/schema/test/positive_test_set/patterns/anchored_membrane_component.json b/src/schema/test/positive_test_set/patterns/anchored_membrane_component.json
new file mode 100644
index 0000000..a5b9116
--- /dev/null
+++ b/src/schema/test/positive_test_set/patterns/anchored_membrane_component.json
@@ -0,0 +1,32 @@
+{
+    "classes": {
+        "anchored component of membrane": "GO_0031225", 
+        "membrane": "GO_0016020", 
+        "side of membrane": "GO_0098552"
+    }, 
+    "def": {
+        "text": "The component of the %s consisting of the gene products that are tethered to the membrane only by a covalently attached anchor, such as a lipid group that is embedded in the membrane. Gene products with peptide sequences that are embedded in the membrane are excluded from this grouping.", 
+        "vars": [
+            "membrane"
+        ]
+    }, 
+    "equivalentTo": {
+        "text": "'anchored component of membrane' and ('part of' some %s)", 
+        "vars": [
+            "membrane"
+        ]
+    }, 
+    "name": {
+        "text": "anchored component of %s", 
+        "vars": [
+            "membrane"
+        ]
+    }, 
+    "pattern_name": "anchored_membrane_component", 
+    "relations": {
+        "part of": "BFO_0000050"
+    }, 
+    "vars": {
+        "membrane": "'membrane' or 'side of membrane'"
+    }
+}
\ No newline at end of file
diff --git a/src/schema/test/positive_test_set/patterns/anchored_membrane_component.md b/src/schema/test/positive_test_set/patterns/anchored_membrane_component.md
new file mode 100644
index 0000000..a0e2ef8
--- /dev/null
+++ b/src/schema/test/positive_test_set/patterns/anchored_membrane_component.md
@@ -0,0 +1,5 @@
+## anchored_membrane_component
+__label:__ anchored component of \{ membrane \}
+
+__def:__ The component of the \{ membrane \} consisting of the gene products that are tethered to the membrane only by a covalently attached anchor, such as a lipid group that is embedded in the membrane. Gene products with peptide sequences that are embedded in the membrane are excluded from this grouping.
+
diff --git a/src/schema/test/positive_test_set/patterns/anchored_membrane_component.yaml b/src/schema/test/positive_test_set/patterns/anchored_membrane_component.yaml
new file mode 100644
index 0000000..670de45
--- /dev/null
+++ b/src/schema/test/positive_test_set/patterns/anchored_membrane_component.yaml
@@ -0,0 +1,28 @@
+pattern_name: anchored_membrane_component
+
+classes:
+  membrane: GO_0016020
+  side of membrane: GO_0098552
+  anchored component of membrane: GO_0031225
+  
+
+relations:
+  part of: BFO_0000050
+
+vars: 
+  membrane: "'membrane' or 'side of membrane'"
+  
+name:
+  text: anchored component of %s
+  vars: 
+    - membrane
+    
+def: 
+  text: "The component of the %s consisting of the gene products that are tethered to the membrane only by a covalently attached anchor, such as a lipid group that is embedded in the membrane. Gene products with peptide sequences that are embedded in the membrane are excluded from this grouping."
+  vars:
+    - membrane
+    
+equivalentTo: 
+  text: "'anchored component of membrane' and ('part of' some %s)"
+  vars:
+    - membrane
diff --git a/src/schema/test/positive_test_set/patterns/bounding_membrane_of_organelle.yaml b/src/schema/test/positive_test_set/patterns/bounding_membrane_of_organelle.yaml
new file mode 100644
index 0000000..bca2c3f
--- /dev/null
+++ b/src/schema/test/positive_test_set/patterns/bounding_membrane_of_organelle.yaml
@@ -0,0 +1,26 @@
+pattern_name: bounding_membrane_of_organelle
+
+classes:
+  'whole membrane': GO_0098805
+  'membrane-bounded organelle': GO_0043227
+
+relations:
+  'bounding layer of': RO_0002007
+
+vars: 
+  organelle: "'membrane-bounded organelle'"
+
+name:
+  text: "%s membrane"
+  vars: 
+    - organelle
+
+def: 
+  text: "The lipid bilayer that forms the outer layer of a %s."
+  vars:
+    - organelle
+
+equivalentTo: 
+  text: "'whole membrane' that ('bounding layer of' some %s)"
+  vars:
+    - organelle
diff --git a/src/schema/test/positive_test_set/patterns/data/acute.yaml b/src/schema/test/positive_test_set/patterns/data/acute.yaml
new file mode 100644
index 0000000..c740924
--- /dev/null
+++ b/src/schema/test/positive_test_set/patterns/data/acute.yaml
@@ -0,0 +1,47 @@
+pattern_name: acute
+
+pattern_iri: http://purl.obolibrary.org/obo/mondo/patterns/acute.yaml
+
+description: 'This pattern is applied to diseases that are described as having an acute onset, i.e. the sudden appearance of disease manifestations over a short period of time.
+
+Examples: [acute bronchiolitis](http://purl.obolibrary.org/obo/MONDO_0020680),
+  [acute liver failure](http://purl.obolibrary.org/obo/MONDO_0019542)'
+
+contributors:
+- https://orcid.org/0000-0002-6601-2165
+- https://orcid.org/0000-0001-5208-3432
+
+classes:
+  acute: PATO:0000389
+  disease: MONDO:0000001
+
+relations:
+  has modifier: RO:0002573
+
+annotationProperties:
+  exact_synonym: oio:hasExactSynonym
+  related_synonym: oio:hasRelatedSynonym
+
+vars:
+  disease: '''disease'''
+
+name:
+  text: acute %s
+  vars:
+  - disease
+
+annotations:
+- annotationProperty: exact_synonym
+  text: '%s, acute'
+  vars:
+  - disease
+
+def:
+  text: Acute form of %s.
+  vars:
+  - disease
+
+equivalentTo:
+  text: '%s and ''has modifier'' some ''acute'''
+  vars:
+  - disease
\ No newline at end of file
diff --git a/src/schema/test/positive_test_set/patterns/data/acute2.yaml b/src/schema/test/positive_test_set/patterns/data/acute2.yaml
new file mode 100644
index 0000000..c740924
--- /dev/null
+++ b/src/schema/test/positive_test_set/patterns/data/acute2.yaml
@@ -0,0 +1,47 @@
+pattern_name: acute
+
+pattern_iri: http://purl.obolibrary.org/obo/mondo/patterns/acute.yaml
+
+description: 'This pattern is applied to diseases that are described as having an acute onset, i.e. the sudden appearance of disease manifestations over a short period of time.
+
+Examples: [acute bronchiolitis](http://purl.obolibrary.org/obo/MONDO_0020680),
+  [acute liver failure](http://purl.obolibrary.org/obo/MONDO_0019542)'
+
+contributors:
+- https://orcid.org/0000-0002-6601-2165
+- https://orcid.org/0000-0001-5208-3432
+
+classes:
+  acute: PATO:0000389
+  disease: MONDO:0000001
+
+relations:
+  has modifier: RO:0002573
+
+annotationProperties:
+  exact_synonym: oio:hasExactSynonym
+  related_synonym: oio:hasRelatedSynonym
+
+vars:
+  disease: '''disease'''
+
+name:
+  text: acute %s
+  vars:
+  - disease
+
+annotations:
+- annotationProperty: exact_synonym
+  text: '%s, acute'
+  vars:
+  - disease
+
+def:
+  text: Acute form of %s.
+  vars:
+  - disease
+
+equivalentTo:
+  text: '%s and ''has modifier'' some ''acute'''
+  vars:
+  - disease
\ No newline at end of file
diff --git a/src/schema/test/positive_test_set/patterns/data/dummy.yaml b/src/schema/test/positive_test_set/patterns/data/dummy.yaml
new file mode 100644
index 0000000..6eafc69
--- /dev/null
+++ b/src/schema/test/positive_test_set/patterns/data/dummy.yaml
@@ -0,0 +1,15 @@
+# An unrelated yaml file
+- martin:
+    name: Martin D'vloper
+    job: Developer
+    skills:
+      - python
+      - perl
+      - pascal
+- tabitha:
+    name: Tabitha Bitumen
+    job: Developer
+    skills:
+      - lisp
+      - fortran
+      - erlang
\ No newline at end of file
diff --git a/src/schema/test/positive_test_set/patterns/data/generated/dummy.text b/src/schema/test/positive_test_set/patterns/data/generated/dummy.text
new file mode 100644
index 0000000..e69de29
diff --git a/src/schema/test/positive_test_set/patterns/data/sample_data/acute.tsv b/src/schema/test/positive_test_set/patterns/data/sample_data/acute.tsv
new file mode 100644
index 0000000..1a0f3de
--- /dev/null
+++ b/src/schema/test/positive_test_set/patterns/data/sample_data/acute.tsv
@@ -0,0 +1,54 @@
+defined_class	defined_class_label	disease	disease_label
+http://purl.obolibrary.org/obo/MONDO_0001090	acute anterolateral myocardial infarction	http://purl.obolibrary.org/obo/MONDO_0006652	anterolateral myocardial infarction
+http://purl.obolibrary.org/obo/MONDO_0020680	acute bronchiolitis	http://purl.obolibrary.org/obo/MONDO_0002465	bronchiolitis (disease)
+http://purl.obolibrary.org/obo/MONDO_0001081	acute cervicitis	http://purl.obolibrary.org/obo/MONDO_0002345	cervicitis (disease)
+http://purl.obolibrary.org/obo/MONDO_0001930	acute cholangitis	http://purl.obolibrary.org/obo/MONDO_0004789	cholangitis
+http://purl.obolibrary.org/obo/MONDO_0001817	acute closed-angle glaucoma	http://purl.obolibrary.org/obo/MONDO_0001744	angle-closure glaucoma
+http://purl.obolibrary.org/obo/MONDO_0001214	acute conjunctivitis	http://purl.obolibrary.org/obo/MONDO_0003799	conjunctivitis (disease)
+http://purl.obolibrary.org/obo/MONDO_0004598	acute cor pulmonale	http://purl.obolibrary.org/obo/MONDO_0004596	cor pulmonale
+http://purl.obolibrary.org/obo/MONDO_0001650	acute cystitis (disease)	http://purl.obolibrary.org/obo/MONDO_0006032	cystitis
+http://purl.obolibrary.org/obo/MONDO_0004812	acute dacryoadenitis	http://purl.obolibrary.org/obo/MONDO_0004804	dacryoadenitis
+http://purl.obolibrary.org/obo/MONDO_0001610	acute dacryocystitis	http://purl.obolibrary.org/obo/MONDO_0004926	dacryocystitis
+http://purl.obolibrary.org/obo/MONDO_0000257	acute diarrhea	http://purl.obolibrary.org/obo/MONDO_0001673	diarrheal disease
+http://purl.obolibrary.org/obo/MONDO_0001871	acute diffuse glomerulonephritis	http://purl.obolibrary.org/obo/MONDO_0003137	diffuse glomerulonephritis
+http://purl.obolibrary.org/obo/MONDO_0020683	acute disease	http://purl.obolibrary.org/obo/MONDO_0000001	disease or disorder
+http://purl.obolibrary.org/obo/MONDO_0004265	acute endometritis	http://purl.obolibrary.org/obo/MONDO_0000918	endometritis
+http://purl.obolibrary.org/obo/MONDO_0017202	acute endophthalmitis	http://purl.obolibrary.org/obo/MONDO_0016047	endophthalmitis
+http://purl.obolibrary.org/obo/MONDO_0041366	acute epiglottitis	http://purl.obolibrary.org/obo/MONDO_0005753	epiglottitis
+http://purl.obolibrary.org/obo/MONDO_0004810	acute ethmoiditis	http://purl.obolibrary.org/obo/MONDO_0005756	ethmoid sinusitis
+http://purl.obolibrary.org/obo/MONDO_0001912	acute frontal sinusitis	http://purl.obolibrary.org/obo/MONDO_0001121	frontal sinusitis
+http://purl.obolibrary.org/obo/MONDO_0001080	acute gonococcal cervicitis	http://purl.obolibrary.org/obo/MONDO_0021157	gonococcal cervicitis
+http://purl.obolibrary.org/obo/MONDO_0001777	acute gonococcal cystitis	http://purl.obolibrary.org/obo/MONDO_0021160	gonococcal cystitis
+http://purl.obolibrary.org/obo/MONDO_0001125	acute gonococcal epididymo-orchitis	http://purl.obolibrary.org/obo/MONDO_0021158	gonococcal epididymo-orchitis
+http://purl.obolibrary.org/obo/MONDO_0001838	acute gonococcal prostatitis	http://purl.obolibrary.org/obo/MONDO_0021161	gonococcal prostatitis
+http://purl.obolibrary.org/obo/MONDO_0001837	acute gonococcal salpingitis	http://purl.obolibrary.org/obo/MONDO_0021159	gonococcal salpingitis
+http://purl.obolibrary.org/obo/MONDO_0020546	acute graft versus host disease	http://purl.obolibrary.org/obo/MONDO_0013730	graft versus host disease
+http://purl.obolibrary.org/obo/MONDO_0005174	acute hypotension	http://purl.obolibrary.org/obo/MONDO_0005468	hypotensive disorder
+http://purl.obolibrary.org/obo/MONDO_0044213	acute idiopathic urticaria	http://purl.obolibrary.org/obo/MONDO_0044211	idiopathic urticaria
+http://purl.obolibrary.org/obo/MONDO_0002492	acute kidney failure	http://purl.obolibrary.org/obo/MONDO_0001106	kidney failure
+http://purl.obolibrary.org/obo/MONDO_0004777	acute laryngitis	http://purl.obolibrary.org/obo/MONDO_0002647	laryngitis
+http://purl.obolibrary.org/obo/MONDO_0019542	acute liver failure	http://purl.obolibrary.org/obo/MONDO_0100192	liver failure
+http://purl.obolibrary.org/obo/MONDO_0002186	acute maxillary sinusitis	http://purl.obolibrary.org/obo/MONDO_0005842	maxillary sinusitis
+http://purl.obolibrary.org/obo/MONDO_0007896	acute monocytic leukemia	http://purl.obolibrary.org/obo/MONDO_0004600	monocytic leukemia
+http://purl.obolibrary.org/obo/MONDO_0018874	acute myeloid leukemia	http://purl.obolibrary.org/obo/MONDO_0004643	myeloid leukemia
+http://purl.obolibrary.org/obo/MONDO_0004781	acute myocardial infarction	http://purl.obolibrary.org/obo/MONDO_0005068	myocardial infarction (disease)
+http://purl.obolibrary.org/obo/MONDO_0002815	acute myocarditis	http://purl.obolibrary.org/obo/MONDO_0004496	myocarditis
+http://purl.obolibrary.org/obo/MONDO_0001051	acute otitis externa	http://purl.obolibrary.org/obo/MONDO_0004795	otitis externa
+http://purl.obolibrary.org/obo/MONDO_0006515	acute pancreatitis	http://purl.obolibrary.org/obo/MONDO_0004982	pancreatitis
+http://purl.obolibrary.org/obo/MONDO_0041295	acute papillary necrosis	http://purl.obolibrary.org/obo/MONDO_0006821	kidney papillary necrosis
+http://purl.obolibrary.org/obo/MONDO_0001028	acute pericementitis	http://purl.obolibrary.org/obo/MONDO_0005076	periodontitis
+http://purl.obolibrary.org/obo/MONDO_0002240	acute perichondritis of pinna	http://purl.obolibrary.org/obo/MONDO_0002246	perichondritis of auricle
+http://purl.obolibrary.org/obo/MONDO_0020600	acute pharyngitis	http://purl.obolibrary.org/obo/MONDO_0002258	pharyngitis
+http://purl.obolibrary.org/obo/MONDO_0001644	acute proliferative glomerulonephritis	http://purl.obolibrary.org/obo/MONDO_0003134	proliferative glomerulonephritis
+http://purl.obolibrary.org/obo/MONDO_0003529	acute pyelonephritis	http://purl.obolibrary.org/obo/MONDO_0006939	pyelonephritis
+http://purl.obolibrary.org/obo/MONDO_0001208	acute respiratory failure	http://purl.obolibrary.org/obo/MONDO_0021113	respiratory failure
+http://purl.obolibrary.org/obo/MONDO_0001895	acute retrobulbar neuritis	http://purl.obolibrary.org/obo/MONDO_0024335	retrobulbar neuritis
+http://purl.obolibrary.org/obo/MONDO_0001173	acute salpingitis	http://purl.obolibrary.org/obo/MONDO_0003619	salpingitis
+http://purl.obolibrary.org/obo/MONDO_0001171	acute salpingo-oophoritis	http://purl.obolibrary.org/obo/MONDO_0001172	salpingo-oophoritis
+http://purl.obolibrary.org/obo/MONDO_0001624	acute sphenoidal sinusitis	http://purl.obolibrary.org/obo/MONDO_0005964	sphenoid sinusitis
+http://purl.obolibrary.org/obo/MONDO_0000990	acute subendocardial myocardial infarction	http://purl.obolibrary.org/obo/MONDO_0003674	subendocardial myocardial infarction
+http://purl.obolibrary.org/obo/MONDO_0001949	acute thyroiditis	http://purl.obolibrary.org/obo/MONDO_0004126	thyroiditis (disease)
+http://purl.obolibrary.org/obo/MONDO_0020686	acute tonsillitis	http://purl.obolibrary.org/obo/MONDO_0001039	tonsillitis
+http://purl.obolibrary.org/obo/MONDO_0002738	acute transudative otitis media	http://purl.obolibrary.org/obo/MONDO_0001212	non-suppurative otitis media
+http://purl.obolibrary.org/obo/MONDO_0001031	purulent acute otitis media	http://purl.obolibrary.org/obo/MONDO_0005975	suppurative otitis media
+http://purl.obolibrary.org/obo/MONDO_0000222	seminal vesicle acute gonorrhea	http://purl.obolibrary.org/obo/MONDO_0001027	gonococcal seminal vesiculitis
diff --git a/src/schema/test/positive_test_set/patterns/envenomation.yaml b/src/schema/test/positive_test_set/patterns/envenomation.yaml
new file mode 100644
index 0000000..c4def23
--- /dev/null
+++ b/src/schema/test/positive_test_set/patterns/envenomation.yaml
@@ -0,0 +1,26 @@
+pattern_name: envenomation
+
+classes:
+   'envenomation resulting in modification of morphology or physiology of other organism' : 'GO_0044468'
+   'modification of morphology or physiology of other organism' : 'GO_0035821'
+
+relations:
+   'has part' : 'BFO_0000051'
+  
+vars: 
+   mod: "'modification of morphology or physiology of other organism'"
+  
+name: 
+  text: 'envenomation resulting in %s'
+  vars: 
+     - mod
+    
+def: 
+  text: "A process that begins with venom being forced into an organism by the bite or sting of another organism, and ends with the resultant %s"
+  vars:
+     - mod  # Need a way to specify regex transformation that subs 'in the bitten organism' for 'in other organism'
+    
+equivalentTo: 
+  text: "'envenomation resulting in modification of morphology or physiology of other organism' and 'has part' some %s"
+  vars:
+   - mod
\ No newline at end of file
diff --git a/src/schema/test/positive_test_set/patterns/export_across_membrane.yaml b/src/schema/test/positive_test_set/patterns/export_across_membrane.yaml
new file mode 100644
index 0000000..70162ee
--- /dev/null
+++ b/src/schema/test/positive_test_set/patterns/export_across_membrane.yaml
@@ -0,0 +1,56 @@
+pattern_name: import_across_membrane
+
+classes: 
+  membrane: GO_0016020
+  cellular_component: GO_0005575
+  chemical entity: CHEBI_24431
+  macromolecular complex: GO_0032991
+  transcript: SO_0000673
+  transport: GO_0006810
+
+relations:
+  transports or maintains localization of: RO_0002313
+  has target start location: RO_0002338
+  has target end location: RO_0002339
+  results in transport across: RO_0002342
+  exports: RO_0002345
+
+vars: 
+  membrane: "'membrane'"
+  cargo: "'chemical entity' or 'macromolecular complex' or 'transcript'"
+  start: "'cellular_component'"
+  end: "'cellular_component'"
+
+name: 
+  text: "%s export across %s"
+  vars: 
+    - cargo
+    - membrane
+
+def: 
+  text: "The directed import of %s from %s, across the %s and into the %s."
+  vars:
+    - cargo
+    - start
+    - membrane
+    - end
+
+comment: 
+  text: "This term covers %s *across* the %s  through a channel or pore.  It does not cover export via vesicle fusion with %s, as in this case transport does not involve crossing the membrane."
+  vars:
+    - cargo
+    - membrane
+    - membrane
+
+equivalentTo: 
+  text: "'transport' that
+    and ('has target start location' some %s)
+    and ('has target end location' some %s)
+    and ('exports' some %s)
+    and ('results in transport across' some %s)"
+    
+  vars:
+    - start
+    - end
+    - cargo
+    - membrane
diff --git a/src/schema/test/positive_test_set/patterns/expression_pattern.yaml b/src/schema/test/positive_test_set/patterns/expression_pattern.yaml
new file mode 100644
index 0000000..5e7ebd6
--- /dev/null
+++ b/src/schema/test/positive_test_set/patterns/expression_pattern.yaml
@@ -0,0 +1,28 @@
+pattern_name: expression_pattern
+
+classes:
+  expression pattern: 
+  cell: 
+
+relations:
+  'part of': BFO_0000050
+  'has part': 
+  
+vars:   
+  expressed_feature: "'gene'" # Needs to be gene or transgene. Use SO?
+  
+name: 
+  text: "%s expression pattern"
+  vars: 
+    - expressed_feature
+
+def: 
+  text: "The sum total of all cells in a single organism in which expression of %s is occurring."
+  vars:
+    - expressed_feature
+
+    
+equivalentTo: 
+  text: ""
+  vars:
+    - expressed_feature
diff --git a/src/schema/test/positive_test_set/patterns/extrinsic_membrane_component.yaml b/src/schema/test/positive_test_set/patterns/extrinsic_membrane_component.yaml
new file mode 100644
index 0000000..9ac23db
--- /dev/null
+++ b/src/schema/test/positive_test_set/patterns/extrinsic_membrane_component.yaml
@@ -0,0 +1,27 @@
+pattern_name: extrinsic_membrane_component
+
+classes:
+  membrane: GO_0016020
+  side of membrane: GO_0098552
+  extrinsic component of membrane: GO_0019898
+
+relations:
+  part of: BFO_0000050
+
+vars: 
+  membrane: "'membrane' or 'side of membrane'"
+  
+name:
+  text: extrinsic component of %s
+  vars: 
+    - membrane
+    
+def: 
+  text: "The component of the %s consisting of gene products and protein complexes that are loosely bound to one of its surfaces, but not integrated into the hydrophobic region."
+  vars:
+    - membrane
+    
+equivalentTo: 
+  text: "'extrinsic component of membrane' and ('part of' some %s)"
+  vars:
+    - membrane
diff --git a/src/schema/test/positive_test_set/patterns/import_across_membrane.yaml b/src/schema/test/positive_test_set/patterns/import_across_membrane.yaml
new file mode 100644
index 0000000..a5b5d29
--- /dev/null
+++ b/src/schema/test/positive_test_set/patterns/import_across_membrane.yaml
@@ -0,0 +1,57 @@
+pattern_name: import_across_membrane
+
+classes: 
+  membrane: GO_0016020
+  cellular_component: GO_0005575
+  chemical entity: CHEBI_24431
+  macromolecular complex: GO_0032991
+  transcript: SO_0000673
+  transport: GO_0006810
+
+relations:
+  transports or maintains localization of: RO_0002313
+  has target start location: RO_0002338
+  has target end location: RO_0002339
+  results in transport across: RO_0002342
+  imports: RO_0002340
+
+vars: 
+  membrane: "'membrane'"
+  cargo: "'chemical entity' or 'macromolecular complex' or 'transcript'"
+  start: "'cellular_component'"
+  end: "'cellular_component'"
+
+name: 
+  text: "%s import across %s"
+  vars: 
+    - cargo
+    - membrane
+
+def: 
+  text: "The directed import of %s from %s, across the %s and into the %s."
+  vars:
+    - cargo
+    - start
+    - membrane
+    - end
+
+comment: 
+  text: "This term covers %s *across* the %s through a channel or pore. It does not cover import via vesicle fusion with %s or vesiculation, as in these cases transport does not involve crossing the membrane."
+  vars:
+    - cargo
+    - membrane
+    - membrane
+
+
+equivalentTo: 
+  text: "'transport'
+    and ('has target start location' some %s)
+    and ('has target end location' some %s)
+    and ('imports' some %s)
+    and ('results in transport across' some %s)"
+    
+  vars:
+    - start
+    - end
+    - cargo
+    - membrane
diff --git a/src/schema/test/positive_test_set/patterns/import_across_plasma_membrane.yaml b/src/schema/test/positive_test_set/patterns/import_across_plasma_membrane.yaml
new file mode 100644
index 0000000..249729e
--- /dev/null
+++ b/src/schema/test/positive_test_set/patterns/import_across_plasma_membrane.yaml
@@ -0,0 +1,34 @@
+pattern_name: import_across_plasma_membrane
+# implicitly import into cell across plamsa membrane
+
+classes: 
+  transport: GO_0006810 
+  extracellular region: GO_0005576 
+  cytosol: GO_0005829
+  chemical entity: CHEBI_24431  # encompasses protein
+  macromolecular complex: GO_0043234  # encompasses protein complex
+  plasma membrane: GO_0005886
+
+relations:
+  has target start location: RO_0002338
+  has target end location: RO_0002339
+  results in transport across: RO_0002342
+  imports: RO_0002340
+
+vars: 
+  imported: "'chemical entity' or 'macromolecular complex'"
+
+name:
+  text: "%s import across plasma membrane"
+  vars: 
+    - imported
+    
+def: 
+  text: The directed movement of %s from outside of a cell, across the plasma membrane and into the cytoplasmic compartment. 
+  vars:
+    - imported
+    
+equivalentTo: 
+  text: "'transport' and ('has target start location' some 'extracellular region') and ('has target end location' some 'cytosol') and ('results in transport across' some 'plasma membrane') and ('imports' some %s)"
+  vars:
+    - imported
diff --git a/src/schema/test/positive_test_set/patterns/import_into_cell.yaml b/src/schema/test/positive_test_set/patterns/import_into_cell.yaml
new file mode 100644
index 0000000..7218ddb
--- /dev/null
+++ b/src/schema/test/positive_test_set/patterns/import_into_cell.yaml
@@ -0,0 +1,32 @@
+pattern_name: import_into_cell
+classes: 
+  transport: GO_0006810 
+  extracellular region: GO_0005576 
+  intracellular part: GO_0044424 
+  chemical entity: CHEBI_24431  # encompasses protein
+  macromolecular complex: GO_0043234  # encompasses protein complex
+
+relations:
+  has target start location: RO_0002338
+  has target end location: RO_0002339
+  imports: RO_0002340
+    
+vars: 
+  imported: "'chemical entity' or 'macromolecular complex'"
+  
+name:
+  text: "%s import into cell"
+  vars: 
+    - imported
+    
+def: 
+  text: "The directed movement of %s from outside of a cell into the cytoplasmic compartment. This may occur via transport across the plasma membrane or via endocytosis."
+  vars:
+    - imported
+    
+equivalentTo: 
+  text: "'transport' and ('has target start location' some 'extracellular region') and ('has target end location' some 'intracellular part') and ('imports' some %s)"
+  vars:
+    - imported
+    
+
diff --git a/src/schema/test/positive_test_set/patterns/integral_membrane_component.yaml b/src/schema/test/positive_test_set/patterns/integral_membrane_component.yaml
new file mode 100644
index 0000000..d916fc5
--- /dev/null
+++ b/src/schema/test/positive_test_set/patterns/integral_membrane_component.yaml
@@ -0,0 +1,27 @@
+pattern_name: integral_membrane_component
+
+classes:
+  membrane: GO_0016020
+  side of membrane: GO_0098552
+  integral component of membrane: GO_0016021
+
+relations:
+  part of: BFO_0000050
+
+vars: 
+  membrane: "'membrane' or 'side of membrane'"
+  
+name:
+  text: integral component of %s
+  vars: 
+    - membrane
+    
+def: 
+  text: "The component of the %s consisting of the gene products and protein complexes having at least some part of their peptide sequence embedded in the hydrophobic region of the membrane."
+  vars:
+    - membrane
+    
+equivalentTo: 
+  text: "'integral component of membrane' and ('part of' some %s)"
+  vars:
+    - membrane
diff --git a/src/schema/test/positive_test_set/patterns/intrinsic_membrane_component.yaml b/src/schema/test/positive_test_set/patterns/intrinsic_membrane_component.yaml
new file mode 100644
index 0000000..eebec7b
--- /dev/null
+++ b/src/schema/test/positive_test_set/patterns/intrinsic_membrane_component.yaml
@@ -0,0 +1,27 @@
+pattern_name: intrinsic_membrane_component
+
+classes:
+  membrane: GO_0016020
+  side of membrane: GO_0098552
+  intrinsic component of membrane: GO_0031224
+
+relations:
+  part of: BFO_0000050
+
+vars: 
+  membrane: "'membrane' or 'side of membrane'"
+  
+name:
+  text: intrinsic component of %s
+  vars: 
+    - membrane
+    
+def: 
+  text: The component of the %s consisting of gene products and protein complexes that have some covalently attached part (e.g. peptide sequence or GPI anchor), which spans or is embedded in one or both leaflets the membrane.
+  vars:
+    - membrane
+    
+equivalentTo: 
+  text: "'intrinsic component of membrane' and ('part of' some %s)"
+  vars:
+    - membrane
diff --git a/src/schema/test/positive_test_set/patterns/membrane_spanning_component.yaml b/src/schema/test/positive_test_set/patterns/membrane_spanning_component.yaml
new file mode 100644
index 0000000..8577a6d
--- /dev/null
+++ b/src/schema/test/positive_test_set/patterns/membrane_spanning_component.yaml
@@ -0,0 +1,26 @@
+pattern_name: spanning_component_of_membrane
+
+classes:
+  membrane: GO_0016020
+  spanning component of membrane: GO_0089717
+
+relations:
+  'part of': BFO_0000050
+  
+vars:   
+  membrane: "'membrane'"
+
+name: 
+  text: "spanning component of %s"
+  vars: 
+    - membrane
+
+def: 
+  text: "The component of the %s consisting of gene products and protein complexes that have some part that spans both leaflets of the membrane."
+  vars:
+    - membrane
+    
+equivalentTo: 
+  text: "'spanning component of membrane' that 'part of' some %s"
+  vars:
+    - membrane
diff --git a/src/schema/test/positive_test_set/patterns/transmembrane_import_into_cell.yaml b/src/schema/test/positive_test_set/patterns/transmembrane_import_into_cell.yaml
new file mode 100644
index 0000000..5dc12cd
--- /dev/null
+++ b/src/schema/test/positive_test_set/patterns/transmembrane_import_into_cell.yaml
@@ -0,0 +1,34 @@
+pattern_name: transmembrane_import_into_cytosol
+# implicitly import into cell across plamsa membrane
+
+classes: 
+  transport: GO_0006810 
+  extracellular region: GO_0005576 
+  cytosol: GO_0005829
+  chemical entity: CHEBI_24431  # encompasses protein
+  macromolecular complex: GO_0043234  # encompasses protein complex
+  plasma membrane: GO_0005886
+
+relations:
+  has target start location: RO_0002338
+  has target end location: RO_0002339
+  results in transport across: RO_0002342
+  imports: RO_0002340
+
+vars: 
+  imported: "'chemical entity' or 'macromolecular complex'"
+
+name:
+  text: "%s import across plasma membrane"
+  vars: 
+    - imported
+    
+def: 
+  text: The directed movement of %s from outside of a cell, across the plasma membrane and into the cytoplasmic compartment. 
+  vars:
+    - imported
+    
+equivalentTo: 
+  text: "'transport' and ('has target start location' some 'extracellular region') and ('has target end location' some 'cytosol') and ('results in transport across' some 'plasma membrane') and ('imports' some %s)"
+  vars:
+    - imported