diff --git a/src/main/scala/org/monarchinitiative/dosdp/DOSDP.scala b/src/main/scala/org/monarchinitiative/dosdp/DOSDP.scala
index 1bdb712..8eca78d 100644
--- a/src/main/scala/org/monarchinitiative/dosdp/DOSDP.scala
+++ b/src/main/scala/org/monarchinitiative/dosdp/DOSDP.scala
@@ -219,7 +219,8 @@ final case class PrintfAnnotation(
text: Option[String],
vars: Option[List[String]],
`override`: Option[String],
- multi_clause: Option[MultiClausePrintf] = None)
+ multi_clause: Option[MultiClausePrintf] = None,
+ permutations: Option[List[Permutation]] = None)
extends Annotations with PrintfText {
val shouldQuote = false
@@ -243,9 +244,9 @@ object Annotations {
implicit val decodeAnnotations: Decoder[Annotations] = Decoder[ListAnnotation].map[Annotations](identity).or(Decoder[IRIValueAnnotation].map[Annotations](identity)).or(Decoder[PrintfAnnotation].map[Annotations](identity))
implicit val encodeAnnotations: Encoder[Annotations] = Encoder.instance {
- case pfa @ PrintfAnnotation(_, _, _, _, _, _) => pfa.asJson
- case la @ ListAnnotation(_, _, _) => la.asJson
- case iva @ IRIValueAnnotation(_, _, _) => iva.asJson
+ case pfa @ PrintfAnnotation(_, _, _, _, _, _, _) => pfa.asJson
+ case la @ ListAnnotation(_, _, _) => la.asJson
+ case iva @ IRIValueAnnotation(_, _, _) => iva.asJson
}
}
@@ -257,7 +258,8 @@ final case class PrintfAnnotationOBO(
xrefs: Option[String],
text: Option[String],
vars: Option[List[String]],
- multi_clause: Option[MultiClausePrintf] = None) extends PrintfText with AnnotationLike with OBOAnnotations {
+ multi_clause: Option[MultiClausePrintf] = None,
+ permutations: Option[List[Permutation]] = None) extends PrintfText with AnnotationLike with OBOAnnotations {
val shouldQuote = false
@@ -350,4 +352,15 @@ final case class RegexFunction(regex: RegexSub) extends Function {
final case class Join(sep: String)
+/**
+ * Represents a permutation specification for generating additional annotations
+ * using values from annotation properties on filler terms.
+ *
+ * @param `var` The name of a single variable for which to generate permutations.
+ * Must correspond to a variable specified in the 'vars' field of the annotation.
+ * @param annotationProperties A list of annotation property names (as declared in the pattern's
+ * annotationProperties dictionary) whose values from the filler term
+ * will be used to generate additional annotations.
+ */
+final case class Permutation(`var`: String, annotationProperties: List[String])
diff --git a/src/main/scala/org/monarchinitiative/dosdp/ExpandedDOSDP.scala b/src/main/scala/org/monarchinitiative/dosdp/ExpandedDOSDP.scala
index 9c3686f..1fe1a9a 100644
--- a/src/main/scala/org/monarchinitiative/dosdp/ExpandedDOSDP.scala
+++ b/src/main/scala/org/monarchinitiative/dosdp/ExpandedDOSDP.scala
@@ -26,6 +26,12 @@ final case class ExpandedDOSDP(dosdp: DOSDP, prefixes: PartialFunction[String, S
private type Bindings = Map[String, Binding]
+ /**
+ * Index mapping filler term IRIs to their annotation property values.
+ * Structure: Map[FillerTermIRI, Map[AnnotationPropertyIRI, Set[AnnotationValues]]]
+ */
+ type PermutationIndex = Map[IRI, Map[IRI, Set[String]]]
+
val substitutions: Seq[ExpandedRegexSub] = dosdp.substitutions.toSeq.flatten.map(ExpandedRegexSub)
def allObjectProperties: Map[String, String] = dosdp.relations.getOrElse(Map.empty) ++ dosdp.objectProperties.getOrElse(Map.empty)
@@ -132,7 +138,7 @@ final case class ExpandedDOSDP(dosdp: DOSDP, prefixes: PartialFunction[String, S
ZIO.collectAll(owlAnnotations).map(_.to(Set).flatten)
}
- def filledAnnotationAxioms(annotationBindings: Option[Bindings], logicalBindings: Option[Bindings]): ZIO[Logging, DOSDPError, Set[OWLAnnotationAssertionAxiom]] = {
+ def filledAnnotationAxioms(annotationBindings: Option[Bindings], logicalBindings: Option[Bindings], permutationIndex: PermutationIndex = Map.empty): ZIO[Logging, DOSDPError, Set[OWLAnnotationAssertionAxiom]] = {
val definedTerm = (for {
actualBindings <- annotationBindings
SingleValue(value) <- actualBindings.get(DOSDP.DefinedClassVariable)
@@ -145,7 +151,7 @@ final case class ExpandedDOSDP(dosdp: DOSDP, prefixes: PartialFunction[String, S
} yield {
for {
normalizedAnnotationField <- allNormalizedAnns
- annotation <- translateAnnotations(normalizedAnnotationField, annotationBindings, logicalBindings)
+ annotation <- translateAnnotations(normalizedAnnotationField, annotationBindings, logicalBindings, permutationIndex)
} yield AnnotationAssertion(annotation.getAnnotations.asScala.toSet, annotation.getProperty, definedTerm, annotation.getValue)
}
}
@@ -171,22 +177,22 @@ final case class ExpandedDOSDP(dosdp: DOSDP, prefixes: PartialFunction[String, S
}.map(_.flatten.to(Set))
}
- private def translateAnnotations(annotationField: NormalizedAnnotation, annotationBindings: Option[Bindings], logicalBindings: Option[Bindings]): Set[OWLAnnotation] = annotationField match {
- case NormalizedPrintfAnnotation(prop, text, vars, multiClause, overrideColumnOpt, subAnnotations) =>
+ private def translateAnnotations(annotationField: NormalizedAnnotation, annotationBindings: Option[Bindings], logicalBindings: Option[Bindings], permutationIndex: PermutationIndex = Map.empty): Set[OWLAnnotation] = annotationField match {
+ case NormalizedPrintfAnnotation(prop, text, vars, multiClause, overrideColumnOpt, subAnnotations, permutations) =>
val valueOpts = (for {
column <- overrideColumnOpt
bindings <- annotationBindings
SingleValue(binding) <- bindings.get(column)
trimmed = binding.trim
if trimmed.nonEmpty
- } yield Seq(trimmed)).orElse(Some(printAnnotation(text, vars, multiClause, annotationBindings)))
- valueOpts.getOrElse(Seq.empty).toSet[String].map(value => Annotation(subAnnotations.flatMap(translateAnnotations(_, annotationBindings, logicalBindings)), prop, value))
+ } yield Seq(trimmed)).orElse(Some(printAnnotationWithPermutations(text, vars, multiClause, annotationBindings, logicalBindings, permutations, permutationIndex)))
+ valueOpts.getOrElse(Seq.empty).toSet[String].map(value => Annotation(subAnnotations.flatMap(translateAnnotations(_, annotationBindings, logicalBindings, permutationIndex)), prop, value))
case NormalizedListAnnotation(prop, value, subAnnotations) =>
// If no variable bindings are passed in, dummy value is filled in using variable name
val multiValBindingsOpt = annotationBindings.map(multiValueBindings)
val bindingsMap = multiValBindingsOpt.getOrElse(Map(value -> MultiValue(Set("'$" + value + "'"))))
val listValueOpt = bindingsMap.get(value)
- listValueOpt.toSet[MultiValue].flatMap(listValue => listValue.value.map(v => Annotation(subAnnotations.flatMap(translateAnnotations(_, annotationBindings, logicalBindings)), prop, v)))
+ listValueOpt.toSet[MultiValue].flatMap(listValue => listValue.value.map(v => Annotation(subAnnotations.flatMap(translateAnnotations(_, annotationBindings, logicalBindings, permutationIndex)), prop, v)))
case NormalizedIRIValueAnnotation(prop, varr, subAnnotations) =>
val maybeIRIValue = logicalBindings.map { actualBindings =>
for {
@@ -195,7 +201,7 @@ final case class ExpandedDOSDP(dosdp: DOSDP, prefixes: PartialFunction[String, S
} yield iri
}.getOrElse(Some(DOSDP.variableToIRI(varr)))
maybeIRIValue.toSet[IRI].map(iriValue => Annotation(
- subAnnotations.flatMap(translateAnnotations(_, annotationBindings, logicalBindings)),
+ subAnnotations.flatMap(translateAnnotations(_, annotationBindings, logicalBindings, permutationIndex)),
prop,
iriValue))
}
@@ -233,12 +239,101 @@ final case class ExpandedDOSDP(dosdp: DOSDP, prefixes: PartialFunction[String, S
}
}
+ /**
+ * Generates annotation texts with permutations. This extends the standard annotation generation
+ * by also substituting synonym values from filler terms to generate additional annotations.
+ *
+ * The algorithm:
+ * 1. For each variable in vars, collect all possible values:
+ * - Always include the label (from annotationBindings)
+ * - If the variable has permutation specs, also include values from the specified annotation properties
+ * 2. Generate the cartesian product of all value combinations
+ * 3. Format each combination using the text template
+ */
+ private def printAnnotationWithPermutations(
+ text: Option[String],
+ vars: Option[List[String]],
+ multiClause: Option[MultiClausePrintf],
+ annotationBindings: Option[Bindings],
+ logicalBindings: Option[Bindings],
+ permutations: List[NormalizedPermutation],
+ permutationIndex: PermutationIndex
+ ): Seq[String] = {
+ val variableList = vars.getOrElse(List.empty)
+
+ // If no permutations or no variables, fall back to standard behavior
+ if (permutations.isEmpty || variableList.isEmpty) {
+ return printAnnotation(text, vars, multiClause, annotationBindings)
+ }
+
+ // Build a map from variable name to permutation spec
+ val permutationsByVar: Map[String, NormalizedPermutation] = permutations.map(p => p.`var` -> p).toMap
+
+ // For each variable, collect all possible values (label + permutation values)
+ val valueLists: List[(String, List[String])] = variableList.map { varName =>
+ // Get the label value from annotation bindings
+ val labelValue: Option[String] = for {
+ bindings <- annotationBindings
+ SingleValue(value) <- bindings.get(varName)
+ } yield value
+
+ // Get the filler IRI from logical bindings to look up permutation values
+ val fillerIRI: Option[IRI] = for {
+ bindings <- logicalBindings
+ SingleValue(value) <- bindings.get(varName)
+ iri <- Prefixes.idToIRI(value, prefixes)
+ } yield iri
+
+ // Collect permutation values if this variable has a permutation spec
+ val permutationValues: Set[String] = (for {
+ perm <- permutationsByVar.get(varName)
+ iri <- fillerIRI
+ termAnnos <- permutationIndex.get(iri)
+ } yield {
+ perm.annotationProperties.flatMap { prop =>
+ termAnnos.getOrElse(prop.getIRI, Set.empty)
+ }.toSet
+ }).getOrElse(Set.empty)
+
+ // Combine label with permutation values, label always first
+ val allValues = labelValue.toList ++ permutationValues.toList
+ varName -> allValues
+ }
+
+ // Generate cartesian product of all value lists
+ val combinations: List[List[String]] = cartesianProduct(valueLists.map(_._2))
+
+ // Format each combination using the text template
+ combinations.flatMap { values =>
+ val bindingsForCombination = variableList.zip(values).map { case (varName, value) =>
+ varName -> SingleValue(value)
+ }.toMap
+ PrintfText.replaced(text, vars, multiClause, Some(bindingsForCombination), quote = false)
+ }.distinct
+ }
+
+ /**
+ * Computes the cartesian product of a list of lists.
+ * E.g., [[a, b], [1, 2]] => [[a, 1], [a, 2], [b, 1], [b, 2]]
+ */
+ private def cartesianProduct[T](lists: List[List[T]]): List[List[T]] = lists match {
+ case Nil => List(Nil)
+ case head :: tail =>
+ val tailProduct = cartesianProduct(tail)
+ for {
+ h <- head
+ t <- tailProduct
+ } yield h :: t
+ }
+
private def normalizeAnnotation(annotation: Annotations): ZIO[Logging, DOSDPError, NormalizedAnnotation] = annotation match {
- case PrintfAnnotation(anns, ap, text, vars, overrideColumn, multiClause) =>
+ case PrintfAnnotation(anns, ap, text, vars, overrideColumn, multiClause, perms) =>
for {
prop <- safeChecker.getOWLAnnotationProperty(ap).orElse(logErrorFail(s"No annotation property binding: $ap"))
annotations <- ZIO.foreach(anns.to(List).flatten)(normalizeAnnotation)
- } yield NormalizedPrintfAnnotation(prop, text, vars, multiClause, overrideColumn, annotations.to(Set))
+ _ <- validatePermutationVars(perms.getOrElse(List.empty), vars.getOrElse(List.empty))
+ normalizedPerms <- ZIO.foreach(perms.getOrElse(List.empty))(normalizePermutation)
+ } yield NormalizedPrintfAnnotation(prop, text, vars, multiClause, overrideColumn, annotations.to(Set), normalizedPerms)
case ListAnnotation(anns, ap, value) =>
for {
prop <- safeChecker.getOWLAnnotationProperty(ap).orElse(logErrorFail(s"No annotation property binding: $ap"))
@@ -253,11 +348,14 @@ final case class ExpandedDOSDP(dosdp: DOSDP, prefixes: PartialFunction[String, S
private def normalizeOBOAnnotation(annotation: OBOAnnotations, property: OWLAnnotationProperty, overrideColumn: Option[String]): ZIO[Logging, DOSDPError, NormalizedAnnotation] =
annotation match {
- case PrintfAnnotationOBO(anns, xrefs, text, vars, multiClause) =>
- ZIO.foreach(anns.to(List).flatten)(normalizeAnnotation).map { annotations =>
- NormalizedPrintfAnnotation(property, text, vars, multiClause, overrideColumn,
- annotations.to(Set) ++ xrefs.map(NormalizedListAnnotation(PrintfAnnotationOBO.Xref, _, Set.empty)))
- }
+ case PrintfAnnotationOBO(anns, xrefs, text, vars, multiClause, perms) =>
+ for {
+ annotations <- ZIO.foreach(anns.to(List).flatten)(normalizeAnnotation)
+ _ <- validatePermutationVars(perms.getOrElse(List.empty), vars.getOrElse(List.empty))
+ normalizedPerms <- ZIO.foreach(perms.getOrElse(List.empty))(normalizePermutation)
+ } yield NormalizedPrintfAnnotation(property, text, vars, multiClause, overrideColumn,
+ annotations.to(Set) ++ xrefs.map(NormalizedListAnnotation(PrintfAnnotationOBO.Xref, _, Set.empty)),
+ normalizedPerms)
case ListAnnotationOBO(value, xrefs) => ZIO.succeed(
NormalizedListAnnotation(
property,
@@ -266,6 +364,24 @@ final case class ExpandedDOSDP(dosdp: DOSDP, prefixes: PartialFunction[String, S
)
}
+ private def normalizePermutation(permutation: Permutation): ZIO[Logging, DOSDPError, NormalizedPermutation] = {
+ for {
+ props <- ZIO.foreach(permutation.annotationProperties) { apName =>
+ safeChecker.getOWLAnnotationProperty(apName).orElse(logErrorFail(s"No annotation property binding for permutation: $apName"))
+ }
+ } yield NormalizedPermutation(permutation.`var`, props)
+ }
+
+ /**
+ * Validates that all permutation vars reference variables in the annotation's vars list.
+ */
+ private def validatePermutationVars(permutations: List[Permutation], vars: List[String]): ZIO[Logging, DOSDPError, Unit] = {
+ val varSet = vars.toSet
+ val invalidVars = permutations.map(_.`var`).filterNot(varSet.contains)
+ if (invalidVars.isEmpty) ZIO.unit
+ else logErrorFail(s"Permutation vars not found in annotation vars list: ${invalidVars.mkString(", ")}. Available vars: ${vars.mkString(", ")}")
+ }
+
private def singleValueBindings(bindings: Bindings): Map[String, SingleValue] = bindings.collect { case (key, value: SingleValue) => key -> value }
private def multiValueBindings(bindings: Bindings): Map[String, MultiValue] = bindings.collect { case (key, value: MultiValue) => key -> value }
@@ -284,7 +400,9 @@ final case class ExpandedDOSDP(dosdp: DOSDP, prefixes: PartialFunction[String, S
def subAnnotations: Set[NormalizedAnnotation]
}
- private case class NormalizedPrintfAnnotation(property: OWLAnnotationProperty, text: Option[String], vars: Option[List[String]], multiClause: Option[MultiClausePrintf], overrideColumn: Option[String], subAnnotations: Set[NormalizedAnnotation]) extends NormalizedAnnotation
+ private case class NormalizedPrintfAnnotation(property: OWLAnnotationProperty, text: Option[String], vars: Option[List[String]], multiClause: Option[MultiClausePrintf], overrideColumn: Option[String], subAnnotations: Set[NormalizedAnnotation], permutations: List[NormalizedPermutation] = List.empty) extends NormalizedAnnotation
+
+ private case class NormalizedPermutation(`var`: String, annotationProperties: List[OWLAnnotationProperty])
private case class NormalizedListAnnotation(property: OWLAnnotationProperty, value: String, subAnnotations: Set[NormalizedAnnotation]) extends NormalizedAnnotation
diff --git a/src/main/scala/org/monarchinitiative/dosdp/cli/Generate.scala b/src/main/scala/org/monarchinitiative/dosdp/cli/Generate.scala
index d039df0..3277eab 100644
--- a/src/main/scala/org/monarchinitiative/dosdp/cli/Generate.scala
+++ b/src/main/scala/org/monarchinitiative/dosdp/cli/Generate.scala
@@ -56,6 +56,8 @@ object Generate {
def renderPattern(dosdp: DOSDP, prefixes: PartialFunction[String, String], fillers: List[Map[String, String]], ontOpt: Option[OWLOntology], outputLogicalAxioms: Boolean, outputAnnotationAxioms: Boolean, restrictAxiomsColumnName: Option[String], annotateAxiomSource: Boolean, axiomSourceProperty: OWLAnnotationProperty, generateDefinedClass: Boolean, extraReadableIdentifiers: Map[IRI, Map[IRI, String]]): ZIO[Logging, DOSDPError, Set[OWLAxiom]] = {
val eDOSDP = ExpandedDOSDP(dosdp, prefixes)
val knownColumns = dosdp.allVars
+ // Create permutation index for looking up annotation property values from filler terms
+ val permutationIndex: eDOSDP.PermutationIndex = ontOpt.map(createPermutationIndex).getOrElse(Map.empty)
for {
readableIdentifiers <- eDOSDP.readableIdentifierProperties
initialReadableIDIndex = ontOpt.map(ont => createReadableIdentifierIndex(readableIdentifiers, eDOSDP, ont)).getOrElse(Map.empty)
@@ -127,7 +129,7 @@ object Generate {
eDOSDP.filledLogicalAxioms(Some(logicalBindingsExtended), Some(annotationBindings))
else ZIO.succeed(Set.empty)
annotationAxioms <- if (localOutputAnnotationAxioms)
- eDOSDP.filledAnnotationAxioms(Some(annotationBindings), Some(logicalBindingsExtended))
+ eDOSDP.filledAnnotationAxioms(Some(annotationBindings), Some(logicalBindingsExtended), permutationIndex)
else ZIO.succeed(Set.empty)
} yield logicalAxioms ++ annotationAxioms
maybeAxioms
@@ -193,6 +195,17 @@ object Generate {
mappings.fold(Map.empty)(_ combine _)
}
+ /**
+ * Creates an index of annotation property values for filler terms, used for permutation generation.
+ * Structure: Map[FillerTermIRI, Map[AnnotationPropertyIRI, Set[AnnotationValues]]]
+ */
+ private def createPermutationIndex(ont: OWLOntology): Map[IRI, Map[IRI, Set[String]]] = {
+ val mappings = for {
+ AnnotationAssertion(_, prop, subj: IRI, value ^^ _) <- ont.getAxioms(AxiomType.ANNOTATION_ASSERTION, Imports.INCLUDED).asScala
+ } yield Map(subj -> Map(prop.getIRI -> Set(value)))
+ mappings.fold(Map.empty)(_ combine _)
+ }
+
private def irisToLabels(readableIdentifiers: List[OWLAnnotationProperty], binding: Binding, dosdp: ExpandedDOSDP, index: Map[IRI, Map[IRI, String]]): Binding = binding match {
case SingleValue(value) => SingleValue(Prefixes.idToIRI(value, dosdp.prefixes).map(iri => readableIdentifierForIRI(readableIdentifiers, iri, dosdp, index)).getOrElse(value))
case MultiValue(values) => MultiValue(values.map(value => Prefixes.idToIRI(value, dosdp.prefixes).map(iri => readableIdentifierForIRI(readableIdentifiers, iri, dosdp, index)).getOrElse(value)))
diff --git a/src/test/resources/org/monarchinitiative/dosdp/permutation_test.ofn b/src/test/resources/org/monarchinitiative/dosdp/permutation_test.ofn
new file mode 100644
index 0000000..f9f2a3f
--- /dev/null
+++ b/src/test/resources/org/monarchinitiative/dosdp/permutation_test.ofn
@@ -0,0 +1,33 @@
+Prefix(:=)
+Prefix(owl:=)
+Prefix(rdf:=)
+Prefix(xml:=)
+Prefix(xsd:=)
+Prefix(rdfs:=)
+Prefix(oio:=)
+
+Ontology(
+
+Declaration(Class(:MONDO_0005267))
+Declaration(Class(:MONDO_0005268))
+Declaration(AnnotationProperty(oio:hasExactSynonym))
+Declaration(AnnotationProperty(oio:hasRelatedSynonym))
+
+############################
+# Classes
+############################
+
+# Class: :MONDO_0005267 (heart disease)
+# Has synonyms: cardiac disease, heart condition
+
+AnnotationAssertion(rdfs:label :MONDO_0005267 "heart disease")
+AnnotationAssertion(oio:hasExactSynonym :MONDO_0005267 "cardiac disease")
+AnnotationAssertion(oio:hasRelatedSynonym :MONDO_0005267 "heart condition")
+
+# Class: :MONDO_0005268 (lung disease)
+# Has synonyms: pulmonary disease
+
+AnnotationAssertion(rdfs:label :MONDO_0005268 "lung disease")
+AnnotationAssertion(oio:hasExactSynonym :MONDO_0005268 "pulmonary disease")
+
+)
diff --git a/src/test/resources/org/monarchinitiative/dosdp/permutation_test.tsv b/src/test/resources/org/monarchinitiative/dosdp/permutation_test.tsv
new file mode 100644
index 0000000..71df333
--- /dev/null
+++ b/src/test/resources/org/monarchinitiative/dosdp/permutation_test.tsv
@@ -0,0 +1,3 @@
+defined_class disease
+MONDO:0001001 MONDO:0005267
+MONDO:0001002 MONDO:0005268
diff --git a/src/test/resources/org/monarchinitiative/dosdp/permutation_test.yaml b/src/test/resources/org/monarchinitiative/dosdp/permutation_test.yaml
new file mode 100644
index 0000000..974d056
--- /dev/null
+++ b/src/test/resources/org/monarchinitiative/dosdp/permutation_test.yaml
@@ -0,0 +1,47 @@
+pattern_name: permutationTestPattern
+pattern_iri: http://purl.obolibrary.org/obo/test/permutation_test.yaml
+
+description: 'Sample pattern to test permutation feature for synonym generation.'
+
+classes:
+ acute: "PATO:0000389"
+ disease: "MONDO:0000001"
+
+annotationProperties:
+ exact_synonym: "oio:hasExactSynonym"
+ related_synonym: "oio:hasRelatedSynonym"
+
+vars:
+ disease: "'disease'"
+
+name:
+ text: "acute %s"
+ vars:
+ - disease
+
+def:
+ text: "Acute form of %s."
+ vars:
+ - disease
+
+# Test permutation with generated_synonyms (OBO Printf style)
+generated_synonyms:
+ - text: "%s, acute"
+ vars:
+ - disease
+ permutations:
+ - var: disease
+ annotationProperties:
+ - exact_synonym
+
+# Test permutation through annotations list (general PrintfAnnotation style)
+annotations:
+ - annotationProperty: related_synonym
+ text: "acute form of %s"
+ vars:
+ - disease
+ permutations:
+ - var: disease
+ annotationProperties:
+ - exact_synonym
+ - related_synonym
diff --git a/src/test/scala/org/monarchinitiative/dosdp/PermutationTest.scala b/src/test/scala/org/monarchinitiative/dosdp/PermutationTest.scala
new file mode 100644
index 0000000..31fb79d
--- /dev/null
+++ b/src/test/scala/org/monarchinitiative/dosdp/PermutationTest.scala
@@ -0,0 +1,55 @@
+package org.monarchinitiative.dosdp
+
+import java.io.File
+import com.github.tototoshi.csv.TSVFormat
+import org.monarchinitiative.dosdp.cli.{Config, Generate}
+import org.phenoscape.scowl.{not => _, _}
+import org.semanticweb.owlapi.model.OWLAnnotationProperty
+import zio.test.Assertion._
+import zio.test._
+import zio.logging._
+
+object PermutationTest extends DefaultRunnableSpec {
+
+ val oioExactSynonym: OWLAnnotationProperty = AnnotationProperty("http://www.geneontology.org/formats/oboInOwl#hasExactSynonym")
+ val oioRelatedSynonym: OWLAnnotationProperty = AnnotationProperty("http://www.geneontology.org/formats/oboInOwl#hasRelatedSynonym")
+
+ def spec = suite("Permutation feature test")(
+ testM("Generate synonyms from filler term synonyms using permutations") {
+ for {
+ ontology <- Utilities.loadOntology("src/test/resources/org/monarchinitiative/dosdp/permutation_test.ofn", None)
+ dosdp <- Config.inputDOSDPFrom("src/test/resources/org/monarchinitiative/dosdp/permutation_test.yaml")
+ columnsAndFillers <- Generate.readFillers(new File("src/test/resources/org/monarchinitiative/dosdp/permutation_test.tsv"), new TSVFormat {})
+ (_, fillers) = columnsAndFillers
+ axioms <- Generate.renderPattern(dosdp: DOSDP, OBOPrefixes, fillers, Some(ontology), outputLogicalAxioms = false, outputAnnotationAxioms = true, None, annotateAxiomSource = false, AxiomRestrictionsTest.OboInOwlSource, generateDefinedClass = false, Map.empty)
+ } yield {
+ // Test that the label-based name is generated
+ assert(axioms)(contains(Class("http://purl.obolibrary.org/obo/MONDO_0001001") Annotation(RDFSLabel, "acute heart disease"))) &&
+ assert(axioms)(contains(Class("http://purl.obolibrary.org/obo/MONDO_0001002") Annotation(RDFSLabel, "acute lung disease"))) &&
+ // Test that label-based exact_synonym (via generated_synonyms) is generated
+ assert(axioms)(contains(Class("http://purl.obolibrary.org/obo/MONDO_0001001") Annotation(oioExactSynonym, "heart disease, acute"))) &&
+ // Test that permutation-based exact_synonyms are generated from filler's exact_synonym
+ assert(axioms)(contains(Class("http://purl.obolibrary.org/obo/MONDO_0001001") Annotation(oioExactSynonym, "cardiac disease, acute"))) &&
+ assert(axioms)(contains(Class("http://purl.obolibrary.org/obo/MONDO_0001002") Annotation(oioExactSynonym, "pulmonary disease, acute"))) &&
+ // Test that related_synonym annotations are generated with permutations from both exact and related synonyms
+ assert(axioms)(contains(Class("http://purl.obolibrary.org/obo/MONDO_0001001") Annotation(oioRelatedSynonym, "acute form of heart disease"))) &&
+ assert(axioms)(contains(Class("http://purl.obolibrary.org/obo/MONDO_0001001") Annotation(oioRelatedSynonym, "acute form of cardiac disease"))) &&
+ assert(axioms)(contains(Class("http://purl.obolibrary.org/obo/MONDO_0001001") Annotation(oioRelatedSynonym, "acute form of heart condition")))
+ }
+ },
+ testM("Generate annotations without ontology (no permutation values available)") {
+ for {
+ dosdp <- Config.inputDOSDPFrom("src/test/resources/org/monarchinitiative/dosdp/permutation_test.yaml")
+ columnsAndFillers <- Generate.readFillers(new File("src/test/resources/org/monarchinitiative/dosdp/permutation_test.tsv"), new TSVFormat {})
+ (_, fillers) = columnsAndFillers
+ // Without ontology, permutation values won't be available - only label-based annotation should be generated
+ axioms <- Generate.renderPattern(dosdp: DOSDP, OBOPrefixes, fillers, None, outputLogicalAxioms = false, outputAnnotationAxioms = true, None, annotateAxiomSource = false, AxiomRestrictionsTest.OboInOwlSource, generateDefinedClass = false, Map.empty)
+ } yield {
+ // Without ontology, the filler IRIs are used directly (no label lookup)
+ assert(axioms)(contains(Class("http://purl.obolibrary.org/obo/MONDO_0001001") Annotation(RDFSLabel, "acute http://purl.obolibrary.org/obo/MONDO_0005267"))) &&
+ assert(axioms)(contains(Class("http://purl.obolibrary.org/obo/MONDO_0001001") Annotation(oioExactSynonym, "http://purl.obolibrary.org/obo/MONDO_0005267, acute")))
+ }
+ }
+ ).provideCustomLayer(Logging.consoleErr())
+
+}