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()) + +}