diff --git a/.claude/settings.json b/.claude/settings.json index 29f0ffc..92b4675 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -1,7 +1,7 @@ { "permissions": { "allow": [ - "Bash(*)", + "Bash", "Edit", "MultiEdit", "NotebookEdit", diff --git a/project.Makefile b/project.Makefile index 9953614..366d2d2 100644 --- a/project.Makefile +++ b/project.Makefile @@ -13,3 +13,48 @@ docs/specification/compliance.md: tests/test_compliance/test_compliance_suite.py doctest: $(RUN) python -m doctest --option ELLIPSIS --option NORMALIZE_WHITESPACE src/linkml_map/*.py src/linkml_map/*/*.py +# Transform a LinkML-Map TransformationSpecification to FAIR Mappings Schema +# This demonstrates mapping metadata elements from linkml-map to fair-mappings-schema +FAIR_EXAMPLE_DIR = tests/input/examples/fair_mappings_metadata +FAIR_OUTPUT_DIR = $(FAIR_EXAMPLE_DIR)/output + +FAIR_LINKMLMAP_SPEC = $(FAIR_EXAMPLE_DIR)/transform/linkmlmap-to-fair.transformation.yaml +LINKML_SAMPLE_INPUT = $(FAIR_EXAMPLE_DIR)/data/sample-linkmlmap-spec.yaml +LINKMLMAP_SCHEMA = src/linkml_map/datamodel/transformer_model.yaml + +$(FAIR_OUTPUT_DIR)/linkmlmap-fair-mappings.yaml: $(LINKML_SAMPLE_INPUT) $(FAIR_LINKMLMAP_SPEC) + mkdir -p $(FAIR_OUTPUT_DIR) + $(RUN) linkml-map map-data \ + -T $(FAIR_LINKMLMAP_SPEC) \ + -s $(LINKMLMAP_SCHEMA) \ + --source-type TransformationSpecification \ + --unrestricted-eval \ + $(LINKML_SAMPLE_INPUT) \ + -o $@ + +transform-to-fair: $(FAIR_OUTPUT_DIR)/linkmlmap-fair-mappings.yaml + @echo "Transformed $(LINKML_SAMPLE_INPUT) to FAIR Mappings Schema format" + @echo "Output: $(FAIR_OUTPUT_DIR)/linkmlmap-fair-mappings.yaml" + +# Transform an SSSOM Mapping Set to FAIR Mappings Schema +# This demonstrates mapping SSSOM metadata to fair-mappings-schema +SSSOM_TRANSFORM_SPEC = $(FAIR_EXAMPLE_DIR)/transform/sssom-to-fair.transformation.yaml +SSSOM_SAMPLE_INPUT = $(FAIR_EXAMPLE_DIR)/data/sample-sssom-mapping-set.yaml +SSSOM_SCHEMA = /Users/matentzn/ws/SSSOM/src/sssom_schema/schema/sssom_schema.yaml + +$(FAIR_OUTPUT_DIR)/sssom-fair-mappings.yaml: $(SSSOM_SAMPLE_INPUT) $(SSSOM_TRANSFORM_SPEC) + mkdir -p $(FAIR_OUTPUT_DIR) + $(RUN) linkml-map map-data \ + -T $(SSSOM_TRANSFORM_SPEC) \ + -s $(SSSOM_SCHEMA) \ + --source-type 'mapping set' \ + --unrestricted-eval \ + $(SSSOM_SAMPLE_INPUT) \ + -o $@ + +sssom-to-fair: $(FAIR_OUTPUT_DIR)/sssom-fair-mappings.yaml + @echo "Transformed $(SSSOM_SAMPLE_INPUT) to FAIR Mappings Schema format" + @echo "Output: $(FAIR_OUTPUT_DIR)/sssom-fair-mappings.yaml" + +# Run all FAIR mappings transformations +all-fair-transforms: transform-to-fair sssom-to-fair diff --git a/src/linkml_map/datamodel/transformer_model.py b/src/linkml_map/datamodel/transformer_model.py index a032662..e3549e6 100644 --- a/src/linkml_map/datamodel/transformer_model.py +++ b/src/linkml_map/datamodel/transformer_model.py @@ -98,7 +98,9 @@ def __contains__(self, key:str) -> bool: 'schema': {'prefix_prefix': 'schema', 'prefix_reference': 'http://schema.org/'}, 'sh': {'prefix_prefix': 'sh', - 'prefix_reference': 'http://www.w3.org/ns/shacl#'}}, + 'prefix_reference': 'http://www.w3.org/ns/shacl#'}, + 'sssom': {'prefix_prefix': 'sssom', + 'prefix_reference': 'https://w3id.org/sssom/'}}, 'source_file': 'src/linkml_map/datamodel/transformer_model.yaml', 'title': 'LinkML Map Data Model', 'types': {'ClassReference': {'from_schema': 'https://w3id.org/linkml/transformer', @@ -171,11 +173,20 @@ class TransformationSpecification(SpecificationComponent): linkml_meta: ClassVar[LinkMLMeta] = LinkMLMeta({'from_schema': 'https://w3id.org/linkml/transformer', 'tree_root': True}) id: Optional[str] = Field(default=None, description="""Unique identifier for this transformation specification""", json_schema_extra = { "linkml_meta": {'alias': 'id', - 'domain_of': ['TransformationSpecification'], + 'domain_of': ['TransformationSpecification', 'Agent'], 'slot_uri': 'schema:identifier'} }) title: Optional[str] = Field(default=None, description="""human readable title for this transformation specification""", json_schema_extra = { "linkml_meta": {'alias': 'title', 'domain_of': ['TransformationSpecification'], 'slot_uri': 'dcterms:title'} }) + publication_date: Optional[date] = Field(default=None, description="""date of publication of this transformation specification""", json_schema_extra = { "linkml_meta": {'alias': 'publication_date', + 'domain_of': ['TransformationSpecification'], + 'slot_uri': 'dcterms:issued'} }) + license: Optional[str] = Field(default=None, description="""license under which this transformation specification is published""", json_schema_extra = { "linkml_meta": {'alias': 'license', + 'domain_of': ['TransformationSpecification'], + 'slot_uri': 'dcterms:license'} }) + version: Optional[str] = Field(default=None, description="""version of this transformation specification""", json_schema_extra = { "linkml_meta": {'alias': 'version', + 'domain_of': ['TransformationSpecification', 'Software'], + 'slot_uri': 'dcterms:version'} }) prefixes: Optional[Dict[str, KeyVal]] = Field(default_factory=dict, description="""maps prefixes to URL expansions""", json_schema_extra = { "linkml_meta": {'alias': 'prefixes', 'domain_of': ['TransformationSpecification'], 'slot_uri': 'sh:declare'} }) @@ -184,6 +195,16 @@ class TransformationSpecification(SpecificationComponent): source_schema: Optional[str] = Field(default=None, description="""name of the schema that describes the source (input) objects""", json_schema_extra = { "linkml_meta": {'alias': 'source_schema', 'domain_of': ['TransformationSpecification']} }) target_schema: Optional[str] = Field(default=None, description="""name of the schema that describes the target (output) objects""", json_schema_extra = { "linkml_meta": {'alias': 'target_schema', 'domain_of': ['TransformationSpecification']} }) source_schema_patches: Optional[Any] = Field(default=None, description="""Schema patches to apply to the source schema before transformation. Useful for adding foreign key relationships to auto-generated schemas. Uses LinkML schema YAML structure (classes, slots, attributes, etc.).""", json_schema_extra = { "linkml_meta": {'alias': 'source_schema_patches', 'domain_of': ['TransformationSpecification']} }) + creator: Optional[List[Union[Agent,Person,Organization,Software]]] = Field(default_factory=list, description="""A list of creators of this transformation specification""", json_schema_extra = { "linkml_meta": {'alias': 'creator', + 'domain_of': ['TransformationSpecification'], + 'slot_uri': 'dcterms:creator'} }) + author: Optional[List[Union[Agent,Person,Organization,Software]]] = Field(default_factory=list, description="""A list of authors of this transformation specification""", json_schema_extra = { "linkml_meta": {'alias': 'author', + 'domain_of': ['TransformationSpecification'], + 'slot_uri': 'dcterms:contributor'} }) + reviewer: Optional[List[Union[Agent,Person,Organization,Software]]] = Field(default_factory=list, description="""A list of reviewers of this transformation specification""", json_schema_extra = { "linkml_meta": {'alias': 'reviewer', 'domain_of': ['TransformationSpecification']} }) + mapping_method: Optional[str] = Field(default=None, description="""The method used to create this mapping, e.g. manual curation, automated mapping, etc.""", json_schema_extra = { "linkml_meta": {'alias': 'mapping_method', 'domain_of': ['TransformationSpecification']} }) + documentation: Optional[str] = Field(default=None, description="""URL or reference to documentation for the mapping specification""", json_schema_extra = { "linkml_meta": {'alias': 'documentation', 'domain_of': ['TransformationSpecification']} }) + content_url: Optional[str] = Field(default=None, description="""Reference to the actual content of the mapping specification""", json_schema_extra = { "linkml_meta": {'alias': 'content_url', 'domain_of': ['TransformationSpecification']} }) class_derivations: Optional[Dict[str, ClassDerivation]] = Field(default_factory=dict, description="""Instructions on how to derive a set of classes in the target schema from classes in the source schema.""", json_schema_extra = { "linkml_meta": {'alias': 'class_derivations', 'domain_of': ['TransformationSpecification', 'ObjectDerivation']} }) enum_derivations: Optional[Dict[str, EnumDerivation]] = Field(default_factory=dict, description="""Instructions on how to derive a set of enums in the target schema""", json_schema_extra = { "linkml_meta": {'alias': 'enum_derivations', 'domain_of': ['TransformationSpecification']} }) @@ -209,7 +230,8 @@ class ElementDerivation(SpecificationComponent): 'ObjectDerivation', 'SlotDerivation', 'EnumDerivation', - 'PermissibleValueDerivation']} }) + 'PermissibleValueDerivation', + 'Agent']} }) copy_directives: Optional[Dict[str, CopyDirective]] = Field(default_factory=dict, json_schema_extra = { "linkml_meta": {'alias': 'copy_directives', 'domain_of': ['TransformationSpecification', 'ElementDerivation']} }) overrides: Optional[Any] = Field(default=None, description="""overrides source schema slots""", json_schema_extra = { "linkml_meta": {'alias': 'overrides', 'domain_of': ['ElementDerivation']} }) @@ -261,7 +283,8 @@ class ClassDerivation(ElementDerivation): 'ObjectDerivation', 'SlotDerivation', 'EnumDerivation', - 'PermissibleValueDerivation']} }) + 'PermissibleValueDerivation', + 'Agent']} }) copy_directives: Optional[Dict[str, CopyDirective]] = Field(default_factory=dict, json_schema_extra = { "linkml_meta": {'alias': 'copy_directives', 'domain_of': ['TransformationSpecification', 'ElementDerivation']} }) overrides: Optional[Any] = Field(default=None, description="""overrides source schema slots""", json_schema_extra = { "linkml_meta": {'alias': 'overrides', 'domain_of': ['ElementDerivation']} }) @@ -294,7 +317,8 @@ class ObjectDerivation(ElementDerivation): 'ObjectDerivation', 'SlotDerivation', 'EnumDerivation', - 'PermissibleValueDerivation']} }) + 'PermissibleValueDerivation', + 'Agent']} }) class_derivations: Optional[Dict[str, ClassDerivation]] = Field(default_factory=dict, json_schema_extra = { "linkml_meta": {'alias': 'class_derivations', 'domain_of': ['TransformationSpecification', 'ObjectDerivation']} }) copy_directives: Optional[Dict[str, CopyDirective]] = Field(default_factory=dict, json_schema_extra = { "linkml_meta": {'alias': 'copy_directives', @@ -339,7 +363,8 @@ class SlotDerivation(ElementDerivation): 'ObjectDerivation', 'SlotDerivation', 'EnumDerivation', - 'PermissibleValueDerivation']} }) + 'PermissibleValueDerivation', + 'Agent']} }) populated_from: Optional[str] = Field(default=None, description="""Source slot name""", json_schema_extra = { "linkml_meta": {'alias': 'populated_from', 'domain_of': ['ClassDerivation', 'SlotDerivation', @@ -405,7 +430,8 @@ class EnumDerivation(ElementDerivation): 'ObjectDerivation', 'SlotDerivation', 'EnumDerivation', - 'PermissibleValueDerivation']} }) + 'PermissibleValueDerivation', + 'Agent']} }) populated_from: Optional[str] = Field(default=None, description="""Source enum name""", json_schema_extra = { "linkml_meta": {'alias': 'populated_from', 'domain_of': ['ClassDerivation', 'SlotDerivation', @@ -459,7 +485,8 @@ class PermissibleValueDerivation(ElementDerivation): 'ObjectDerivation', 'SlotDerivation', 'EnumDerivation', - 'PermissibleValueDerivation']} }) + 'PermissibleValueDerivation', + 'Agent']} }) expr: Optional[str] = Field(default=None, json_schema_extra = { "linkml_meta": {'alias': 'expr', 'domain_of': ['SlotDerivation', 'EnumDerivation', @@ -507,7 +534,8 @@ class PrefixDerivation(ElementDerivation): 'ObjectDerivation', 'SlotDerivation', 'EnumDerivation', - 'PermissibleValueDerivation']} }) + 'PermissibleValueDerivation', + 'Agent']} }) copy_directives: Optional[Dict[str, CopyDirective]] = Field(default_factory=dict, json_schema_extra = { "linkml_meta": {'alias': 'copy_directives', 'domain_of': ['TransformationSpecification', 'ElementDerivation']} }) overrides: Optional[Any] = Field(default=None, description="""overrides source schema slots""", json_schema_extra = { "linkml_meta": {'alias': 'overrides', 'domain_of': ['ElementDerivation']} }) @@ -622,6 +650,84 @@ class KeyVal(ConfiguredBaseModel): value: Optional[Any] = Field(default=None, json_schema_extra = { "linkml_meta": {'alias': 'value', 'domain_of': ['SlotDerivation', 'KeyVal']} }) +class Agent(ConfiguredBaseModel): + """ + An entity that can create or contribute to a digital object, such as an author or creator. + """ + linkml_meta: ClassVar[LinkMLMeta] = LinkMLMeta({'abstract': True, 'from_schema': 'https://w3id.org/linkml/transformer'}) + + id: str = Field(default=..., description="""Identifier for the agent""", json_schema_extra = { "linkml_meta": {'alias': 'id', 'domain_of': ['TransformationSpecification', 'Agent']} }) + name: Optional[str] = Field(default=None, description="""Name of the agent""", json_schema_extra = { "linkml_meta": {'alias': 'name', + 'domain_of': ['ElementDerivation', + 'ObjectDerivation', + 'SlotDerivation', + 'EnumDerivation', + 'PermissibleValueDerivation', + 'Agent'], + 'slot_uri': 'schema:name'} }) + type: Literal["Agent"] = Field(default="Agent", description="""Type of the agent""", json_schema_extra = { "linkml_meta": {'alias': 'type', 'designates_type': True, 'domain_of': ['Agent']} }) + + +class Person(Agent): + """ + An individual person who contributes to a mapping specification + """ + linkml_meta: ClassVar[LinkMLMeta] = LinkMLMeta({'from_schema': 'https://w3id.org/linkml/transformer'}) + + orcid: Optional[str] = Field(default=None, description="""ORCID identifier for the person""", json_schema_extra = { "linkml_meta": {'alias': 'orcid', 'domain_of': ['Person']} }) + affiliation: Optional[str] = Field(default=None, description="""Institutional affiliation of the person""", json_schema_extra = { "linkml_meta": {'alias': 'affiliation', 'domain_of': ['Person']} }) + id: str = Field(default=..., description="""Identifier for the agent""", json_schema_extra = { "linkml_meta": {'alias': 'id', 'domain_of': ['TransformationSpecification', 'Agent']} }) + name: Optional[str] = Field(default=None, description="""Name of the agent""", json_schema_extra = { "linkml_meta": {'alias': 'name', + 'domain_of': ['ElementDerivation', + 'ObjectDerivation', + 'SlotDerivation', + 'EnumDerivation', + 'PermissibleValueDerivation', + 'Agent'], + 'slot_uri': 'schema:name'} }) + type: Literal["Person"] = Field(default="Person", description="""Type of the agent""", json_schema_extra = { "linkml_meta": {'alias': 'type', 'designates_type': True, 'domain_of': ['Agent']} }) + + +class Organization(Agent): + """ + An organization or institution that contributes to a mapping specification + """ + linkml_meta: ClassVar[LinkMLMeta] = LinkMLMeta({'from_schema': 'https://w3id.org/linkml/transformer'}) + + ror_id: Optional[str] = Field(default=None, description="""ROR (Research Organization Registry) identifier""", json_schema_extra = { "linkml_meta": {'alias': 'ror_id', 'domain_of': ['Organization']} }) + url: Optional[str] = Field(default=None, description="""URL or web address of the organization""", json_schema_extra = { "linkml_meta": {'alias': 'url', 'domain_of': ['Organization']} }) + id: str = Field(default=..., description="""Identifier for the agent""", json_schema_extra = { "linkml_meta": {'alias': 'id', 'domain_of': ['TransformationSpecification', 'Agent']} }) + name: Optional[str] = Field(default=None, description="""Name of the agent""", json_schema_extra = { "linkml_meta": {'alias': 'name', + 'domain_of': ['ElementDerivation', + 'ObjectDerivation', + 'SlotDerivation', + 'EnumDerivation', + 'PermissibleValueDerivation', + 'Agent'], + 'slot_uri': 'schema:name'} }) + type: Literal["Organization"] = Field(default="Organization", description="""Type of the agent""", json_schema_extra = { "linkml_meta": {'alias': 'type', 'designates_type': True, 'domain_of': ['Agent']} }) + + +class Software(Agent): + """ + A software tool or system used in creating mappings + """ + linkml_meta: ClassVar[LinkMLMeta] = LinkMLMeta({'from_schema': 'https://w3id.org/linkml/transformer'}) + + version: Optional[str] = Field(default=None, description="""Version of the software""", json_schema_extra = { "linkml_meta": {'alias': 'version', 'domain_of': ['TransformationSpecification', 'Software']} }) + repository_url: Optional[str] = Field(default=None, description="""URL to a code repository""", json_schema_extra = { "linkml_meta": {'alias': 'repository_url', 'domain_of': ['Software']} }) + id: str = Field(default=..., description="""Identifier for the agent""", json_schema_extra = { "linkml_meta": {'alias': 'id', 'domain_of': ['TransformationSpecification', 'Agent']} }) + name: Optional[str] = Field(default=None, description="""Name of the agent""", json_schema_extra = { "linkml_meta": {'alias': 'name', + 'domain_of': ['ElementDerivation', + 'ObjectDerivation', + 'SlotDerivation', + 'EnumDerivation', + 'PermissibleValueDerivation', + 'Agent'], + 'slot_uri': 'schema:name'} }) + type: Literal["Software"] = Field(default="Software", description="""Type of the agent""", json_schema_extra = { "linkml_meta": {'alias': 'type', 'designates_type': True, 'domain_of': ['Agent']} }) + + class CopyDirective(ConfiguredBaseModel): """ Instructs a Schema Mapper in how to map to a target schema. Not used for data transformation. @@ -662,5 +768,9 @@ class CopyDirective(ConfiguredBaseModel): GroupingOperation.model_rebuild() PivotOperation.model_rebuild() KeyVal.model_rebuild() +Agent.model_rebuild() +Person.model_rebuild() +Organization.model_rebuild() +Software.model_rebuild() CopyDirective.model_rebuild() diff --git a/src/linkml_map/datamodel/transformer_model.yaml b/src/linkml_map/datamodel/transformer_model.yaml index 528985d..43d8ac0 100644 --- a/src/linkml_map/datamodel/transformer_model.yaml +++ b/src/linkml_map/datamodel/transformer_model.yaml @@ -23,6 +23,7 @@ prefixes: rdfs: http://www.w3.org/2000/01/rdf-schema# sh: http://www.w3.org/ns/shacl# STATO: http://purl.obolibrary.org/obo/STATO_ + sssom: https://w3id.org/sssom/ default_prefix: linkmlmap imports: @@ -73,8 +74,21 @@ classes: description: Unique identifier for this transformation specification slot_uri: schema:identifier title: + # Corresponds to fair_mappings_schema:MappingSpecification.name description: human readable title for this transformation specification slot_uri: dcterms:title + publication_date: + description: date of publication of this transformation specification + range: date + slot_uri: dcterms:issued + license: + description: license under which this transformation specification is published + range: uriorcurie + slot_uri: dcterms:license + version: + description: version of this transformation specification + range: string + slot_uri: dcterms:version prefixes: description: maps prefixes to URL expansions range: KeyVal @@ -86,8 +100,10 @@ classes: multivalued: true inlined: true source_schema: + # Corresponds to fair_mappings_schema:MappingSpecification.subject_source description: name of the schema that describes the source (input) objects target_schema: + # Corresponds to fair_mappings_schema:MappingSpecification.object_source description: name of the schema that describes the target (output) objects source_schema_patches: range: Any @@ -95,6 +111,36 @@ classes: Schema patches to apply to the source schema before transformation. Useful for adding foreign key relationships to auto-generated schemas. Uses LinkML schema YAML structure (classes, slots, attributes, etc.). + creator: + description: A list of creators of this transformation specification + range: Agent + multivalued: true + inlined: true + inlined_as_list: true + slot_uri: dcterms:creator + author: + description: A list of authors of this transformation specification + range: Agent + multivalued: true + inlined: true + inlined_as_list: true + slot_uri: dcterms:contributor + reviewer: + description: A list of reviewers of this transformation specification + range: Agent + multivalued: true + inlined: true + inlined_as_list: true + mapping_method: + description: The method used to create this mapping, e.g. manual curation, automated + mapping, etc. + range: uriorcurie + documentation: + description: URL or reference to documentation for the mapping specification + range: uri + content_url: + description: Reference to the actual content of the mapping specification + range: uri class_derivations: description: >- Instructions on how to derive a set of classes in the target schema @@ -458,6 +504,60 @@ classes: value: range: Any + # Aligned with fair_mappings_schema:Agent + Agent: + abstract: true + description: An entity that can create or contribute to a digital object, such as an author or creator. + attributes: + id: + identifier: true + description: Identifier for the agent + range: uriorcurie + name: + description: Name of the agent + range: string + slot_uri: schema:name + type: + description: Type of the agent + range: string + designates_type: true + + # Aligned with fair_mappings_schema:Person + Person: + is_a: Agent + description: An individual person who contributes to a mapping specification + attributes: + orcid: + description: ORCID identifier for the person + range: uriorcurie + affiliation: + description: Institutional affiliation of the person + range: string + + # Aligned with fair_mappings_schema:Organization + Organization: + is_a: Agent + description: An organization or institution that contributes to a mapping specification + attributes: + ror_id: + description: ROR (Research Organization Registry) identifier + range: uriorcurie + url: + description: URL or web address of the organization + range: uri + + # Aligned with fair_mappings_schema:Software + Software: + is_a: Agent + description: A software tool or system used in creating mappings + attributes: + version: + description: Version of the software + range: string + repository_url: + description: URL to a code repository + range: uri + CopyDirective: description: >- Instructs a Schema Mapper in how to map to a target schema. Not used for data transformation. diff --git a/tests/input/examples/fair_mappings_metadata/__init__.py b/tests/input/examples/fair_mappings_metadata/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/input/examples/fair_mappings_metadata/data/sample-linkmlmap-spec.yaml b/tests/input/examples/fair_mappings_metadata/data/sample-linkmlmap-spec.yaml new file mode 100644 index 0000000..4f7d5b3 --- /dev/null +++ b/tests/input/examples/fair_mappings_metadata/data/sample-linkmlmap-spec.yaml @@ -0,0 +1,76 @@ +# Sample TransformationSpecification instance for testing transformation to FAIR Mappings Schema +# This example includes both metadata AND actual transformation content. +# When transformed to FAIR Mappings Schema, only the metadata elements are extracted; +# the class_derivations, enum_derivations etc. are linkml-map specific and not part of FAIR Mappings. +# +# NOTE: class_derivations and enum_derivations are commented out below due to a bug +# in dynamic_object.py that doesn't handle key-based (vs identifier-based) classes. +# The actual transformation would ignore these anyway since FAIR Mappings Schema +# doesn't have equivalents for these linkml-map specific elements. + +id: https://example.org/transformations/gene-expression-mapping +title: Gene Expression Data Transformation +description: |- + A transformation specification that maps gene expression data from + a legacy format to a standardized biomedical data model. + + This specification includes class derivations for: + - GeneExpressionMixin (from GeneExpressionRecord) + - OrganismTaxon (from SampleMetadata) + - Study (from ExperimentInfo) + + And enum derivations for: + - ExpressionLevelCategory (from LegacyExpressionLevel) +publication_date: "2024-01-15" +license: https://creativecommons.org/licenses/by/4.0/ +version: "1.0.0" +mapping_method: semapv:ManualMappingCuration +documentation: https://example.org/docs/gene-expression-mapping +content_url: https://example.org/content/gene-expression-mapping.yaml + +# Using Person type for creator/author/reviewer +creator: + - type: Person + id: orcid:0000-0002-1234-5678 + name: Jane Doe + +author: + - type: Person + id: orcid:0000-0003-9876-5432 + name: John Smith + +reviewer: + - type: Person + id: orcid:0000-0001-1111-2222 + name: Alice Johnson + +# source_schema and target_schema (simple strings in linkml-map) +source_schema: legacy_gene_expression.yaml +target_schema: biolink_model.yaml + +# ============================================================================ +# ACTUAL TRANSFORMATION CONTENT (commented out due to dynamic_object bug) +# These elements are linkml-map specific and won't appear in FAIR Mappings output +# ============================================================================ +# +# class_derivations: +# GeneExpressionMixin: +# populated_from: GeneExpressionRecord +# slot_derivations: +# id: +# populated_from: record_id +# gene_id: +# populated_from: gene_symbol +# +# OrganismTaxon: +# populated_from: SampleMetadata +# slot_derivations: +# id: +# populated_from: taxon_id +# +# enum_derivations: +# ExpressionLevelCategory: +# populated_from: LegacyExpressionLevel +# permissible_value_derivations: +# HIGH: +# populated_from: H diff --git a/tests/input/examples/fair_mappings_metadata/data/sample-sssom-mapping-set.yaml b/tests/input/examples/fair_mappings_metadata/data/sample-sssom-mapping-set.yaml new file mode 100644 index 0000000..6ee8a5c --- /dev/null +++ b/tests/input/examples/fair_mappings_metadata/data/sample-sssom-mapping-set.yaml @@ -0,0 +1,69 @@ +# Sample SSSOM Mapping Set instance for testing transformation to FAIR Mappings Schema +# This demonstrates how SSSOM metadata maps to FAIR Mappings Schema MappingSpecification +mapping_set_id: http://purl.obolibrary.org/obo/mondo/mappings/mondo_exactmatch_ncit.sssom.tsv +mapping_set_title: Mondo to NCIT Exact Mappings +mapping_set_description: |- + This mapping set contains exact mappings between Mondo Disease Ontology + and NCI Thesaurus (NCIT) terms. These mappings were curated by the + Monarch Initiative team to support disease data integration. +mapping_set_version: "2024-01-15" +license: https://creativecommons.org/publicdomain/zero/1.0/ +publication_date: "2024-01-15" + +# Creator and author information (SSSOM uses ID + label pattern) +creator_id: + - orcid:0000-0002-7356-1779 + - orcid:0000-0002-6601-2165 +creator_label: + - Nicolas Matentzoglu + - Chris Mungall + +author_id: + - orcid:0000-0002-7356-1779 +author_label: + - Nicolas Matentzoglu + +reviewer_id: + - orcid:0000-0001-9114-8737 +reviewer_label: + - Melissa Haendel + +# Source information +subject_source: obo:mondo.owl +subject_source_version: http://purl.obolibrary.org/obo/mondo/releases/2024-01-03/mondo.owl +object_source: obo:ncit.owl +object_source_version: http://purl.obolibrary.org/obo/ncit/releases/2024-01/ncit.owl + +# Mapping provenance +mapping_tool: https://github.com/mapping-commons/sssom-py +mapping_tool_version: "0.4.0" +mapping_date: "2024-01-10" +mapping_provider: https://monarchinitiative.org/ + +# Sample mappings (demonstrating that mappings are preserved but not part of FAIR Mappings output) +mappings: + - subject_id: MONDO:0005015 + subject_label: diabetes mellitus + predicate_id: skos:exactMatch + object_id: NCIT:C2985 + object_label: Diabetes Mellitus + mapping_justification: semapv:ManualMappingCuration + confidence: 0.95 + + - subject_id: MONDO:0004992 + subject_label: cancer + predicate_id: skos:exactMatch + object_id: NCIT:C9305 + object_label: Malignant Neoplasm + mapping_justification: semapv:ManualMappingCuration + confidence: 0.90 + + - subject_id: MONDO:0005301 + subject_label: multiple sclerosis + predicate_id: skos:exactMatch + object_id: NCIT:C3243 + object_label: Multiple Sclerosis + mapping_justification: semapv:LexicalMatching + confidence: 0.85 + match_string: + - multiple sclerosis diff --git a/tests/input/examples/fair_mappings_metadata/output/linkmlmap-fair-mappings.yaml b/tests/input/examples/fair_mappings_metadata/output/linkmlmap-fair-mappings.yaml new file mode 100644 index 0000000..93d6ff3 --- /dev/null +++ b/tests/input/examples/fair_mappings_metadata/output/linkmlmap-fair-mappings.yaml @@ -0,0 +1,41 @@ +id: https://example.org/transformations/gene-expression-mapping +name: Gene Expression Data Transformation +description: 'A transformation specification that maps gene expression data from + + a legacy format to a standardized biomedical data model. + + + This specification includes class derivations for: + + - GeneExpressionMixin (from GeneExpressionRecord) + + - OrganismTaxon (from SampleMetadata) + + - Study (from ExperimentInfo) + + + And enum derivations for: + + - ExpressionLevelCategory (from LegacyExpressionLevel)' +publication_date: '2024-01-15' +license: https://creativecommons.org/licenses/by/4.0/ +version: 1.0.0 +mapping_method: semapv:ManualMappingCuration +documentation: https://example.org/docs/gene-expression-mapping +content_url: https://example.org/content/gene-expression-mapping.yaml +creator: +- id: orcid:0000-0002-1234-5678 + name: Jane Doe + type: Person +author: +- id: orcid:0000-0003-9876-5432 + name: John Smith + type: Person +reviewer: +- id: orcid:0000-0001-1111-2222 + name: Alice Johnson + type: Person +subject_source: + name: legacy_gene_expression.yaml +object_source: + name: biolink_model.yaml diff --git a/tests/input/examples/fair_mappings_metadata/output/sssom-fair-mappings.yaml b/tests/input/examples/fair_mappings_metadata/output/sssom-fair-mappings.yaml new file mode 100644 index 0000000..7e0f4f9 --- /dev/null +++ b/tests/input/examples/fair_mappings_metadata/output/sssom-fair-mappings.yaml @@ -0,0 +1,12 @@ +id: http://purl.obolibrary.org/obo/mondo/mappings/mondo_exactmatch_ncit.sssom.tsv +name: Mondo to NCIT Exact Mappings +description: 'This mapping set contains exact mappings between Mondo Disease Ontology + + and NCI Thesaurus (NCIT) terms. These mappings were curated by the + + Monarch Initiative team to support disease data integration.' +version: '2024-01-15' +license: https://creativecommons.org/publicdomain/zero/1.0/ +publication_date: '2024-01-15' +type: sssom +mapping_method: https://github.com/mapping-commons/sssom-py diff --git a/tests/input/examples/fair_mappings_metadata/transform/linkmlmap-to-fair.transformation.yaml b/tests/input/examples/fair_mappings_metadata/transform/linkmlmap-to-fair.transformation.yaml new file mode 100644 index 0000000..7449202 --- /dev/null +++ b/tests/input/examples/fair_mappings_metadata/transform/linkmlmap-to-fair.transformation.yaml @@ -0,0 +1,135 @@ +id: https://w3id.org/linkml/transformer/linkml-map-to-fair-mappings +title: LinkML-Map to FAIR Mappings Schema Transformation +description: |- + Transforms a LinkML-Map TransformationSpecification to a FAIR Mappings Schema + MappingSpecification. This preserves metadata elements while discarding the + actual transformation rules (class_derivations, slot_derivations, etc.) which + have no equivalent in the FAIR Mappings Schema. + +prefixes: + linkmlmap: https://w3id.org/linkml/transformer/ + fair_mappings_schema: https://w3id.org/mapping-commons/fair-mappings-schema/ + +source_schema: src/linkml_map/datamodel/transformer_model.yaml +# Note: Use absolute path or ensure fair-mappings-schema is available locally +# The transformation will still work without the target schema, but with warnings +target_schema: /Users/matentzn/ws/fair-mappings-schema/src/fair_mappings_schema/schema/fair_mappings_schema.yaml + +class_derivations: + + MappingSpecification: + populated_from: TransformationSpecification + description: |- + Maps TransformationSpecification metadata to MappingSpecification. + Note: class_derivations, slot_derivations, enum_derivations, prefixes, + copy_directives, and schema patches are not preserved as they have no + equivalent in the FAIR Mappings Schema. + slot_derivations: + id: + populated_from: id + name: + populated_from: title + description: Maps 'title' from source to 'name' in target + description: + populated_from: description + publication_date: + populated_from: publication_date + license: + populated_from: license + version: + populated_from: version + mapping_method: + populated_from: mapping_method + documentation: + populated_from: documentation + content_url: + populated_from: content_url + creator: + populated_from: creator + author: + populated_from: author + reviewer: + populated_from: reviewer + subject_source: + expr: "{'name': source_schema}" + description: Maps source_schema string to a Source object with name field + object_source: + expr: "{'name': target_schema}" + description: Maps target_schema string to a Source object with name field + + Agent: + populated_from: Agent + description: Maps Agent class (abstract in both schemas) + slot_derivations: + id: + populated_from: id + name: + populated_from: name + type: + populated_from: type + + Person: + populated_from: Person + description: Maps Person (individual contributor) + slot_derivations: + id: + populated_from: id + name: + populated_from: name + type: + populated_from: type + orcid: + populated_from: orcid + affiliation: + populated_from: affiliation + + Organization: + populated_from: Organization + description: Maps Organization + slot_derivations: + id: + populated_from: id + name: + populated_from: name + type: + populated_from: type + ror_id: + populated_from: ror_id + url: + populated_from: url + + Software: + populated_from: Software + description: Maps Software agent + slot_derivations: + id: + populated_from: id + name: + populated_from: name + type: + populated_from: type + version: + populated_from: version + repository_url: + populated_from: repository_url + + Source: + populated_from: Source + description: Maps Source (data source metadata) + slot_derivations: + id: + populated_from: id + name: + populated_from: name + version: + populated_from: version + documentation: + populated_from: documentation + content_url: + populated_from: content_url + content_type: + populated_from: content_type + metadata_url: + populated_from: metadata_url + metadata_type: + populated_from: metadata_type diff --git a/tests/input/examples/fair_mappings_metadata/transform/sssom-to-fair.transformation.yaml b/tests/input/examples/fair_mappings_metadata/transform/sssom-to-fair.transformation.yaml new file mode 100644 index 0000000..ce1c021 --- /dev/null +++ b/tests/input/examples/fair_mappings_metadata/transform/sssom-to-fair.transformation.yaml @@ -0,0 +1,61 @@ +id: https://w3id.org/linkml/transformer/sssom-to-fair-mappings +title: SSSOM to FAIR Mappings Schema Transformation +description: |- + Transforms an SSSOM Mapping Set to a FAIR Mappings Schema MappingSpecification. + This preserves metadata elements while discarding the individual mappings, + which have no equivalent in the FAIR Mappings Schema (which focuses on + mapping specification metadata, not individual mapping assertions). + + Key transformations: + - mapping_set_id → id + - mapping_set_title → name + - mapping_set_description → description + - subject_source → subject_source.id + - object_source → object_source.id + - Sets type to 'sssom' to indicate the mapping specification type + + Note: Creator/author/reviewer transformations are simplified since SSSOM + uses flat ID lists while FAIR Mappings uses structured Agent objects. + +prefixes: + sssom: https://w3id.org/sssom/ + fair_mappings_schema: https://w3id.org/mapping-commons/fair-mappings-schema/ + +source_schema: /Users/matentzn/ws/SSSOM/src/sssom_schema/schema/sssom_schema.yaml +target_schema: /Users/matentzn/ws/fair-mappings-schema/src/fair_mappings_schema/schema/fair_mappings_schema.yaml + +class_derivations: + + MappingSpecification: + populated_from: "mapping set" + description: |- + Maps SSSOM Mapping Set metadata to MappingSpecification. + Note: Individual mappings are not preserved as they represent + actual mapping assertions, not metadata about the specification. + slot_derivations: + id: + populated_from: mapping_set_id + name: + populated_from: mapping_set_title + description: + populated_from: mapping_set_description + version: + populated_from: mapping_set_version + license: + populated_from: license + publication_date: + populated_from: publication_date + type: + # SSSOM mapping sets are always of type 'sssom' + value: "sssom" + mapping_method: + # Use mapping_tool as a proxy for mapping_method + populated_from: mapping_tool + subject_source: + # Map subject_source string to Source object with id + expr: "{'id': str(subject_source)} if subject_source else None" + description: Maps subject_source to a Source object + object_source: + # Map object_source string to Source object with id + expr: "{'id': str(object_source)} if object_source else None" + description: Maps object_source to a Source object diff --git a/tests/input/examples/personinfo_basic/output/personinfo_compiled.md b/tests/input/examples/personinfo_basic/output/personinfo_compiled.md index 6b1b384..d368f3a 100644 --- a/tests/input/examples/personinfo_basic/output/personinfo_compiled.md +++ b/tests/input/examples/personinfo_basic/output/personinfo_compiled.md @@ -1,4 +1,4 @@ -# my mappings +# PersonInfo to Agent example transformation ## Class Mappings diff --git a/tests/input/examples/personinfo_basic/transform/personinfo-to-agent.transform.yaml b/tests/input/examples/personinfo_basic/transform/personinfo-to-agent.transform.yaml index d16593b..3625a91 100644 --- a/tests/input/examples/personinfo_basic/transform/personinfo-to-agent.transform.yaml +++ b/tests/input/examples/personinfo_basic/transform/personinfo-to-agent.transform.yaml @@ -1,9 +1,21 @@ -id: my-mappings -title: my mappings +id: https://w3id.org/linkml/map/example/personinfo-to-agent.transform.yaml +title: PersonInfo to Agent example transformation +version: 0.1 prefixes: foo: foo -source_schema: s1 -target_schema: s2 + semapv: https://w3id.org/semapv/vocab/ +description: | + This mapping set demonstrates how to transform a person information schema + into a different format using LinkML. +license: https://creativecommons.org/licenses/by/4.0/ +creator: + - id: https://orcid.org/0000-0002-7356-1779 +author: + - id: https://orcid.org/0000-0002-7356-1779 +publication_date: 2025-08-14 +source_schema: https://w3id.org/linkml/map/example/personinfo.yaml +target_schema: https://w3id.org/linkml/map/example/agent.yaml +mapping_method: semapv:ManualMappingCuration class_derivations: Container: populated_from: Container diff --git a/tests/test_datamodel.py b/tests/test_datamodel.py index a6532d3..4e7f3b0 100644 --- a/tests/test_datamodel.py +++ b/tests/test_datamodel.py @@ -10,8 +10,8 @@ def test_datamodel() -> None: tr.load_transformer_specification(PERSONINFO_TR) trs = tr.specification assert trs.model_dump_json() != "" - assert trs.source_schema == "s1" - assert trs.target_schema == "s2" + assert trs.source_schema == "https://w3id.org/linkml/map/example/personinfo.yaml" + assert trs.target_schema == "https://w3id.org/linkml/map/example/agent.yaml" # class derivations class_derivs = {