Repository: INCATools/dead_simple_owl_design_patterns (language: python)

Issue:
Support 0-many var input
Related ticket: https://github.com/INCATools/dead_simple_owl_design_patterns/issues/16

We need to be able to support templating in cases where a variable is a list (0-many) => 0-many clauses added to text or manchester syntax output using some transparent templating system.

One option might be to use [Moustache](https://mustache.github.io/mustache.5.html) for templating.  Another, perhaps not well thought our suggestion:

```yaml
text: "A %s that {%s,’ and ‘} and something else %s”
vars:
neuron_type

list_vars:
	marker_list

def:
	clauses:
text: "A %s %s %s that "
  vars:
taxon
brain_region
neuron_type
  cardinality: 1
text: "expresses %s" 
  vars:
marker_list
  cardinality: ">=0"
  sep: " and "
# => "A GABAergic that expresses gene 1 and expresses gene 2." 
equivalent_to:
  and_clauses:
text: "%s"
vars: 
neuron_type
cardinality: 1
text: "'expresses' some %s"
vars:
marker_list
cardinality: ">=1"
  annotation: 

# equivalent_to: 'GABAergic neuron' and (expresses some gene1) and (expresses some gene2)
``` 

see discussion in https://docs.google.com/document/d/1n6gJdypX7l-JQypPX-4Vl4Yf_ItcV47XdZM1Uqk9OlM/edit#heading=h.czjzooey00yv

Based on current discussion we favour Moustache - but needs further investigation.
Support 0-many var input
Related ticket: https://github.com/INCATools/dead_simple_owl_design_patterns/issues/16

We need to be able to support templating in cases where a variable is a list (0-many) => 0-many clauses added to text or manchester syntax output using some transparent templating system.

One option might be to use [Moustache](https://mustache.github.io/mustache.5.html) for templating.  Another, perhaps not well thought our suggestion:

```yaml
text: "A %s that {%s,’ and ‘} and something else %s”
vars:
neuron_type

list_vars:
	marker_list

def:
	clauses:
text: "A %s %s %s that "
  vars:
taxon
brain_region
neuron_type
  cardinality: 1
text: "expresses %s" 
  vars:
marker_list
  cardinality: ">=0"
  sep: " and "
# => "A GABAergic that expresses gene 1 and expresses gene 2." 
equivalent_to:
  and_clauses:
text: "%s"
vars: 
neuron_type
cardinality: 1
text: "'expresses' some %s"
vars:
marker_list
cardinality: ">=1"
  annotation: 

# equivalent_to: 'GABAergic neuron' and (expresses some gene1) and (expresses some gene2)
``` 

see discussion in https://docs.google.com/document/d/1n6gJdypX7l-JQypPX-4Vl4Yf_ItcV47XdZM1Uqk9OlM/edit#heading=h.czjzooey00yv

Based on current discussion we favour Moustache - but needs further investigation.


Discussion / hints:

Fix patch:
diff --git a/.gitignore b/.gitignore
index 58a1303..7dc1ba8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,4 @@ venv
 .idea
 **/.DS_Store
 
+.DS_Store
diff --git a/spec/DOSDP_schema_full.yaml b/spec/DOSDP_schema_full.yaml
index a4ddb11..5d4c906 100644
--- a/spec/DOSDP_schema_full.yaml
+++ b/spec/DOSDP_schema_full.yaml
@@ -7,20 +7,71 @@ 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.
+        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.
+        type: array
+        items: 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
-    required: [annotationProperty, text]
+    oneOf:
+      - required: [ annotationProperty, text ]
+      - required: [ multi_clause ]
     properties:
       annotationProperty:
-        description: > 
+        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" } 
+        items: { $ref: "#/definitions/annotations" }
         type: array
       text:
         description: A print format string.
@@ -35,14 +86,17 @@ definitions:
          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: > 
+        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
@@ -50,7 +104,7 @@ definitions:
         type: string
       value:
         description: >
-         A single list variable (list_var or data_list_var).  Each item in this list 
+         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
 
@@ -66,11 +120,11 @@ definitions:
         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" } 
+        items: { $ref: "#/definitions/annotations" }
         type: array
 
   annotations:
-   oneOf: 
+   oneOf:
      - { $ref: "#/definitions/printf_annotation" }
      - { $ref: "#/definitions/list_annotation" }
      - { $ref: "#/definitions/iri_value_annotation" }
@@ -78,44 +132,50 @@ definitions:
   printf_owl:
     type: object
     additionalProperties: False
-    required: [axiom_type, text, vars]
+    oneOf:
+      - required: [ axiom_type, text, vars ]
+      - required: [ multi_clause ]
     properties:
       annotations:
         items: {$ref: '#/definitions/annotations'}
         type: array
       axiom_type:
-        description: > 
+        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: 
+      text:
         type: string
-        description: > 
+        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)    
+         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.  
+         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
-    required: [text, vars]
+    oneOf:
+      - required: [ text, vars ]
+      - required: [ multi_clause ]
     properties:
       annotations:
         items: {$ref: '#/definitions/annotations'}
         type: array
-      text: 
+      text:
         type: string
         description: >
          A print format string in OWL Manchester syntax. Each entry
@@ -129,25 +189,27 @@ definitions:
           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 
+      in:
+        type: string
         description: name of input var
-      out: 
-        type: string 
-        description: > 
-          Name of output var.  If input var specified an OWL entity then 
+      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 
+        type: string
+        description: perl style regex match
+      sub:
+        type: string
         description: perl style regex sub.  May include backreferences.
 
   opa:
@@ -168,27 +230,29 @@ definitions:
         minItems: 3
         maxItems: 3
       annotations:
-        type: array 
+        type: array
         items: { $ref: '#/definitions/annotations' }
       not:
         description: "Optional field for negated OPAs"
         type: boolean
 
   # TODO - split into separate schema file
-  printf_annotation_obo: 
+  printf_annotation_obo:
     type: object
     additionalProperties: False
-    required: [text, vars]
+    oneOf:
+      - required: [text, vars]
+      - required: [multi_clause]
     properties:
       annotations:
-        items: { $ref: "#/definitions/annotations" } 
+        items: { $ref: "#/definitions/annotations" }
         type: array
       xrefs:
-        description: > 
-         Takes the name of a single data_list_var specifying a list of database 
+        description: >
+         Takes the name of a single data_list_var specifying a list of database
          cross references.
         type: string
-        mapping: "oboInOwl:hasDbXref" 
+        mapping: "oboInOwl:hasDbXref"
       text:
         description: A print format string.
         type: string
@@ -201,7 +265,9 @@ definitions:
          used in the substitution.
         items: {type: string}
         type: array
-        
+      multi_clause:
+        items: { $ref: '#/definitions/multi_clause_printf' }
+
   list_annotation_obo:
     type: object
     additionalProperties: False
@@ -209,13 +275,13 @@ definitions:
     properties:
       value:
         description: >
-         A single list variable (list_var or data_list_var).  Each item in this list 
+         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 
+        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"
@@ -231,47 +297,47 @@ properties:
       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: 
+
+  pattern_iri:
     type: string
-    description: > 
-       A global identifier for the pattern. This can be a full IRI or a CURIE, using 
+    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.
-      
+
   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."
-    
+
   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. 
+       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:
     type: string # specify UTF-8 string?
     description: "A free text description of the pattern.  Must be UTF-8 encoded."
-    
+
   examples:
     type: list
     items: string
     description:  "A list of example terms implementing this pattern."
-    
+
   status:
     type: string
     description: "Implementation status of pattern."
-    enum: 
+    enum:
        - development
        - published
-    
+
   tags:
     type: list
     items: string
     description: >
-       A list of strings used to tag a pattern for the purposes of arbitrary, 
+       A list of strings used to tag a pattern for the purposes of arbitrary,
        cross-cutting grouping of patterns.
 
   readable_identifiers:
@@ -302,39 +368,53 @@ properties:
 
   vars:
     type: object
-    propertyNames: 
+    propertyNames:
        pattern: "^[A-Za-z_][A-Za-z0-9_]*$"
     description: >
-     A dictionary of variables ranging over OWL classes.  
+     A dictionary of variables ranging over OWL classes.
      Key = variable name, value = variable range as manchester syntax string.
-     
+
   list_vars:
     type: object
-    propertyNames: 
+    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.
-  
+
   data_vars:
     type: object
-    propertyNames: 
+    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.
-     
+
   data_list_vars:
     type: object
-    propertyNames: 
+    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, 
+        A dictionary of variables referring 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:
+    type: array
+    properties:
+      var_name:
+        pattern: "^[A-Za-z_][A-Za-z0-9_]*$"
+      apply: { $ref: "#/definitions/function" }
+      input:
+        description: >
+          A list_vars or data_list_vars variable to which the given function applied.
+        type: string
+      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.
+
 # Var munging
 
   substitutions:
@@ -344,14 +424,14 @@ properties:
 # Specifying axioms:
 
   annotations:
-    items: { $ref: "#/definitions/annotations" } 
+    items: { $ref: "#/definitions/annotations" }
     type: array
-    
+
   logical_axioms:
     items: { $ref: '#/definitions/printf_owl'}
     type: array
 
-# logical convenience fields.  
+# logical convenience fields.
 # Where only one of any OWL axiom type is present, these convenience fields may be used.
 
   equivalentTo:
@@ -362,8 +442,8 @@ properties:
 
   GCI:
     { $ref: '#/definitions/printf_owl_convenience' }
- 
-  disjointWith: 
+
+  disjointWith:
     { $ref: '#/definitions/printf_owl_convenience' }
 
 # OBO fields - TODO split to a separate file with import statement.
@@ -377,7 +457,7 @@ properties:
   namespace:
     { $ref: '#/definitions/printf_annotation_obo', mapping: "oboInOwl:hasOBONamespace" }
 
-  # TODO: make all these into list_annotations 
+  # TODO: make all these into list_annotations
   exact_synonym:
     $ref: '#/definitions/list_annotation_obo'
     mapping: "oboInOwl:hasExactSynonym"
@@ -398,40 +478,40 @@ properties:
     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. 
+      Each entry may be annotated.
     type: array
-    items:  
-      $ref: '#/definitions/printf_annotation_obo' 
+    items:
+      $ref: '#/definitions/printf_annotation_obo'
       mapping: "oboInOwl:hasExactSynonym"
-      
+
   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. 
+      Each entry may be annotated.
     type: array
-    items:  
-      $ref: '#/definitions/printf_annotation_obo' 
+    items:
+      $ref: '#/definitions/printf_annotation_obo'
       mapping: "oboInOwl:hasNarrowSynonym"
 
   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. 
+      Each entry may be annotated.
     type: array
-    items:  
-      $ref: '#/definitions/printf_annotation_obo' 
+    items:
+      $ref: '#/definitions/printf_annotation_obo'
       mapping: "oboInOwl:hasBroadSynonym"
-      
+
   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. 
+      Each entry may be annotated.
     type: array
-    items:  
-      $ref: '#/definitions/printf_annotation_obo' 
+    items:
+      $ref: '#/definitions/printf_annotation_obo'
       mapping: "oboInOwl:hasBroadSynonym"
 
 # instance graph spec - TODO be split to a separate file
@@ -442,18 +522,18 @@ properties:
     required: [nodes, edges]
     properties:
       nodes:
-        description: > 
+        description: >
                        Key = name of individual within this pattern doc
-                       Value = Type of individual specified using either 
+                       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 
+                       or a var name.  This field does not support typing via
                        anonymous class expressions
         type: object
       edges:
         type: array
-        items: { $ref : '#/definitions/opa' } 
+        items: { $ref : '#/definitions/opa' }
+
 
- 
 
 
 


Test patch:
diff --git a/.github/workflows/tester.yaml b/.github/workflows/tester.yaml
new file mode 100644
index 0000000..3120818
--- /dev/null
+++ b/.github/workflows/tester.yaml
@@ -0,0 +1,29 @@
+name: Run Tests
+
+# Controls when the action will run.
+on:
+  # Triggers the workflow on push or pull request events but only for the master branch
+  push:
+    branches: [ master ]
+  pull_request:
+    branches: [ master ]
+
+  # Allows you to run this workflow manually from the Actions tab
+  workflow_dispatch:
+
+# A workflow run is made up of one or more jobs that can run sequentially or in parallel
+jobs:
+  build:
+    # The type of runner that the job will run on
+    runs-on: ubuntu-latest
+
+    # Steps represent a sequence of tasks that will be executed as part of the job
+    steps:
+      # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
+      - uses: actions/checkout@v2
+      - name: install dependencies
+        run: |
+            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'
diff --git a/src/schema/test/generic_test/schema_test.py b/src/schema/test/generic_test/schema_test.py
new file mode 100644
index 0000000..1c66af4
--- /dev/null
+++ b/src/schema/test/generic_test/schema_test.py
@@ -0,0 +1,48 @@
+import unittest
+import os
+import pathlib
+from ruamel.yaml import YAML, YAMLError
+from jsonschema import Draft4Validator
+
+SCHEMA = os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                      "../../../../spec/DOSDP_schema_full.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__)),
+                                  "../positive_test_set/patterns/multi_clause_schema.yaml")
+NEGATIVE_PATTERN_1 = os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                  "../negative_test_set/acute_negative.yaml")
+
+
+def show_errors(pattern, validator):
+    es = validator.iter_errors(pattern)
+    for e in es:
+        print(" => ".join([str(e.schema_path), str(e.message), str(e.context)]))
+
+
+class SchemaValidationTests(unittest.TestCase):
+
+    def test_schema_validity1(self):
+        self.assertTrue(pathlib.Path(SCHEMA).exists())
+
+        validator = Draft4Validator(self.read_yaml(SCHEMA))
+
+        ## self.show_errors(pattern, validator)
+        self.assertTrue(validator.is_valid(self.read_yaml(POSITIVE_PATTERN_1)))
+        self.assertTrue(validator.is_valid(self.read_yaml(POSITIVE_PATTERN_2)))
+
+        negative_pattern_1 = self.read_yaml(NEGATIVE_PATTERN_1)
+        self.assertFalse(validator.is_valid(negative_pattern_1))
+        es = validator.iter_errors(negative_pattern_1)
+
+        # failing because def does not have a text
+        self.assertEqual("{'vars': ['disease']} is not valid under any of the given schemas", next(es).message)
+
+    def read_yaml(self, yaml_path):
+        with open(yaml_path, "r") as stream:
+            try:
+                yaml_file = YAML(typ='safe').load(stream)
+            except YAMLError as exc:
+                self.fail("Yaml read failed:"+yaml_path)
+        return yaml_file
+
diff --git a/src/schema/test/negative_test_set/acute_negative.yaml b/src/schema/test/negative_test_set/acute_negative.yaml
new file mode 100644
index 0000000..a8aca1d
--- /dev/null
+++ b/src/schema/test/negative_test_set/acute_negative.yaml
@@ -0,0 +1,50 @@
+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
+
+# failing because
+#oneOf:
+#  - required: [text, vars]
+#  - required: [multi_clause]
+def:
+  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/acute.yaml b/src/schema/test/positive_test_set/patterns/acute.yaml
new file mode 100644
index 0000000..c740924
--- /dev/null
+++ b/src/schema/test/positive_test_set/patterns/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/multi_clause_schema.yaml b/src/schema/test/positive_test_set/patterns/multi_clause_schema.yaml
new file mode 100644
index 0000000..3bf80b4
--- /dev/null
+++ b/src/schema/test/positive_test_set/patterns/multi_clause_schema.yaml
@@ -0,0 +1,103 @@
+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.'
+                vars:
+                  - allen_markers_cat
+              - text: 'These cells have projection type %s.'
+                vars:
+                  - projection_type
+              - 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