From 6c3ba13f4dcb6ff714fbc76c4bca596e91116abd Mon Sep 17 00:00:00 2001 From: Michael Fritzsche Date: Mon, 19 Jan 2026 11:00:38 +0100 Subject: [PATCH 01/19] started to add support for deposit step and added useful method for SoftwareMetadata --- src/hermes/commands/__init__.py | 2 +- src/hermes/commands/cli.py | 4 +- src/hermes/commands/deposit/base.py | 41 ++++++---------- src/hermes/commands/deposit/file.py | 9 +--- src/hermes/commands/deposit/invenio.py | 68 ++++++++++++-------------- src/hermes/error.py | 2 +- src/hermes/model/api.py | 21 +++++++- 7 files changed, 72 insertions(+), 75 deletions(-) diff --git a/src/hermes/commands/__init__.py b/src/hermes/commands/__init__.py index 14f77741..278faddf 100644 --- a/src/hermes/commands/__init__.py +++ b/src/hermes/commands/__init__.py @@ -15,5 +15,5 @@ # from hermes.commands.curate.base import HermesCurateCommand from hermes.commands.harvest.base import HermesHarvestCommand # from hermes.commands.process.base import HermesProcessCommand -# from hermes.commands.deposit.base import HermesDepositCommand +from hermes.commands.deposit.base import HermesDepositCommand # from hermes.commands.postprocess.base import HermesPostprocessCommand diff --git a/src/hermes/commands/cli.py b/src/hermes/commands/cli.py index db109a5e..0ec2d1ae 100644 --- a/src/hermes/commands/cli.py +++ b/src/hermes/commands/cli.py @@ -16,7 +16,7 @@ # from hermes.commands import (HermesHelpCommand, HermesVersionCommand, HermesCleanCommand, # HermesHarvestCommand, HermesProcessCommand, HermesCurateCommand, # HermesDepositCommand, HermesPostprocessCommand, HermesInitCommand) -from hermes.commands import HermesHarvestCommand +from hermes.commands import HermesDepositCommand, HermesHarvestCommand from hermes.commands.base import HermesCommand @@ -45,7 +45,7 @@ def main() -> None: HermesHarvestCommand(parser), # HermesProcessCommand(parser), # HermesCurateCommand(parser), - # HermesDepositCommand(parser), + HermesDepositCommand(parser), # HermesPostprocessCommand(parser), ): if command.settings_class is not None: diff --git a/src/hermes/commands/deposit/base.py b/src/hermes/commands/deposit/base.py index 75018579..800c15e9 100644 --- a/src/hermes/commands/deposit/base.py +++ b/src/hermes/commands/deposit/base.py @@ -7,15 +7,13 @@ import abc import argparse -import json -import sys from pydantic import BaseModel from hermes.commands.base import HermesCommand, HermesPlugin -from hermes.model.context import CodeMetaContext -from hermes.model.path import ContextPath -from hermes.model.errors import HermesValidationError +from hermes.model.context_manager import HermesContext +from hermes.model import SoftwareMetadata +from hermes.model.error import HermesValidationError class BaseDepositPlugin(HermesPlugin): @@ -24,16 +22,19 @@ class BaseDepositPlugin(HermesPlugin): TODO: describe workflow... needs refactoring to be less stateful! """ - def __init__(self, command, ctx): - self.command = command - self.ctx = ctx - def __call__(self, command: HermesCommand) -> None: """Initiate the deposition process. This calls a list of additional methods on the class, none of which need to be implemented. """ self.command = command + self.ctx = HermesContext() + + self.ctx.prepare_step("curate") + self.metadata = SoftwareMetadata.load_from_cache(self.ctx, "result") + self.ctx.finalize_step("curate") + + self.ctx.prepare_step("deposit") self.prepare() self.map_metadata() @@ -106,7 +107,7 @@ def publish(self) -> None: pass -class _DepositSettings(BaseModel): +class DepositSettings(BaseModel): """Generic deposition settings.""" target: str = "" @@ -116,7 +117,7 @@ class HermesDepositCommand(HermesCommand): """ Deposit the curated metadata to repositories. """ command_name = "deposit" - settings_class = _DepositSettings + settings_class = DepositSettings def init_command_parser(self, command_parser: argparse.ArgumentParser) -> None: command_parser.add_argument('--file', '-f', nargs=1, action='append', @@ -128,26 +129,12 @@ def __call__(self, args: argparse.Namespace) -> None: self.args = args plugin_name = self.settings.target - ctx = CodeMetaContext() - codemeta_file = ctx.get_cache("curate", ctx.hermes_name) - if not codemeta_file.exists(): - self.log.error("You must run the 'curate' command before deposit") - sys.exit(1) - - codemeta_path = ContextPath("codemeta") - with open(codemeta_file) as codemeta_fh: - ctx.update(codemeta_path, json.load(codemeta_fh)) - try: - plugin_func = self.plugins[plugin_name](self, ctx) - + plugin_func = self.plugins[plugin_name]() + plugin_func(self) except KeyError as e: self.log.error("Plugin '%s' not found.", plugin_name) self.errors.append(e) - - try: - plugin_func(self) - except HermesValidationError as e: self.log.error("Error while executing %s: %s", plugin_name, e) self.errors.append(e) diff --git a/src/hermes/commands/deposit/file.py b/src/hermes/commands/deposit/file.py index 6c5d6419..5ce8d8e0 100644 --- a/src/hermes/commands/deposit/file.py +++ b/src/hermes/commands/deposit/file.py @@ -11,22 +11,17 @@ from pydantic import BaseModel from hermes.commands.deposit.base import BaseDepositPlugin -from hermes.model.path import ContextPath class FileDepositSettings(BaseModel): - filename: str = 'hermes.json' + filename: str = 'codemeta.json' class FileDepositPlugin(BaseDepositPlugin): settings_class = FileDepositSettings - def map_metadata(self) -> None: - self.ctx.update(ContextPath.parse('deposit.file'), self.ctx['codemeta']) - def publish(self) -> None: file_config = self.command.settings.file - output_data = self.ctx['deposit.file'] with open(file_config.filename, 'w') as deposition_file: - json.dump(output_data, deposition_file, indent=2) + json.dump(self.metadata.compact(), deposition_file, indent=2) diff --git a/src/hermes/commands/deposit/invenio.py b/src/hermes/commands/deposit/invenio.py index 69fb87a0..aafe51b7 100644 --- a/src/hermes/commands/deposit/invenio.py +++ b/src/hermes/commands/deposit/invenio.py @@ -17,11 +17,10 @@ import requests from pydantic import BaseModel -from hermes.commands.deposit.base import BaseDepositPlugin, HermesDepositCommand +from hermes.commands.deposit.base import BaseDepositPlugin from hermes.commands.deposit.error import DepositionUnauthorizedError from hermes.error import MisconfigurationError -from hermes.model.context import CodeMetaContext -from hermes.model.path import ContextPath +from hermes.model.context_manager import HermesContext from hermes.utils import hermes_doi, hermes_user_agent @@ -258,11 +257,13 @@ class InvenioDepositPlugin(BaseDepositPlugin): invenio_resolver_class = InvenioResolver settings_class = InvenioDepositSettings - def __init__(self, command: HermesDepositCommand, ctx: CodeMetaContext, client=None, resolver=None) -> None: - super().__init__(command, ctx) + def __init__(self) -> None: + super().__init__() - self.invenio_context_path = ContextPath.parse(f"deposit.{self.platform_name}") self.invenio_ctx = None + + def __call__(self, command, *, client=None, resolver=None): + self.command = command self.config = getattr(self.command.settings, self.platform_name) if client is None: @@ -292,7 +293,9 @@ def __init__(self, command: HermesDepositCommand, ctx: CodeMetaContext, client=N self.resolver = resolver or self.invenio_resolver_class(self.client) self.links = {} - # TODO: Populate some data structure here? Or move more of this into __init__? + super().__call__(command) + + # TODO: Populate some data structure here? Or move more of this into __init__.py? def prepare(self) -> None: """Prepare the deposition on an Invenio-based platform. @@ -305,49 +308,42 @@ def prepare(self) -> None: - check access modalities (access right, access conditions, embargo data, existence of license) - check whether required configuration options are present - - update ``self.ctx`` with metadata collected during the checks + - update ``self.metadata`` with metadata collected during the checks """ rec_id = self.config.record_id doi = self.config.doi - try: - codemeta_identifier = self.ctx["codemeta.identifier"] - except KeyError: - codemeta_identifier = None - + codemeta_identifier = self.metadata.get("identifier", None) rec_id, rec_meta = self.resolver.resolve_latest_id( record_id=rec_id, doi=doi, codemeta_identifier=codemeta_identifier ) - version = self.ctx["codemeta"].get("version") + version = self.metadata["version"] if rec_meta and (version == rec_meta.get("version")): raise ValueError(f"Version {version} already deposited.") - self.ctx.update(self.invenio_context_path['latestRecord'], {'id': rec_id, 'metadata': rec_meta}) - - license = self._get_license_identifier() - self.ctx.update(self.invenio_context_path["license"], license) - - communities = self._get_community_identifiers() - self.ctx.update(self.invenio_context_path["communities"], communities) + deposition_data = {} + deposition_data["latestRecord"] = {'id': rec_id, 'metadata': rec_meta} + deposition_data["license"] = self._get_license_identifier() + deposition_data["communities"] = self._get_community_identifiers() access_right, embargo_date, access_conditions = self._get_access_modalities(license) - self.ctx.update(self.invenio_context_path["access_right"], access_right) - self.ctx.update(self.invenio_context_path["embargo_date"], embargo_date) - self.ctx.update(self.invenio_context_path["access_conditions"], access_conditions) + deposition_data["access_right"] = access_right + deposition_data["embargo_date"] = embargo_date + deposition_data["access_conditions"] = access_conditions - self.invenio_ctx = self.ctx[self.invenio_context_path] + self.invenio_ctx = deposition_data def map_metadata(self) -> None: """Map the harvested metadata onto the Invenio schema.""" deposition_metadata = self._codemeta_to_invenio_deposition() - self.ctx.update(self.invenio_context_path["depositionMetadata"], deposition_metadata) - - # Store a snapshot of the mapped data within the cache, useful for analysis, debugging, etc - with open(self.ctx.get_cache("deposit", self.platform_name, create=True), 'w') as invenio_json: - json.dump(deposition_metadata, invenio_json, indent=' ') + ctx = HermesContext() + ctx.prepare_step("deposit") + with ctx[self.platform_name] as deposit_ctx: + deposit_ctx["deposit"] = deposition_metadata + ctx.finalize_step("deposit") def is_initial_publication(self) -> bool: latest_record_id = self.invenio_ctx.get("latestRecord", {}).get("id") @@ -426,7 +422,7 @@ def update_metadata(self) -> None: self.links.update(deposit["links"]) _log.debug("Created new version deposit: %s", self.links["html"]) - with open(self.ctx.get_cache('deposit', 'deposit', create=True), 'w') as deposit_file: + with open(self.metadata.get_cache('deposit', 'deposit', create=True), 'w') as deposit_file: json.dump(deposit, deposit_file, indent=4) def delete_artifacts(self) -> None: @@ -505,7 +501,7 @@ def _codemeta_to_invenio_deposition(self) -> dict: differences between Invenio-based platforms. """ - metadata = self.ctx["codemeta"] + metadata = self.metadata license = self.invenio_ctx["license"] communities = self.invenio_ctx["communities"] access_right = self.invenio_ctx["access_right"] @@ -520,7 +516,7 @@ def _codemeta_to_invenio_deposition(self) -> dict: "affiliation": author.get("affiliation", {"legalName": None}).get("legalName"), # Invenio wants "family, given". author.get("name") might not have this format. "name": f"{author.get('familyName')}, {author.get('givenName')}" - if author.get("familyName") and author.get("givenName") + if "familyName" in author and "givenName" in author else author.get("name"), # Invenio expects the ORCID without the URL part "orcid": author.get("@id", "").replace("https://orcid.org/", "") or None, @@ -538,7 +534,7 @@ def _codemeta_to_invenio_deposition(self) -> dict: "affiliation": contributor.get("affiliation", {"legalName": None}).get("legalName"), # Invenio wants "family, given". contributor.get("name") might not have this format. "name": f"{contributor.get('familyName')}, {contributor.get('givenName')}" - if contributor.get("familyName") and contributor.get("givenName") + if "familyName" in contributor and "givenName" in contributor else contributor.get("name"), # Invenio expects the ORCID without the URL part "orcid": contributor.get("@id", "").replace("https://orcid.org/", "") or None, @@ -604,7 +600,7 @@ def _get_license_identifier(self) -> t.Optional[str]: If no license is configured, ``None`` will be returned. """ - license_url = self.ctx["codemeta"].get("license") + license_url = self.metadata["license"] return self.resolver.resolve_license_id(license_url) def _get_community_identifiers(self): @@ -612,7 +608,7 @@ def _get_community_identifiers(self): This function gets the communities to be used for the deposition on an Invenio-based site from the config and checks their validity against the site's API. If one of the - identifiers can not be found on the site, a :class:`HermesMisconfigurationError` is + identifiers can not be found on the site, a :class:`MisconfigurationError` is raised. """ diff --git a/src/hermes/error.py b/src/hermes/error.py index e56c2499..1669ed39 100644 --- a/src/hermes/error.py +++ b/src/hermes/error.py @@ -4,5 +4,5 @@ # SPDX-FileContributor: David Pape -class HermesMisconfigurationError(Exception): +class MisconfigurationError(Exception): pass diff --git a/src/hermes/model/api.py b/src/hermes/model/api.py index 8b079544..24f1405e 100644 --- a/src/hermes/model/api.py +++ b/src/hermes/model/api.py @@ -1,6 +1,7 @@ +from hermes.model.context_manager import HermesContext, HermesContexError from hermes.model.types import ld_dict - from hermes.model.types.ld_context import ALL_CONTEXTS +from hermes.model.types.ld_dict import bundled_loader class SoftwareMetadata(ld_dict): @@ -8,3 +9,21 @@ class SoftwareMetadata(ld_dict): def __init__(self, data: dict = None, extra_vocabs: dict[str, str] = None) -> None: ctx = ALL_CONTEXTS + [{**extra_vocabs}] if extra_vocabs is not None else ALL_CONTEXTS super().__init__([ld_dict.from_dict(data, context=ctx).data_dict if data else {}], context=ctx) + + @classmethod + def load_from_cache(cls, ctx: HermesContext, source: str) -> "SoftwareMetadata": + with ctx[source] as cache: + try: + return SoftwareMetadata(cache["codemeta"]) + except Exception: + pass + try: + context = cache["context"]["@context"] + data = SoftwareMetadata() + data.active_ctx = data.ld_proc.initial_ctx(context, {"documentLoader": bundled_loader}) + data.context = context + for key, value in cache["expanded"][0]: + data[key] = value + return data + except Exception as e: + raise HermesContexError("There is no (valid) data stored in the cache.") from e From feeb16b9263849f14a0cfe9b34bfd6ab12b3e7b7 Mon Sep 17 00:00:00 2001 From: notactuallyfinn Date: Fri, 23 Jan 2026 14:18:39 +0100 Subject: [PATCH 02/19] worked on invenio deposit --- src/hermes/commands/deposit/base.py | 29 ++++--- src/hermes/commands/deposit/file.py | 5 +- src/hermes/commands/deposit/invenio.py | 94 ++++++++++++++------- test/hermes_test/model/test_api_e2e.py | 108 +++++++++++++++++++++++++ 4 files changed, 195 insertions(+), 41 deletions(-) diff --git a/src/hermes/commands/deposit/base.py b/src/hermes/commands/deposit/base.py index 800c15e9..4a996eaa 100644 --- a/src/hermes/commands/deposit/base.py +++ b/src/hermes/commands/deposit/base.py @@ -34,17 +34,25 @@ def __call__(self, command: HermesCommand) -> None: self.metadata = SoftwareMetadata.load_from_cache(self.ctx, "result") self.ctx.finalize_step("curate") - self.ctx.prepare_step("deposit") - self.prepare() - self.map_metadata() + deposit = self.map_metadata() + self.ctx.prepare_step("deposit") + with self.ctx[command.settings.target] as cache: + cache["deposit"] = deposit.compact() + self.ctx.finalize_step("deposit") if self.is_initial_publication(): self.create_initial_version() else: self.create_new_version() - self.update_metadata() + deposit = self.update_metadata() + self.ctx.prepare_step("deposit") + with self.ctx[command.settings.target] as cache: + cache["codemeta"] = deposit.compact() + cache["expanded"] = deposit.ld_value + cache["context"] = {"@context": deposit.full_context} + self.ctx.finalize_step("deposit") self.delete_artifacts() self.upload_artifacts() self.publish() @@ -59,8 +67,8 @@ def prepare(self) -> None: pass @abc.abstractmethod - def map_metadata(self) -> None: - """Map the given metadata to the target schema of the deposition platform. + def map_metadata(self) -> SoftwareMetadata: + """Map the given metadata to the target schema of the deposition platform and return it. When mapping metadata, make sure to add traces to the HERMES software, e.g. via DataCite's ``relatedIdentifier`` using the ``isCompiledBy`` relation. Ideally, the value @@ -89,9 +97,9 @@ def create_new_version(self) -> None: """Create a new version of an existing publication on the target platform.""" pass - def update_metadata(self) -> None: - """Update the metadata of the newly created version.""" - pass + def update_metadata(self) -> SoftwareMetadata: + """Update the metadata of the newly created version and return it even if it hasn't changed.""" + return self.metadata def delete_artifacts(self) -> None: """Delete any superfluous artifacts taken from the previous version of the publication.""" @@ -131,10 +139,11 @@ def __call__(self, args: argparse.Namespace) -> None: try: plugin_func = self.plugins[plugin_name]() - plugin_func(self) except KeyError as e: self.log.error("Plugin '%s' not found.", plugin_name) self.errors.append(e) + try: + plugin_func(self) except HermesValidationError as e: self.log.error("Error while executing %s: %s", plugin_name, e) self.errors.append(e) diff --git a/src/hermes/commands/deposit/file.py b/src/hermes/commands/deposit/file.py index 5ce8d8e0..53876c53 100644 --- a/src/hermes/commands/deposit/file.py +++ b/src/hermes/commands/deposit/file.py @@ -11,7 +11,7 @@ from pydantic import BaseModel from hermes.commands.deposit.base import BaseDepositPlugin - +from hermes.model import SoftwareMetadata class FileDepositSettings(BaseModel): filename: str = 'codemeta.json' @@ -20,6 +20,9 @@ class FileDepositSettings(BaseModel): class FileDepositPlugin(BaseDepositPlugin): settings_class = FileDepositSettings + def map_metadata(self) -> SoftwareMetadata: + return self.metadata + def publish(self) -> None: file_config = self.command.settings.file diff --git a/src/hermes/commands/deposit/invenio.py b/src/hermes/commands/deposit/invenio.py index aafe51b7..2fd13f0d 100644 --- a/src/hermes/commands/deposit/invenio.py +++ b/src/hermes/commands/deposit/invenio.py @@ -6,21 +6,21 @@ # SPDX-FileContributor: Oliver Bertuch # SPDX-FileContributor: Michael Meinel -import json import logging import pathlib -import typing as t from datetime import date, datetime from pathlib import Path from urllib.parse import urlparse import requests from pydantic import BaseModel +from typing import Union from hermes.commands.deposit.base import BaseDepositPlugin from hermes.commands.deposit.error import DepositionUnauthorizedError from hermes.error import MisconfigurationError -from hermes.model.context_manager import HermesContext +from hermes.model import SoftwareMetadata +from hermes.model.error import HermesValidationError from hermes.utils import hermes_doi, hermes_user_agent @@ -108,7 +108,7 @@ def __init__(self, client=None): def resolve_latest_id( self, record_id=None, doi=None, codemeta_identifier=None - ) -> t.Tuple[t.Optional[str], dict]: + ) -> tuple[Union[str, None], dict]: """ Using the given metadata parameters, figure out the latest record id. @@ -166,7 +166,7 @@ def resolve_doi(self, doi) -> str: *_, record_id = page_url.path.split('/') return record_id - def resolve_record_id(self, record_id: str) -> t.Tuple[str, dict]: + def resolve_record_id(self, record_id: str) -> tuple[str, dict]: """ Find the latest version of a given record. @@ -185,7 +185,7 @@ def resolve_record_id(self, record_id: str) -> t.Tuple[str, dict]: res_json = res.json() return res_json['id'], res_json['metadata'] - def resolve_license_id(self, license_url: t.Optional[str]) -> t.Optional[str]: + def resolve_license_id(self, license_url: Union[str, None]) -> Union[str, None]: """Get Invenio license representation from CodeMeta. The license to use is extracted from the ``license`` field in the @@ -218,7 +218,7 @@ def resolve_license_id(self, license_url: t.Optional[str]) -> t.Optional[str]: parsed_url = urlparse(license_url) url_path = parsed_url.path.rstrip("/") - license_id = url_path.split("/")[-1] + license_id = str.lower(url_path.split("/")[-1]) response = self.client.get_license(license_id) if response.status_code == 404: @@ -230,7 +230,8 @@ def resolve_license_id(self, license_url: t.Optional[str]) -> t.Optional[str]: @staticmethod def _extract_license_id_from_response(data: dict) -> str: - return data["metadata"]["id"] + # TODO: find correct key, data["metadata"]["id"] did not work for me but data["id"] does + return data["id"] class InvenioDepositSettings(BaseModel): @@ -242,7 +243,7 @@ class InvenioDepositSettings(BaseModel): access_right: str = None embargo_date: str = None access_conditions: str = None - api_paths: t.Dict = {} + api_paths: dict = {} auth_token: str = '' files: list[pathlib.Path] = [] @@ -335,15 +336,10 @@ def prepare(self) -> None: self.invenio_ctx = deposition_data - def map_metadata(self) -> None: - """Map the harvested metadata onto the Invenio schema.""" - - deposition_metadata = self._codemeta_to_invenio_deposition() - ctx = HermesContext() - ctx.prepare_step("deposit") - with ctx[self.platform_name] as deposit_ctx: - deposit_ctx["deposit"] = deposition_metadata - ctx.finalize_step("deposit") + def map_metadata(self) -> SoftwareMetadata: + """Map the harvested metadata onto the Invenio schema and return it.""" + self.invenio_ctx["depositionMetadata"] = self._codemeta_to_invenio_deposition() + return SoftwareMetadata(self.invenio_ctx["depositionMetadata"]) def is_initial_publication(self) -> bool: latest_record_id = self.invenio_ctx.get("latestRecord", {}).get("id") @@ -402,8 +398,8 @@ def related_identifiers(self): }, ] - def update_metadata(self) -> None: - """Update the metadata of a draft.""" + def update_metadata(self) -> SoftwareMetadata: + """Update the metadata of a draft and return it.""" draft_url = self.links["latest_draft"] @@ -422,8 +418,7 @@ def update_metadata(self) -> None: self.links.update(deposit["links"]) _log.debug("Created new version deposit: %s", self.links["html"]) - with open(self.metadata.get_cache('deposit', 'deposit', create=True), 'w') as deposit_file: - json.dump(deposit, deposit_file, indent=4) + return SoftwareMetadata(deposit.get("metadata", {})) def delete_artifacts(self) -> None: """Delete existing file artifacts. @@ -444,7 +439,10 @@ def upload_artifacts(self) -> None: bucket_url = self.links["bucket"] - files = *self.config.files, *[f[0] for f in self.command.args.file] + if self.command.args.file: + files = *self.config.files, *[f[0] for f in self.command.args.file] + else: + files = tuple(*self.config.files) for path_arg in files: path = Path(path_arg) @@ -508,7 +506,22 @@ def _codemeta_to_invenio_deposition(self) -> dict: embargo_date = self.invenio_ctx["embargo_date"] access_conditions = self.invenio_ctx["access_conditions"] - creators = [ + creators = [] + for author in metadata["author"]: + creator = {} + if len(affils := [name for affil in author["affiliation"] for name in affil["legalname"]]) != 0: + creator["affiliation"] = affils + given_names_str = " ".join(author["givenName"]) + names = [f"{family_name}, {given_names_str}" for family_name in author["familyName"]] + names.extend(author["names"]) + if len(names) != 0: + creator["name"] = names + if (id := author.get("@id", None)) is not None: + creator["orcid"] = id.replace("https://orcid.org/", "") + if creator: + creators.append(creator) + + """creators = [ # TODO: Distinguish between @type "Person" and others { k: v for k, v in { @@ -523,7 +536,7 @@ def _codemeta_to_invenio_deposition(self) -> dict: }.items() if v is not None } for author in metadata["author"] - ] + ]""" # This is not used at the moment. See comment below in `deposition_metadata` dict. contributors = [ # noqa: F841 @@ -546,6 +559,27 @@ def _codemeta_to_invenio_deposition(self) -> dict: for contributor in metadata.get("contributor", []) if contributor.get("name") != "GitHub" ] + if len(metadata["name"]) != 1: + _log.error("More than one or zero names for the Software are given.") + raise HermesValidationError("More than one or zerno names for the Software.") + name = metadata["name"][0] + + if len(metadata["schema:description"]) > 1: + _log.error("More than one descriptions of the Software are given.") + raise HermesValidationError("More than one descriptions of the Software are given.") + if len(metadata["schema:description"]) == 1: + description = metadata["schema:description"][0] + else: + description = None + + if len(metadata["schema:version"]) > 1: + _log.error("More than one version of the Software are given.") + raise HermesValidationError("More than one version of the Software are given.") + if len(metadata["schema:version"]) == 1: + version = metadata["schema:version"][0] + else: + version = None + # TODO: Use the fields currently set to `None`. # Some more fields are available but they most likely don't relate to software # publications targeted by hermes. @@ -559,12 +593,12 @@ def _codemeta_to_invenio_deposition(self) -> dict: # TODO: Maybe we want a different date? Then make this configurable. If not, # this can be removed as it defaults to today. "publication_date": date.today().isoformat(), - "title": metadata["name"], + "title": name, "creators": creators, # TODO: Use a real description here. Possible sources could be # `tool.poetry.description` from pyproject.toml or `abstract` from # CITATION.cff. This should then be stored in codemeta description field. - "description": metadata["name"], + "description": description, "access_right": access_right, "license": license, "embargo_date": embargo_date, @@ -590,17 +624,17 @@ def _codemeta_to_invenio_deposition(self) -> dict: "communities": communities, "grants": None, "subjects": None, - "version": metadata.get('version'), + "version": version, }.items() if v is not None} return deposition_metadata - def _get_license_identifier(self) -> t.Optional[str]: + def _get_license_identifier(self) -> Union[str, None]: """Get Invenio license identifier that matches the given license URL. If no license is configured, ``None`` will be returned. """ - license_url = self.metadata["license"] + license_url = self.metadata["license"][0] return self.resolver.resolve_license_id(license_url) def _get_community_identifiers(self): diff --git a/test/hermes_test/model/test_api_e2e.py b/test/hermes_test/model/test_api_e2e.py index f4ec7fd6..1202572e 100644 --- a/test/hermes_test/model/test_api_e2e.py +++ b/test/hermes_test/model/test_api_e2e.py @@ -4,10 +4,21 @@ # SPDX-FileContributor: Michael Fritzsche +import json import pytest import sys from hermes.model import context_manager, SoftwareMetadata from hermes.commands import cli +from pathlib import Path + + +@pytest.fixture +def sandbox_auth(): + path = Path("./../auth.txt") + if not path.exists(): + pytest.skip("Local auth token file does not exist.") + with path.open() as f: + yield f.read() @pytest.mark.parametrize( @@ -353,3 +364,100 @@ def test_codemeta_harvest(tmp_path, monkeypatch, codemeta, res): sys.argv = orig_argv assert result.data_dict == res.data_dict + + +@pytest.mark.parametrize( + "deposit, res", + [ + 2 * ( + SoftwareMetadata({ + "@type": ["http://schema.org/SoftwareSourceCode"], + "http://schema.org/description": [{"@value": "for testing"}], + "http://schema.org/name": [{"@value": "Test"}] + }), + ) + ] +) +def test_file_deposit(tmp_path, monkeypatch, deposit, res): + monkeypatch.chdir(tmp_path) + + manager = context_manager.HermesContext(tmp_path) + manager.prepare_step("curate") + with manager["result"] as cache: + cache["codemeta"] = deposit.compact() + manager.finalize_step("curate") + + config_file = tmp_path / "hermes.toml" + config_file.write_text("[deposit]\ntarget = \"file\"") + + orig_argv = sys.argv[:] + sys.argv = ["hermes", "deposit", "--path", str(tmp_path), "--config", str(config_file)] + result = {} + try: + monkeypatch.setattr(context_manager.HermesContext.__init__, "__defaults__", (tmp_path.cwd(),)) + cli.main() + except SystemExit: + with open('codemeta.json', 'r') as cache: + result = SoftwareMetadata(json.load(cache)) + finally: + sys.argv = orig_argv + + assert result.data_dict == res.data_dict + + +@pytest.mark.parametrize( + "metadata", + [ + SoftwareMetadata({ + "@type": ["http://schema.org/SoftwareSourceCode"], + "http://schema.org/description": [{"@value": "for testing"}], + "http://schema.org/name": [{"@value": "Test"}], + "http://schema.org/author": [{ + "@type": "http://schema.org/Person", + "http://schema.org/familyName": [{"@value": "Test"}], + "http://schema.org/givenName": [{"@value": "Testi"}] + }], + "http://schema.org/license": [{"@id": "https://spdx.org/licenses/apache-2.0"}] + }), + ] +) +def test_invenio_deposit(tmp_path, monkeypatch, sandbox_auth, metadata): + monkeypatch.chdir(tmp_path) + + manager = context_manager.HermesContext(tmp_path) + manager.prepare_step("curate") + with manager["result"] as cache: + cache["codemeta"] = metadata.compact() + manager.finalize_step("curate") + + config_file = tmp_path / "hermes.toml" + config_file.write_text(f"""[deposit] +target = \"invenio\" +[deposit.invenio] +site_url = \"https://sandbox.zenodo.org\" +access_right = \"closed\" +auth_token = \"{sandbox_auth}\" +file = [] +[deposit.invenio.api_paths] +licenses = "api/vocabularies/licenses" +""") + + orig_argv = sys.argv[:] + sys.argv = ["hermes", "deposit", "--path", str(tmp_path), "--config", str(config_file), "--initial"] + result = {} + try: + monkeypatch.setattr(context_manager.HermesContext.__init__, "__defaults__", (tmp_path.cwd(),)) + cli.main() + except SystemExit: + manager.prepare_step("deposit") + result = SoftwareMetadata.load_from_cache(manager, "invenio") + manager.finalize_step("deposit") + finally: + sys.argv = orig_argv + + assert result.data_dict == metadata.data_dict + +# TODO: +# - handle get() on Softwaremetadata objects in invenio.py +# - Sophie genaueres bezüglich Zeiten für Arbeitszeiterhöhung und -zeitraumerweiterung schicken + From ed0916baa4b9c75983ad3ced5bf9da200b20d0ff Mon Sep 17 00:00:00 2001 From: notactuallyfinn Date: Mon, 26 Jan 2026 10:12:20 +0100 Subject: [PATCH 03/19] fixed bugs in invenio.py --- src/hermes/commands/deposit/invenio.py | 19 +++++++++++++------ test/hermes_test/model/test_api_e2e.py | 22 ++++++++++------------ 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/src/hermes/commands/deposit/invenio.py b/src/hermes/commands/deposit/invenio.py index 2fd13f0d..01211e5a 100644 --- a/src/hermes/commands/deposit/invenio.py +++ b/src/hermes/commands/deposit/invenio.py @@ -442,7 +442,8 @@ def upload_artifacts(self) -> None: if self.command.args.file: files = *self.config.files, *[f[0] for f in self.command.args.file] else: - files = tuple(*self.config.files) + files = tuple(self.config.files) + for path_arg in files: path = Path(path_arg) @@ -511,11 +512,17 @@ def _codemeta_to_invenio_deposition(self) -> dict: creator = {} if len(affils := [name for affil in author["affiliation"] for name in affil["legalname"]]) != 0: creator["affiliation"] = affils - given_names_str = " ".join(author["givenName"]) - names = [f"{family_name}, {given_names_str}" for family_name in author["familyName"]] - names.extend(author["names"]) - if len(names) != 0: - creator["name"] = names + if len(author["familyName"]) > 1: + raise HermesValidationError(f"Author has too many family names: {author.to_python()}") + if len(author["familyName"]) == 1: + given_names_str = " ".join(author["givenName"]) + name = f"{author["familyName"][0]}, {given_names_str}" + elif len(author["name"]) != 1: + raise HermesValidationError(f"Author has too many names: {author.to_python()}") + else: + name = author["name"][0] + if len(name) != 0: + creator["name"] = name if (id := author.get("@id", None)) is not None: creator["orcid"] = id.replace("https://orcid.org/", "") if creator: diff --git a/test/hermes_test/model/test_api_e2e.py b/test/hermes_test/model/test_api_e2e.py index 1202572e..fa8f4ac8 100644 --- a/test/hermes_test/model/test_api_e2e.py +++ b/test/hermes_test/model/test_api_e2e.py @@ -205,7 +205,7 @@ def test_cff_harvest(tmp_path, monkeypatch, cff, res): # FIXME: update to compare the SoftwareMetadata objects instead of the data_dicts (in multiple places) # after merge with refactor/data-model and/or refactor/423-implement-public-api - assert result.data_dict == res.data_dict + assert result == res @pytest.mark.parametrize( @@ -363,7 +363,7 @@ def test_codemeta_harvest(tmp_path, monkeypatch, codemeta, res): finally: sys.argv = orig_argv - assert result.data_dict == res.data_dict + assert result == res @pytest.mark.parametrize( @@ -402,7 +402,7 @@ def test_file_deposit(tmp_path, monkeypatch, deposit, res): finally: sys.argv = orig_argv - assert result.data_dict == res.data_dict + assert result == res @pytest.mark.parametrize( @@ -432,12 +432,12 @@ def test_invenio_deposit(tmp_path, monkeypatch, sandbox_auth, metadata): config_file = tmp_path / "hermes.toml" config_file.write_text(f"""[deposit] -target = \"invenio\" +target = "invenio" [deposit.invenio] -site_url = \"https://sandbox.zenodo.org\" -access_right = \"closed\" -auth_token = \"{sandbox_auth}\" -file = [] +site_url = "https://sandbox.zenodo.org" +access_right = "closed" +auth_token = "{sandbox_auth}" +files = ["hermes.toml"] [deposit.invenio.api_paths] licenses = "api/vocabularies/licenses" """) @@ -455,9 +455,7 @@ def test_invenio_deposit(tmp_path, monkeypatch, sandbox_auth, metadata): finally: sys.argv = orig_argv - assert result.data_dict == metadata.data_dict + assert result == metadata -# TODO: -# - handle get() on Softwaremetadata objects in invenio.py -# - Sophie genaueres bezüglich Zeiten für Arbeitszeiterhöhung und -zeitraumerweiterung schicken +# TODO: handle get() on Softwaremetadata objects in invenio.py From 382e2c3e3f55c95bf1a9908208cea061eaf7b17e Mon Sep 17 00:00:00 2001 From: notactuallyfinn Date: Fri, 30 Jan 2026 14:07:09 +0100 Subject: [PATCH 04/19] fixed bug and adjusted tests --- src/hermes/commands/deposit/base.py | 15 ++--- src/hermes/commands/deposit/file.py | 9 ++- src/hermes/commands/deposit/invenio.py | 68 ++++++++++++-------- src/hermes/model/types/ld_dict.py | 27 ++++---- test/hermes_test/model/test_api.py | 20 +++--- test/hermes_test/model/test_api_e2e.py | 61 +++++++++--------- test/hermes_test/model/types/test_ld_dict.py | 19 ++++++ 7 files changed, 129 insertions(+), 90 deletions(-) diff --git a/src/hermes/commands/deposit/base.py b/src/hermes/commands/deposit/base.py index 4a996eaa..6fbf3625 100644 --- a/src/hermes/commands/deposit/base.py +++ b/src/hermes/commands/deposit/base.py @@ -38,7 +38,7 @@ def __call__(self, command: HermesCommand) -> None: deposit = self.map_metadata() self.ctx.prepare_step("deposit") with self.ctx[command.settings.target] as cache: - cache["deposit"] = deposit.compact() + cache["deposit"] = deposit self.ctx.finalize_step("deposit") if self.is_initial_publication(): @@ -48,10 +48,8 @@ def __call__(self, command: HermesCommand) -> None: deposit = self.update_metadata() self.ctx.prepare_step("deposit") - with self.ctx[command.settings.target] as cache: - cache["codemeta"] = deposit.compact() - cache["expanded"] = deposit.ld_value - cache["context"] = {"@context": deposit.full_context} + with self.ctx["deposit"] as cache: + cache["result"] = deposit self.ctx.finalize_step("deposit") self.delete_artifacts() self.upload_artifacts() @@ -67,7 +65,7 @@ def prepare(self) -> None: pass @abc.abstractmethod - def map_metadata(self) -> SoftwareMetadata: + def map_metadata(self) -> dict: """Map the given metadata to the target schema of the deposition platform and return it. When mapping metadata, make sure to add traces to the HERMES software, e.g. via @@ -97,9 +95,10 @@ def create_new_version(self) -> None: """Create a new version of an existing publication on the target platform.""" pass - def update_metadata(self) -> SoftwareMetadata: + @abc.abstractmethod + def update_metadata(self) -> dict: """Update the metadata of the newly created version and return it even if it hasn't changed.""" - return self.metadata + pass def delete_artifacts(self) -> None: """Delete any superfluous artifacts taken from the previous version of the publication.""" diff --git a/src/hermes/commands/deposit/file.py b/src/hermes/commands/deposit/file.py index 53876c53..ed6bd570 100644 --- a/src/hermes/commands/deposit/file.py +++ b/src/hermes/commands/deposit/file.py @@ -11,7 +11,7 @@ from pydantic import BaseModel from hermes.commands.deposit.base import BaseDepositPlugin -from hermes.model import SoftwareMetadata + class FileDepositSettings(BaseModel): filename: str = 'codemeta.json' @@ -20,8 +20,11 @@ class FileDepositSettings(BaseModel): class FileDepositPlugin(BaseDepositPlugin): settings_class = FileDepositSettings - def map_metadata(self) -> SoftwareMetadata: - return self.metadata + def map_metadata(self) -> dict: + return self.metadata.compact() + + def update_metadata(self) -> dict: + return self.metadata.compact() def publish(self) -> None: file_config = self.command.settings.file diff --git a/src/hermes/commands/deposit/invenio.py b/src/hermes/commands/deposit/invenio.py index 01211e5a..9434beca 100644 --- a/src/hermes/commands/deposit/invenio.py +++ b/src/hermes/commands/deposit/invenio.py @@ -19,7 +19,6 @@ from hermes.commands.deposit.base import BaseDepositPlugin from hermes.commands.deposit.error import DepositionUnauthorizedError from hermes.error import MisconfigurationError -from hermes.model import SoftwareMetadata from hermes.model.error import HermesValidationError from hermes.utils import hermes_doi, hermes_user_agent @@ -320,7 +319,12 @@ def prepare(self) -> None: record_id=rec_id, doi=doi, codemeta_identifier=codemeta_identifier ) - version = self.metadata["version"] + if len(self.metadata.get("version", [])) > 1: + raise HermesValidationError("Too many licenses for invenio deposit.") + if len(self.metadata.get("version", [])) == 1: + version = self.metadata["version"][0] + else: + version = None if rec_meta and (version == rec_meta.get("version")): raise ValueError(f"Version {version} already deposited.") @@ -336,10 +340,10 @@ def prepare(self) -> None: self.invenio_ctx = deposition_data - def map_metadata(self) -> SoftwareMetadata: + def map_metadata(self) -> dict: """Map the harvested metadata onto the Invenio schema and return it.""" self.invenio_ctx["depositionMetadata"] = self._codemeta_to_invenio_deposition() - return SoftwareMetadata(self.invenio_ctx["depositionMetadata"]) + return self.invenio_ctx["depositionMetadata"] def is_initial_publication(self) -> bool: latest_record_id = self.invenio_ctx.get("latestRecord", {}).get("id") @@ -398,7 +402,7 @@ def related_identifiers(self): }, ] - def update_metadata(self) -> SoftwareMetadata: + def update_metadata(self) -> dict: """Update the metadata of a draft and return it.""" draft_url = self.links["latest_draft"] @@ -418,7 +422,7 @@ def update_metadata(self) -> SoftwareMetadata: self.links.update(deposit["links"]) _log.debug("Created new version deposit: %s", self.links["html"]) - return SoftwareMetadata(deposit.get("metadata", {})) + return deposit def delete_artifacts(self) -> None: """Delete existing file artifacts. @@ -508,21 +512,25 @@ def _codemeta_to_invenio_deposition(self) -> dict: access_conditions = self.invenio_ctx["access_conditions"] creators = [] - for author in metadata["author"]: + for author in metadata.get("author", []): creator = {} - if len(affils := [name for affil in author["affiliation"] for name in affil["legalname"]]) != 0: + if len( + affils := [ + name for affil in author.get("affiliation", []) for name in affil.get("legalname", []) + ] + ) != 0: creator["affiliation"] = affils - if len(author["familyName"]) > 1: - raise HermesValidationError(f"Author has too many family names: {author.to_python()}") - if len(author["familyName"]) == 1: - given_names_str = " ".join(author["givenName"]) + + if len(author.get("familyName", [])) > 1: + raise HermesValidationError(f"Author has too many family names: {author}") + if len(author.get("familyName", [])) == 1: + given_names_str = " ".join(author.get("givenName", [])) name = f"{author["familyName"][0]}, {given_names_str}" - elif len(author["name"]) != 1: - raise HermesValidationError(f"Author has too many names: {author.to_python()}") + elif len(author.get("name", [])) != 1: + raise HermesValidationError(f"Author has too many or no names: {author}") else: name = author["name"][0] - if len(name) != 0: - creator["name"] = name + creator["name"] = name if (id := author.get("@id", None)) is not None: creator["orcid"] = id.replace("https://orcid.org/", "") if creator: @@ -545,6 +553,7 @@ def _codemeta_to_invenio_deposition(self) -> dict: for author in metadata["author"] ]""" + # TODO: reimplement with new api # This is not used at the moment. See comment below in `deposition_metadata` dict. contributors = [ # noqa: F841 # TODO: Distinguish between @type "Person" and others @@ -566,27 +575,33 @@ def _codemeta_to_invenio_deposition(self) -> dict: for contributor in metadata.get("contributor", []) if contributor.get("name") != "GitHub" ] - if len(metadata["name"]) != 1: + if len(metadata.get("name", [])) != 1: _log.error("More than one or zero names for the Software are given.") raise HermesValidationError("More than one or zerno names for the Software.") name = metadata["name"][0] - if len(metadata["schema:description"]) > 1: + if len(metadata.get("schema:description", [])) > 1: _log.error("More than one descriptions of the Software are given.") raise HermesValidationError("More than one descriptions of the Software are given.") - if len(metadata["schema:description"]) == 1: + if len(metadata.get("schema:description", [])) == 1: description = metadata["schema:description"][0] else: description = None - if len(metadata["schema:version"]) > 1: + if len(metadata.get("schema:version", [])) > 1: _log.error("More than one version of the Software are given.") raise HermesValidationError("More than one version of the Software are given.") - if len(metadata["schema:version"]) == 1: + if len(metadata.get("schema:version", [])) == 1: version = metadata["schema:version"][0] else: version = None + keywords = metadata.get("schema:keywords", []) + if len(keywords) == 0: + keywords = None + else: + keywords = keywords.to_python() + # TODO: Use the fields currently set to `None`. # Some more fields are available but they most likely don't relate to software # publications targeted by hermes. @@ -602,9 +617,6 @@ def _codemeta_to_invenio_deposition(self) -> dict: "publication_date": date.today().isoformat(), "title": name, "creators": creators, - # TODO: Use a real description here. Possible sources could be - # `tool.poetry.description` from pyproject.toml or `abstract` from - # CITATION.cff. This should then be stored in codemeta description field. "description": description, "access_right": access_right, "license": license, @@ -618,8 +630,8 @@ def _codemeta_to_invenio_deposition(self) -> dict: # them. # TODO: Use the DOI we get back from this. "prereserve_doi": True, - # TODO: A good source for this could be `tool.poetry.keywords` in pyproject.toml. - "keywords": None, + "keywords": keywords, + # TODO: Is there a good codemeta/ schema field? "notes": None, "related_identifiers": self.related_identifiers(), # TODO: Use `contributors`. In the case of the hermes workflow itself, the @@ -641,6 +653,10 @@ def _get_license_identifier(self) -> Union[str, None]: If no license is configured, ``None`` will be returned. """ + if "license" not in self.metadata: + raise HermesValidationError("No license is given.") + if len(self.metadata["license"]) > 1: + raise HermesValidationError("Too many licenses for invenio deposit.") license_url = self.metadata["license"][0] return self.resolver.resolve_license_id(license_url) diff --git a/src/hermes/model/types/ld_dict.py b/src/hermes/model/types/ld_dict.py index 8311b67f..f368ec73 100644 --- a/src/hermes/model/types/ld_dict.py +++ b/src/hermes/model/types/ld_dict.py @@ -22,14 +22,7 @@ def __init__(self, data, *, parent=None, key=None, index=None, context=None): def __getitem__(self, key): full_iri = self.ld_proc.expand_iri(self.active_ctx, key) - if full_iri == "@id": - return self._to_python(full_iri, self.data_dict[full_iri]) - try: - ld_value = self.data_dict[full_iri] - except KeyError: - self[key] = [] - ld_value = self.data_dict[full_iri] - return self._to_python(full_iri, ld_value) + return self._to_python(full_iri, self.data_dict[full_iri]) def __setitem__(self, key, value): ld_value = self._to_expanded_json({key: value}) @@ -41,12 +34,7 @@ def __delitem__(self, key): def __contains__(self, key): full_iri = self.ld_proc.expand_iri(self.active_ctx, key) - if full_iri == "@id": - return "@id" in self.data_dict - try: - return len(self[full_iri]) != 0 - except KeyError: - return False + return full_iri in self.data_dict def __eq__(self, other): if not isinstance(other, (dict, ld_dict)): @@ -89,6 +77,15 @@ def get(self, key, default=_NO_DEFAULT): return default return self[key] + def setdefault(self, key, default): + if key not in self: + self[key] = default + return self[key] + + def emplace(self, key): + if key not in self: + self[key] = [] + def update(self, other): for key, value in other.items(): self[key] = value @@ -136,7 +133,7 @@ def from_dict(cls, value, *, parent=None, key=None, context=None, ld_type=None): full_context = parent.full_context + merged_contexts ld_value = cls.ld_proc.expand(ld_data, {"expandContext": full_context, "documentLoader": bundled_loader}) - ld_value = cls(ld_value, parent=parent, key=key, context=merged_contexts) + ld_value = ld_dict(ld_value, parent=parent, key=key, context=merged_contexts) return ld_value diff --git a/test/hermes_test/model/test_api.py b/test/hermes_test/model/test_api.py index 6845a210..895968d7 100644 --- a/test/hermes_test/model/test_api.py +++ b/test/hermes_test/model/test_api.py @@ -53,16 +53,18 @@ def test_init_nested_object(): def test_append(): data = SoftwareMetadata() + data.emplace("schema:name") data["schema:name"].append("a") assert type(data["schema:name"]) is ld_list assert data["schema:name"][0] == "a" and data["schema:name"].item_list == [{"@value": "a"}] data["schema:name"].append("b") assert type(data["schema:name"]) is ld_list and data["schema:name"].item_list == [{"@value": "a"}, {"@value": "b"}] + data.emplace("schema:name") data["schema:name"].append("c") assert data["schema:name"].item_list == [{"@value": "a"}, {"@value": "b"}, {"@value": "c"}] data = SoftwareMetadata() - data["schema:Person"].append({"schema:name": "foo"}) + data.setdefault("schema:Person", []).append({"schema:name": "foo"}) assert type(data["schema:Person"]) is ld_list and type(data["schema:Person"][0]) is ld_dict assert data["schema:Person"][0].data_dict == {"http://schema.org/name": [{"@value": "foo"}]} data["schema:Person"].append({"schema:name": "foo"}) @@ -94,7 +96,7 @@ def test_usage(): data["author"][0]["email"].append("foo@baz.com") assert len(data["author"]) == 2 assert len(data["author"][0]["email"]) == 2 - assert len(data["author"][1]["email"]) == 0 + assert len(data["author"][1].get("email", [])) == 0 harvest = { "authors": [ {"name": "Foo", "affiliation": ["Uni A", "Lab B"], "kw": ["a", "b", "c"]}, @@ -103,17 +105,19 @@ def test_usage(): ] } for author in harvest["authors"]: - for exist_author in data["author"]: - if author["name"] == exist_author["name"][0]: + for exist_author in data.get("author", []): + if author["name"] in exist_author.get("name", []): exist_author["affiliation"] = author["affiliation"] if "email" in author: + exist_author.emplace("email") exist_author["email"].append(author["email"]) if "kw" in author: + exist_author.emplace("schema:knowsAbout") exist_author["schema:knowsAbout"].extend(author["kw"]) break else: - data["author"].append(author) - assert len(data["author"]) == 3 + data.setdefault("author", []).append(author) + assert len(data.get("author", [])) == 3 foo, bar, baz = data["author"] assert foo["name"][0] == "Foo" assert foo["affiliation"].to_python() == ["Uni A", "Lab B"] @@ -124,8 +128,8 @@ def test_usage(): assert bar["email"].to_python() == ["bar@c.edu"] assert baz["name"][0] == "Baz" assert baz["affiliation"].to_python() == ["Lab E"] - assert len(baz["schema:knowsAbout"]) == 0 - assert len(baz["email"]) == 0 + assert len(baz.get("schema:knowsAbout", [])) == 0 + assert len(baz.get("email", [])) == 0 for author in data["author"]: assert "name" in author if "Baz" not in author["name"]: diff --git a/test/hermes_test/model/test_api_e2e.py b/test/hermes_test/model/test_api_e2e.py index fa8f4ac8..16302000 100644 --- a/test/hermes_test/model/test_api_e2e.py +++ b/test/hermes_test/model/test_api_e2e.py @@ -194,17 +194,16 @@ def test_cff_harvest(tmp_path, monkeypatch, cff, res): try: monkeypatch.setattr(context_manager.HermesContext.__init__, "__defaults__", (tmp_path.cwd(),)) cli.main() - except SystemExit: + except SystemExit as e: + if e.code != 0: + raise e + finally: manager = context_manager.HermesContext() manager.prepare_step("harvest") - with manager["cff"] as cache: - result = SoftwareMetadata(cache["codemeta"]) + result = SoftwareMetadata.load_from_cache(manager, "cff") manager.finalize_step("harvest") - finally: sys.argv = orig_argv - # FIXME: update to compare the SoftwareMetadata objects instead of the data_dicts (in multiple places) - # after merge with refactor/data-model and/or refactor/423-implement-public-api assert result == res @@ -354,37 +353,36 @@ def test_codemeta_harvest(tmp_path, monkeypatch, codemeta, res): try: monkeypatch.setattr(context_manager.HermesContext.__init__, "__defaults__", (tmp_path.cwd(),)) cli.main() - except SystemExit: + except SystemExit as e: + if e.code != 0: + raise e + finally: manager = context_manager.HermesContext() manager.prepare_step("harvest") - with manager["codemeta"] as cache: - result = SoftwareMetadata(cache["codemeta"]) + result = SoftwareMetadata.load_from_cache(manager, "codemeta") manager.finalize_step("harvest") - finally: sys.argv = orig_argv assert result == res @pytest.mark.parametrize( - "deposit, res", + "metadata", [ - 2 * ( - SoftwareMetadata({ - "@type": ["http://schema.org/SoftwareSourceCode"], - "http://schema.org/description": [{"@value": "for testing"}], - "http://schema.org/name": [{"@value": "Test"}] - }), - ) + SoftwareMetadata({ + "@type": ["http://schema.org/SoftwareSourceCode"], + "http://schema.org/description": [{"@value": "for testing"}], + "http://schema.org/name": [{"@value": "Test"}] + }), ] ) -def test_file_deposit(tmp_path, monkeypatch, deposit, res): +def test_file_deposit(tmp_path, monkeypatch, metadata): monkeypatch.chdir(tmp_path) manager = context_manager.HermesContext(tmp_path) manager.prepare_step("curate") with manager["result"] as cache: - cache["codemeta"] = deposit.compact() + cache["codemeta"] = metadata.compact() manager.finalize_step("curate") config_file = tmp_path / "hermes.toml" @@ -396,13 +394,15 @@ def test_file_deposit(tmp_path, monkeypatch, deposit, res): try: monkeypatch.setattr(context_manager.HermesContext.__init__, "__defaults__", (tmp_path.cwd(),)) cli.main() - except SystemExit: + except SystemExit as e: + if e.code != 0: + raise e + finally: with open('codemeta.json', 'r') as cache: result = SoftwareMetadata(json.load(cache)) - finally: sys.argv = orig_argv - assert result == res + assert result == metadata @pytest.mark.parametrize( @@ -448,14 +448,15 @@ def test_invenio_deposit(tmp_path, monkeypatch, sandbox_auth, metadata): try: monkeypatch.setattr(context_manager.HermesContext.__init__, "__defaults__", (tmp_path.cwd(),)) cli.main() - except SystemExit: + except SystemExit as e: + if e.code != 0: + raise e + finally: manager.prepare_step("deposit") - result = SoftwareMetadata.load_from_cache(manager, "invenio") + with manager["deposit"] as cache: + result = cache["result"] manager.finalize_step("deposit") - finally: sys.argv = orig_argv - assert result == metadata - -# TODO: handle get() on Softwaremetadata objects in invenio.py - + # TODO: compare to actually expected value + assert result == {} diff --git a/test/hermes_test/model/types/test_ld_dict.py b/test/hermes_test/model/types/test_ld_dict.py index c7a7a183..8736439d 100644 --- a/test/hermes_test/model/types/test_ld_dict.py +++ b/test/hermes_test/model/types/test_ld_dict.py @@ -197,6 +197,25 @@ def test_get(): di["bar"] +def test_setdefault(): + di = ld_dict([{"https://schema.org/name": [{"@value": "Manu Sporny"}]}], + context=[{"schema": "https://schema.org/"}]) + assert di.setdefault("schema:name", []) == [{"@value": "Manu Sporny"}] + assert di.setdefault("schema:email", []) == [] + assert di["schema:email"] == [] + + +def test_emplace(): + di = ld_dict([{"https://schema.org/name": [{"@value": "Manu Sporny"}]}], + context=[{"schema": "https://schema.org/"}]) + di.emplace("schema:name") + assert di["schema:name"] == [{"@value": "Manu Sporny"}] + with pytest.raises(KeyError): + di["schema:email"] + di.emplace("schema:email") + assert di["schema:email"] == [] + + def test_update(): di = ld_dict([{"http://xmlns.com/foaf/0.1/name": [{"@value": "Manu Sporny"}], "http://xmlns.com/foaf/0.1/homepage": [{"@id": "http://manu.sporny.org/"}]}], From 96861ec750f8ef4553a34c062e2b9604b021ff32 Mon Sep 17 00:00:00 2001 From: notactuallyfinn Date: Mon, 2 Feb 2026 10:45:50 +0100 Subject: [PATCH 05/19] adjusted invenio.py and its test a bit --- src/hermes/commands/deposit/invenio.py | 2 + src/hermes/commands/deposit/invenio_rdm.py | 14 ++++-- test/hermes_test/model/test_api_e2e.py | 51 ++++++++++++++-------- 3 files changed, 45 insertions(+), 22 deletions(-) diff --git a/src/hermes/commands/deposit/invenio.py b/src/hermes/commands/deposit/invenio.py index 9434beca..3915d536 100644 --- a/src/hermes/commands/deposit/invenio.py +++ b/src/hermes/commands/deposit/invenio.py @@ -513,6 +513,8 @@ def _codemeta_to_invenio_deposition(self) -> dict: creators = [] for author in metadata.get("author", []): + if not "Person" in author.get("@type", []): + continue creator = {} if len( affils := [ diff --git a/src/hermes/commands/deposit/invenio_rdm.py b/src/hermes/commands/deposit/invenio_rdm.py index a381db90..01e08371 100644 --- a/src/hermes/commands/deposit/invenio_rdm.py +++ b/src/hermes/commands/deposit/invenio_rdm.py @@ -6,9 +6,8 @@ # SPDX-FileContributor: Oliver Bertuch # SPDX-FileContributor: Michael Meinel -import typing as t - from requests import HTTPError +from typing import Union from hermes.commands.deposit.invenio import InvenioClient, InvenioDepositPlugin, InvenioResolver @@ -27,7 +26,7 @@ def get_licenses(self): class InvenioRDMResolver(InvenioResolver): invenio_client_class = InvenioRDMClient - def resolve_license_id(self, license_url: t.Optional[str]) -> t.Optional[dict]: + def resolve_license_id(self, license_url: Union[str, None]) -> Union[dict, None]: """Deliberately try to resolve the license URL to a valid InvenioRDM license information record from the vocabulary. @@ -47,6 +46,12 @@ def resolve_license_id(self, license_url: t.Optional[str]) -> t.Optional[dict]: except HTTPError: pass + # FIXME: Why not get all license_cross_refs and then use a query parameter like this: + # ?q=props.url:("license_url" OR "license_cross_ref[1]" OR ...)&size=1000 + # That would be able to replace _search_license_info. + # FIXME: Some licenses in valid_licenses["hits"]["hits"]["props"]["url"] are only http although + # https://spdx.org/licenses/license.json lists them in crossRef as https + # If the easy "mapping" did not work, we really need to "search" for the correct license ID. response = self.client.get_licenses() response.raise_for_status() @@ -65,6 +70,7 @@ def resolve_license_id(self, license_url: t.Optional[str]) -> t.Optional[dict]: if license_info is not None: break else: + # FIXME: Why is this only raised here and not always when license_info is None? raise RuntimeError(f"Could not resolve license URL {license_url} to a valid identifier.") return license_info @@ -73,7 +79,7 @@ def resolve_license_id(self, license_url: t.Optional[str]) -> t.Optional[dict]: def _extract_license_id_from_response(data: dict) -> str: return data["id"] - def _search_license_info(self, _url: str, valid_licenses: dict) -> t.Optional[dict]: + def _search_license_info(self, _url: str, valid_licenses: dict) -> Union[dict, None]: for license_info in valid_licenses['hits']['hits']: try: if license_info['props']['url'] == _url: diff --git a/test/hermes_test/model/test_api_e2e.py b/test/hermes_test/model/test_api_e2e.py index 16302000..18dc973c 100644 --- a/test/hermes_test/model/test_api_e2e.py +++ b/test/hermes_test/model/test_api_e2e.py @@ -172,7 +172,7 @@ def sandbox_auth(): "http://schema.org/license": [{"@id": "https://spdx.org/licenses/Apache-2.0"}], "http://schema.org/name": [{"@value": "Test"}], "http://schema.org/url": [ - {"@id": 'https://arxiv.org/abs/2201.09015'}, + {"@id": "https://arxiv.org/abs/2201.09015"}, {"@id": "https://docs.software-metadata.pub/en/latest"} ], "http://schema.org/version": [{"@value": "9.0.1"}] @@ -398,7 +398,7 @@ def test_file_deposit(tmp_path, monkeypatch, metadata): if e.code != 0: raise e finally: - with open('codemeta.json', 'r') as cache: + with open("codemeta.json", "r") as cache: result = SoftwareMetadata(json.load(cache)) sys.argv = orig_argv @@ -406,22 +406,37 @@ def test_file_deposit(tmp_path, monkeypatch, metadata): @pytest.mark.parametrize( - "metadata", + "metadata, invenio_metadata", [ - SoftwareMetadata({ - "@type": ["http://schema.org/SoftwareSourceCode"], - "http://schema.org/description": [{"@value": "for testing"}], - "http://schema.org/name": [{"@value": "Test"}], - "http://schema.org/author": [{ - "@type": "http://schema.org/Person", - "http://schema.org/familyName": [{"@value": "Test"}], - "http://schema.org/givenName": [{"@value": "Testi"}] - }], - "http://schema.org/license": [{"@id": "https://spdx.org/licenses/apache-2.0"}] - }), + ( + SoftwareMetadata({ + "@type": ["http://schema.org/SoftwareSourceCode"], + "http://schema.org/description": [{"@value": "for testing"}], + "http://schema.org/name": [{"@value": "Test"}], + "http://schema.org/author": [{ + "@type": "http://schema.org/Person", + "http://schema.org/familyName": [{"@value": "Test"}], + "http://schema.org/givenName": [{"@value": "Testi"}] + }], + "http://schema.org/license": [{"@id": "https://spdx.org/licenses/Apache-2.0"}] + }), + { + "upload_type": "software", + "publication_date": "2026-02-02", + "title": "Test", + "creators": [{"name": "Test, Testi"}], + "description": "for testing", + "access_right": "closed", + "license": "apache-2.0", + "prereserve_doi": True, + "related_identifiers": [ + {"identifier": "10.5281/zenodo.13311079", "relation": "isCompiledBy", "scheme": "doi"} + ] + } + ) ] ) -def test_invenio_deposit(tmp_path, monkeypatch, sandbox_auth, metadata): +def test_invenio_deposit(tmp_path, monkeypatch, sandbox_auth, metadata, invenio_metadata): monkeypatch.chdir(tmp_path) manager = context_manager.HermesContext(tmp_path) @@ -453,10 +468,10 @@ def test_invenio_deposit(tmp_path, monkeypatch, sandbox_auth, metadata): raise e finally: manager.prepare_step("deposit") - with manager["deposit"] as cache: - result = cache["result"] + with manager["invenio"] as cache: + result = cache["deposit"] manager.finalize_step("deposit") sys.argv = orig_argv # TODO: compare to actually expected value - assert result == {} + assert result == invenio_metadata From 248ae33b8f094c361a8280b83241fc780f4629f7 Mon Sep 17 00:00:00 2001 From: notactuallyfinn Date: Fri, 6 Feb 2026 13:12:19 +0100 Subject: [PATCH 06/19] added adjusted files from feature/153-refactor-datamodel for process --- src/hermes/commands/process/base.py | 51 ++++------- src/hermes/model/merge/__init__.py | 3 + src/hermes/model/merge/action.py | 83 ++++++++++++++++++ src/hermes/model/merge/container.py | 116 +++++++++++++++++++++++++ src/hermes/model/merge/match.py | 17 ++++ src/hermes/model/merge/strategy.py | 42 +++++++++ src/hermes/model/types/ld_container.py | 6 +- 7 files changed, 279 insertions(+), 39 deletions(-) create mode 100644 src/hermes/model/merge/__init__.py create mode 100644 src/hermes/model/merge/action.py create mode 100644 src/hermes/model/merge/container.py create mode 100644 src/hermes/model/merge/match.py create mode 100644 src/hermes/model/merge/strategy.py diff --git a/src/hermes/commands/process/base.py b/src/hermes/commands/process/base.py index 9e29d1e6..83480056 100644 --- a/src/hermes/commands/process/base.py +++ b/src/hermes/commands/process/base.py @@ -5,13 +5,13 @@ # SPDX-FileContributor: Michael Meinel import argparse -import json -import sys from pydantic import BaseModel from hermes.commands.base import HermesCommand, HermesPlugin -from hermes.model.context import HermesHarvestContext, CodeMetaContext +from hermes.model.api import SoftwareMetadata +from hermes.model.context_manager import HermesContext +from hermes.model.merge.container import ld_merge_dict class HermesProcessPlugin(HermesPlugin): @@ -33,42 +33,21 @@ class HermesProcessCommand(HermesCommand): def __call__(self, args: argparse.Namespace) -> None: self.args = args - ctx = CodeMetaContext() - - if not (ctx.hermes_dir / "harvest").exists(): - self.log.error("You must run the harvest command before process") - sys.exit(1) + ctx = HermesContext() + merged_doc = ld_merge_dict([{}]) # Get all harvesters harvester_names = self.root_settings.harvest.sources - harvester_names.reverse() # Switch order for priority handling + ctx.prepare_step('harvest') for harvester in harvester_names: self.log.info("## Process data from %s", harvester) - - harvest_context = HermesHarvestContext(ctx, harvester, {}) - try: - harvest_context.load_cache() - # when the harvest step ran, but there is no cache file, this is a serious flaw - except FileNotFoundError: - self.log.warning("No output data from harvester %s found, skipping", harvester) - continue - - ctx.merge_from(harvest_context) - ctx.merge_contexts_from(harvest_context) - - if ctx._errors: - self.log.error('Errors during merge') - self.errors.extend(ctx._errors) - - for ep, error in ctx._errors: - self.log.info(" - %s: %s", ep.name, error) - - tags_path = ctx.get_cache('process', 'tags', create=True) - with tags_path.open('w') as tags_file: - json.dump(ctx.tags, tags_file, indent=2) - - ctx.prepare_codemeta() - - with open(ctx.get_cache("process", ctx.hermes_name, create=True), 'w') as codemeta_file: - json.dump(ctx._data, codemeta_file, indent=2) + merged_doc.update(SoftwareMetadata.load_from_cache(ctx, harvester)) + ctx.finalize_step("harvest") + + ctx.prepare_step("process") + with ctx["result"] as result_ctx: + result_ctx["codemeta"] = merged_doc.compact() + result_ctx["context"] = {"@context": merged_doc.full_context} + result_ctx["expanded"] = merged_doc.ld_value + ctx.finalize_step("process") diff --git a/src/hermes/model/merge/__init__.py b/src/hermes/model/merge/__init__.py new file mode 100644 index 00000000..1741dca8 --- /dev/null +++ b/src/hermes/model/merge/__init__.py @@ -0,0 +1,3 @@ +# SPDX-FileCopyrightText: 2022 German Aerospace Center (DLR) +# +# SPDX-License-Identifier: Apache-2.0 \ No newline at end of file diff --git a/src/hermes/model/merge/action.py b/src/hermes/model/merge/action.py new file mode 100644 index 00000000..80f45591 --- /dev/null +++ b/src/hermes/model/merge/action.py @@ -0,0 +1,83 @@ +# SPDX-FileCopyrightText: 2025 German Aerospace Center (DLR) +# +# SPDX-License-Identifier: Apache-2.0 + +# SPDX-FileContributor: Michael Meinel + +from hermes.model.types import ld_list + + +class MergeError(ValueError): + pass + + +class MergeAction: + def merge(self, target, key, value, update): + raise NotImplementedError() + + +class Reject(MergeAction): + @classmethod + def merge(cls, target, key, value, update): + if value != update: + target.reject(key, update) + return value + + +class Replace(MergeAction): + @classmethod + def merge(cls, target, key, value, update): + if value != update: + target.replace(key, value) + return update + + +class Concat(MergeAction): + @classmethod + def merge(cls, target, key, value, update): + return cls.merge_to_list(value, update) + + @classmethod + def merge_to_list(cls, head, tail): + if not isinstance(head, (list, ld_list)): + head = [head] + if not isinstance(tail, (list, ld_list)): + head.append(tail) + else: + head.extend(tail) + return head + + +class Collect(MergeAction): + def __init__(self, match): + self.match = match + + def merge(self, target, key, value, update): + if not isinstance(value, list): + value = [value] + if not isinstance(update, list): + update = [update] + + for update_item in update: + if not any(self.match(item, update_item) for item in value): + value.append(update_item) + + if len(value) == 1: + return value[0] + else: + return value + + +class MergeSet(MergeAction): + def __init__(self, match, merge_items=True): + self.match = match + self.merge_items = merge_items + + def merge(self, target, key, value, update): + for item in update: + target_item = target.match(key[-1], item, self.match) + if target_item and self.merge_items: + target_item.update(item) + else: + value.append(item) + return value diff --git a/src/hermes/model/merge/container.py b/src/hermes/model/merge/container.py new file mode 100644 index 00000000..80395d87 --- /dev/null +++ b/src/hermes/model/merge/container.py @@ -0,0 +1,116 @@ +# SPDX-FileCopyrightText: 2025 German Aerospace Center (DLR) +# +# SPDX-License-Identifier: Apache-2.0 + +# SPDX-FileContributor: Michael Meinel + +from hermes.model.types import ld_context, ld_dict, ld_list + +from .strategy import CODEMETA_STRATEGY, PROV_STRATEGY, REPLACE_STRATEGY +from ..types.pyld_util import bundled_loader + + +class _ld_merge_container: + def _to_python(self, full_iri, ld_value): + value = super()._to_python(full_iri, ld_value) + if isinstance(value, ld_dict) and not isinstance(value, ld_merge_dict): + value = ld_merge_dict( + value.ld_value, + parent=value.parent, + key=value.key, + index=value.index, + context=value.context + ) + if isinstance(value, ld_list) and not isinstance(value, ld_merge_list): + value = ld_merge_list( + value.ld_value, + parent=value.parent, + key=value.key, + index=value.index, + context=value.context + ) + return value + + +class ld_merge_list(_ld_merge_container, ld_list): + def __init__(self, data, *, parent=None, key=None, index=None, context=None): + super().__init__(data, parent=parent, key=key, index=index, context=context) + + +class ld_merge_dict(_ld_merge_container, ld_dict): + def __init__(self, data, *, parent=None, key=None, index=None, context=None): + super().__init__(data, parent=parent, key=key, index=index, context=context) + + self.update_context(ld_context.HERMES_PROV_CONTEXT) + + self.strategies = {**REPLACE_STRATEGY} + self.add_strategy(CODEMETA_STRATEGY) + self.add_strategy(PROV_STRATEGY) + + def update_context(self, other_context): + if other_context: + if len(self.context) < 1 or not isinstance(self.context[-1], dict): + self.context.append({}) + + if not isinstance(other_context, list): + other_context = [other_context] + for ctx in other_context: + if isinstance(ctx, dict): + # FIXME: Shouldn't the dict be appended instead? + # How it is implemented currently results in anomalies like this: + # other_context = [{"codemeta": "https://doi.org/10.5063/schema/codemeta-1.0/"}] + # self.context = [{"codemeta": "https://doi.org/10.5063/schema/codemeta-2.0/"}] + # resulting context is only [{"codemeta": "https://doi.org/10.5063/schema/codemeta-1.0/"}] + # values that start with "https://doi.org/10.5063/schema/codemeta-2.0/" can't be compacted anymore + self.context[-1].update(ctx) + elif ctx not in self.context: + self.context.insert(0, ctx) + + self.active_ctx = self.ld_proc.initial_ctx(self.context, {"documentLoader": bundled_loader}) + + def update(self, other): + if isinstance(other, ld_dict): + self.update_context(other.context) + + super().update(other) + + def add_strategy(self, strategy): + for key, value in strategy.items(): + self.strategies[key] = {**value, **self.strategies.get(key, {})} + + def __setitem__(self, key, value): + if key in self: + value = self._merge_item(key, value) + super().__setitem__(key, value) + + def match(self, key, value, match): + for index, item in enumerate(self[key]): + if match(item, value): + if isinstance(item, ld_dict) and not isinstance(item, ld_merge_dict): + item = ld_merge_dict( + item.ld_value, parent=item.parent, key=item.key, index=index, context=item.context + ) + elif isinstance(item, ld_list) and not isinstance(item, ld_merge_list): + item = ld_merge_list( + item.ld_value, parent=item.parent, key=item.key, index=index, context=item.context + ) + return item + + def _merge_item(self, key, value): + strategy = {**self.strategies[None]} + ld_types = self.data_dict.get('@type', []) + for ld_type in ld_types: + strategy.update(self.strategies.get(ld_type, {})) + + merger = strategy.get(key, strategy[None]) + return merger.merge(self, [*self.path, key], self[key], value) + + def _add_related(self, rel, key, value): + self.emplace(rel) + self[rel].append({"@type": "schema:PropertyValue", "schema:name": str(key), "schema:value": str(value)}) + + def reject(self, key, value): + self._add_related("hermes-rt:reject", key, value) + + def replace(self, key, value): + self._add_related("hermes-rt:replace", key, value) diff --git a/src/hermes/model/merge/match.py b/src/hermes/model/merge/match.py new file mode 100644 index 00000000..03b9f9ef --- /dev/null +++ b/src/hermes/model/merge/match.py @@ -0,0 +1,17 @@ +# SPDX-FileCopyrightText: 2025 German Aerospace Center (DLR) +# +# SPDX-License-Identifier: Apache-2.0 + +# SPDX-FileContributor: Michael Meinel + + +def match_equals(a, b): + return a == b + + +def match_keys(*keys): + def match_func(left, right): + active_keys = [key for key in keys if key in left and key in right] + pairs = [(left[key] == right[key]) for key in active_keys] + return len(active_keys) > 0 and all(pairs) + return match_func diff --git a/src/hermes/model/merge/strategy.py b/src/hermes/model/merge/strategy.py new file mode 100644 index 00000000..12681fe6 --- /dev/null +++ b/src/hermes/model/merge/strategy.py @@ -0,0 +1,42 @@ +# SPDX-FileCopyrightText: 2025 German Aerospace Center (DLR) +# +# SPDX-License-Identifier: Apache-2.0 + +# SPDX-FileContributor: Michael Meinel + +from hermes.model.types.ld_context import iri_map as iri + +from .action import Reject, Replace, Collect, Concat, MergeSet +from .match import match_equals, match_keys + + +REPLACE_STRATEGY = { + None: { + None: Replace, + "@type": Collect(match_equals), + }, +} + + +REJECT_STRATEGY = { + None: { + None: Reject, + "@type": Collect(match_equals), + }, +} + + +PROV_STRATEGY = { + None: { + iri["hermes-rt:graph"]: Concat, + iri["hermes-rt:replace"]: Concat, + iri["hermes-rt:reject"]: Concat, + }, +} + + +CODEMETA_STRATEGY = { + iri["schema:SoftwareSourceCode"]: { + iri["schema:author"]: MergeSet(match_keys('@id', iri['schema:email'])), + }, +} diff --git a/src/hermes/model/types/ld_container.py b/src/hermes/model/types/ld_container.py index a18c886d..f97868d9 100644 --- a/src/hermes/model/types/ld_container.py +++ b/src/hermes/model/types/ld_container.py @@ -237,7 +237,7 @@ def _to_expanded_json( # while searching build a path such that it leads from the found ld_dicts ld_value to selfs data_dict/ item_list parent = self path = [] - while parent.__class__.__name__ not in ("ld_dict", "SoftwareMetadata"): + while parent.__class__.__name__ not in ("ld_dict", "SoftwareMetadata", "ld_merge_dict"): if parent.container_type == "@list": path.extend(["@list", 0]) elif parent.container_type == "@graph": @@ -250,7 +250,7 @@ def _to_expanded_json( # if neither self nor any of its parents is a ld_dict: # create a dict with the key of the outer most parent of self and this parents ld_value as a value # this dict is stored in an ld_container and simulates the most minimal JSON-LD object possible - if parent.__class__.__name__ not in ("ld_dict", "SoftwareMetadata"): + if parent.__class__.__name__ not in ("ld_dict", "SoftwareMetadata", "ld_merge_dict"): key = self.ld_proc.expand_iri(parent.active_ctx, parent.key) parent = ld_container([{key: parent._data}]) path.append(0) @@ -277,7 +277,7 @@ def _to_expanded_json( [(new_key, temp) for new_key in temp.keys() if isinstance(temp[new_key], special_types)] ) elif isinstance(temp, ld_container): - if temp.__class__.__name__ == "ld_list" and temp.container_type == "@set": + if temp.__class__.__name__ in ("ld_list", "ld_merge_list") and temp.container_type == "@set": ref[key] = temp._data else: ref[key] = temp._data[0] From ebebca4e5099c1a856acfbf755077ca5d0a2aa45 Mon Sep 17 00:00:00 2001 From: notactuallyfinn Date: Fri, 6 Feb 2026 14:00:09 +0100 Subject: [PATCH 07/19] added first tests --- src/hermes/commands/__init__.py | 2 +- src/hermes/commands/cli.py | 4 +- test/hermes_test/model/test_api_e2e.py | 103 +++++++++++++++++++++++++ 3 files changed, 106 insertions(+), 3 deletions(-) diff --git a/src/hermes/commands/__init__.py b/src/hermes/commands/__init__.py index 278faddf..e1ddf036 100644 --- a/src/hermes/commands/__init__.py +++ b/src/hermes/commands/__init__.py @@ -14,6 +14,6 @@ # from hermes.commands.init.base import HermesInitCommand # from hermes.commands.curate.base import HermesCurateCommand from hermes.commands.harvest.base import HermesHarvestCommand -# from hermes.commands.process.base import HermesProcessCommand +from hermes.commands.process.base import HermesProcessCommand from hermes.commands.deposit.base import HermesDepositCommand # from hermes.commands.postprocess.base import HermesPostprocessCommand diff --git a/src/hermes/commands/cli.py b/src/hermes/commands/cli.py index 0ec2d1ae..d465f3b8 100644 --- a/src/hermes/commands/cli.py +++ b/src/hermes/commands/cli.py @@ -16,7 +16,7 @@ # from hermes.commands import (HermesHelpCommand, HermesVersionCommand, HermesCleanCommand, # HermesHarvestCommand, HermesProcessCommand, HermesCurateCommand, # HermesDepositCommand, HermesPostprocessCommand, HermesInitCommand) -from hermes.commands import HermesDepositCommand, HermesHarvestCommand +from hermes.commands import HermesDepositCommand, HermesHarvestCommand, HermesProcessCommand from hermes.commands.base import HermesCommand @@ -43,7 +43,7 @@ def main() -> None: # HermesInitCommand(parser), # HermesCleanCommand(parser), HermesHarvestCommand(parser), - # HermesProcessCommand(parser), + HermesProcessCommand(parser), # HermesCurateCommand(parser), HermesDepositCommand(parser), # HermesPostprocessCommand(parser), diff --git a/test/hermes_test/model/test_api_e2e.py b/test/hermes_test/model/test_api_e2e.py index 18dc973c..0eddc59b 100644 --- a/test/hermes_test/model/test_api_e2e.py +++ b/test/hermes_test/model/test_api_e2e.py @@ -475,3 +475,106 @@ def test_invenio_deposit(tmp_path, monkeypatch, sandbox_auth, metadata, invenio_ # TODO: compare to actually expected value assert result == invenio_metadata + + +@pytest.mark.parametrize( + "metadata_in, metadata_out", + [ + ( + { + "cff": SoftwareMetadata({ + "@type": ["http://schema.org/SoftwareSourceCode"], + "http://schema.org/description": [{"@value": "for testing"}], + "http://schema.org/name": [{"@value": "Test"}], + "http://schema.org/author": [{ + "@type": "http://schema.org/Person", + "http://schema.org/familyName": [{"@value": "Test"}], + "http://schema.org/givenName": [{"@value": "Testi"}] + }], + "http://schema.org/license": [{"@id": "https://spdx.org/licenses/Apache-2.0"}] + }) + }, + SoftwareMetadata({ + "@type": ["http://schema.org/SoftwareSourceCode"], + "http://schema.org/description": [{"@value": "for testing"}], + "http://schema.org/name": [{"@value": "Test"}], + "http://schema.org/author": [{ + "@type": "http://schema.org/Person", + "http://schema.org/familyName": [{"@value": "Test"}], + "http://schema.org/givenName": [{"@value": "Testi"}] + }], + "http://schema.org/license": [{"@id": "https://spdx.org/licenses/Apache-2.0"}] + }) + ), + ( + { + "cff": SoftwareMetadata({ + "@type": ["http://schema.org/SoftwareSourceCode"], + "http://schema.org/name": [{"@value": "Test"}], + "http://schema.org/author": [{ + "@type": "http://schema.org/Person", + "http://schema.org/familyName": [{"@value": "Test"}], + "http://schema.org/givenName": [{"@value": "Testi"}], + "http://schema.org/email": [{"@value": "test.testi@testis.tests"}] + }], + "http://schema.org/license": [{"@id": "https://spdx.org/licenses/Apache-2.0"}] + }), + "codemeta": SoftwareMetadata({ + "@type": ["http://schema.org/SoftwareSourceCode"], + "http://schema.org/description": [{"@value": "for testing"}], + "http://schema.org/name": [{"@value": "Test"}], + "http://schema.org/author": [{ + "@type": "http://schema.org/Person", + "http://schema.org/familyName": [{"@value": "Test"}], + "http://schema.org/givenName": [{"@value": "Testi"}], + "http://schema.org/email": [{"@value": "test.testi@testis.tests"}] + }] + }) + }, + SoftwareMetadata({ + "@type": ["http://schema.org/SoftwareSourceCode"], + "http://schema.org/description": [{"@value": "for testing"}], + "http://schema.org/name": [{"@value": "Test"}], + "http://schema.org/author": [{ + "@type": "http://schema.org/Person", + "http://schema.org/familyName": [{"@value": "Test"}], + "http://schema.org/givenName": [{"@value": "Testi"}], + "http://schema.org/email": [{"@value": "test.testi@testis.tests"}] + }], + "http://schema.org/license": [{"@id": "https://spdx.org/licenses/Apache-2.0"}] + }) + ) + ] +) +def test_process(tmp_path, monkeypatch, metadata_in, metadata_out): + monkeypatch.chdir(tmp_path) + + manager = context_manager.HermesContext(tmp_path) + manager.prepare_step("harvest") + for harvester, result in metadata_in.items(): + with manager[harvester] as cache: + cache["codemeta"] = result.compact() + cache["context"] = {"@context": result.full_context} + cache["expanded"] = result.ld_value + manager.finalize_step("harvest") + + config_file = tmp_path / "hermes.toml" + config_file.write_text(f"[harvest]\nsources = [{", ".join(f"\"{harvester}\"" for harvester in metadata_in)}]") + + orig_argv = sys.argv[:] + sys.argv = ["hermes", "process", "--path", str(tmp_path), "--config", str(config_file)] + result = {} + try: + monkeypatch.setattr(context_manager.HermesContext.__init__, "__defaults__", (tmp_path.cwd(),)) + cli.main() + except SystemExit as e: + if e.code != 0: + raise e + finally: + manager.prepare_step("process") + result = SoftwareMetadata.load_from_cache(manager, "result") + manager.finalize_step("process") + sys.argv = orig_argv + + assert result.ld_value == metadata_out.ld_value + assert result == metadata_out From f21df496ef85d61341dfa31ff15f4cbf54d42a87 Mon Sep 17 00:00:00 2001 From: Michael Fritzsche Date: Mon, 9 Feb 2026 09:16:05 +0100 Subject: [PATCH 08/19] (re)added version and help commands to the available commands --- src/hermes/commands/__init__.py | 6 +++--- src/hermes/commands/base.py | 21 +++++++++++++++++++++ src/hermes/commands/cli.py | 8 +++++--- 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/src/hermes/commands/__init__.py b/src/hermes/commands/__init__.py index e1ddf036..d239cb0e 100644 --- a/src/hermes/commands/__init__.py +++ b/src/hermes/commands/__init__.py @@ -8,9 +8,9 @@ # "unused import" errors. # flake8: noqa -# from hermes.commands.base import HermesHelpCommand -# from hermes.commands.base import HermesVersionCommand -# from hermes.commands.clean.base import HermesCleanCommand +from hermes.commands.base import HermesHelpCommand +from hermes.commands.base import HermesVersionCommand +from hermes.commands.clean.base import HermesCleanCommand # from hermes.commands.init.base import HermesInitCommand # from hermes.commands.curate.base import HermesCurateCommand from hermes.commands.harvest.base import HermesHarvestCommand diff --git a/src/hermes/commands/base.py b/src/hermes/commands/base.py index 2d182267..12e3c994 100644 --- a/src/hermes/commands/base.py +++ b/src/hermes/commands/base.py @@ -175,6 +175,7 @@ def __call__(self, command: HermesCommand) -> None: class HermesHelpSettings(BaseModel): + """Intentionally empty settings class for the help command.""" pass @@ -200,3 +201,23 @@ def __call__(self, args: argparse.Namespace) -> None: # Otherwise, simply show the general help and exit (cleanly). self.parser.print_help() self.parser.exit() + + +class HermesVersionSettings(BaseModel): + """Intentionally empty settings class for the version command.""" + pass + + +class HermesVersionCommand(HermesCommand): + """Show HERMES version and exit.""" + + command_name = "version" + settings_class = HermesVersionSettings + + def load_settings(self, args: argparse.Namespace): + """Pass loading settings as not necessary for this command.""" + pass + + def __call__(self, args: argparse.Namespace) -> None: + self.log.info(metadata.version("hermes")) + self.parser.exit() diff --git a/src/hermes/commands/cli.py b/src/hermes/commands/cli.py index d465f3b8..debe6f62 100644 --- a/src/hermes/commands/cli.py +++ b/src/hermes/commands/cli.py @@ -16,7 +16,9 @@ # from hermes.commands import (HermesHelpCommand, HermesVersionCommand, HermesCleanCommand, # HermesHarvestCommand, HermesProcessCommand, HermesCurateCommand, # HermesDepositCommand, HermesPostprocessCommand, HermesInitCommand) -from hermes.commands import HermesDepositCommand, HermesHarvestCommand, HermesProcessCommand +from hermes.commands import ( + HermesDepositCommand, HermesHarvestCommand, HermesHelpCommand, HermesProcessCommand, HermesVersionCommand +) from hermes.commands.base import HermesCommand @@ -38,8 +40,8 @@ def main() -> None: setting_types = {} for command in ( - # HermesHelpCommand(parser), - # HermesVersionCommand(parser), + HermesHelpCommand(parser), + HermesVersionCommand(parser), # HermesInitCommand(parser), # HermesCleanCommand(parser), HermesHarvestCommand(parser), From d4d9ca8d6e84edf137cf739483816a346139a151 Mon Sep 17 00:00:00 2001 From: Michael Fritzsche Date: Mon, 9 Feb 2026 09:16:50 +0100 Subject: [PATCH 09/19] made test for process step more complex --- test/hermes_test/model/test_api_e2e.py | 37 +++++++++++++++++--------- 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/test/hermes_test/model/test_api_e2e.py b/test/hermes_test/model/test_api_e2e.py index 0eddc59b..7a65098b 100644 --- a/test/hermes_test/model/test_api_e2e.py +++ b/test/hermes_test/model/test_api_e2e.py @@ -511,12 +511,18 @@ def test_invenio_deposit(tmp_path, monkeypatch, sandbox_auth, metadata, invenio_ "cff": SoftwareMetadata({ "@type": ["http://schema.org/SoftwareSourceCode"], "http://schema.org/name": [{"@value": "Test"}], - "http://schema.org/author": [{ - "@type": "http://schema.org/Person", - "http://schema.org/familyName": [{"@value": "Test"}], - "http://schema.org/givenName": [{"@value": "Testi"}], - "http://schema.org/email": [{"@value": "test.testi@testis.tests"}] - }], + "http://schema.org/author": [ + { + "@type": "http://schema.org/Person", + "http://schema.org/familyName": [{"@value": "Test"}], + "http://schema.org/email": [{"@value": "test.testi@testis.tests"}] + }, + { + "@type": "http://schema.org/Person", + "http://schema.org/familyName": [{"@value": "Tester"}], + "http://schema.org/email": [{"@value": "test@tester.tests"}] + } + ], "http://schema.org/license": [{"@id": "https://spdx.org/licenses/Apache-2.0"}] }), "codemeta": SoftwareMetadata({ @@ -535,12 +541,19 @@ def test_invenio_deposit(tmp_path, monkeypatch, sandbox_auth, metadata, invenio_ "@type": ["http://schema.org/SoftwareSourceCode"], "http://schema.org/description": [{"@value": "for testing"}], "http://schema.org/name": [{"@value": "Test"}], - "http://schema.org/author": [{ - "@type": "http://schema.org/Person", - "http://schema.org/familyName": [{"@value": "Test"}], - "http://schema.org/givenName": [{"@value": "Testi"}], - "http://schema.org/email": [{"@value": "test.testi@testis.tests"}] - }], + "http://schema.org/author": [ + { + "@type": "http://schema.org/Person", + "http://schema.org/familyName": [{"@value": "Test"}], + "http://schema.org/givenName": [{"@value": "Testi"}], + "http://schema.org/email": [{"@value": "test.testi@testis.tests"}] + }, + { + "@type": "http://schema.org/Person", + "http://schema.org/familyName": [{"@value": "Tester"}], + "http://schema.org/email": [{"@value": "test@tester.tests"}] + } + ], "http://schema.org/license": [{"@id": "https://spdx.org/licenses/Apache-2.0"}] }) ) From 7cfa7bcc7be101dd6580ead1d933f762e768d280 Mon Sep 17 00:00:00 2001 From: Michael Fritzsche Date: Mon, 9 Feb 2026 09:18:19 +0100 Subject: [PATCH 10/19] made process step and ld_container._to_expanded_json more robust --- src/hermes/commands/process/base.py | 9 ++++++++- src/hermes/model/types/ld_container.py | 6 +++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/hermes/commands/process/base.py b/src/hermes/commands/process/base.py index 83480056..1aae0dab 100644 --- a/src/hermes/commands/process/base.py +++ b/src/hermes/commands/process/base.py @@ -11,6 +11,7 @@ from hermes.commands.base import HermesCommand, HermesPlugin from hermes.model.api import SoftwareMetadata from hermes.model.context_manager import HermesContext +from hermes.model.error import HermesContextError from hermes.model.merge.container import ld_merge_dict @@ -42,7 +43,13 @@ def __call__(self, args: argparse.Namespace) -> None: ctx.prepare_step('harvest') for harvester in harvester_names: self.log.info("## Process data from %s", harvester) - merged_doc.update(SoftwareMetadata.load_from_cache(ctx, harvester)) + try: + metadata = SoftwareMetadata.load_from_cache(ctx, harvester) + except HermesContextError as e: + self.log.error("Error while trying to load data from harvest plugin '%s': %s", harvester, e) + self.errors.append(e) + continue + merged_doc.update(metadata) ctx.finalize_step("harvest") ctx.prepare_step("process") diff --git a/src/hermes/model/types/ld_container.py b/src/hermes/model/types/ld_container.py index f97868d9..756f2033 100644 --- a/src/hermes/model/types/ld_container.py +++ b/src/hermes/model/types/ld_container.py @@ -237,7 +237,7 @@ def _to_expanded_json( # while searching build a path such that it leads from the found ld_dicts ld_value to selfs data_dict/ item_list parent = self path = [] - while parent.__class__.__name__ not in ("ld_dict", "SoftwareMetadata", "ld_merge_dict"): + while not "ld_dict" in [sub_cls.__name__ for sub_cls in type(parent).mro()]: if parent.container_type == "@list": path.extend(["@list", 0]) elif parent.container_type == "@graph": @@ -250,7 +250,7 @@ def _to_expanded_json( # if neither self nor any of its parents is a ld_dict: # create a dict with the key of the outer most parent of self and this parents ld_value as a value # this dict is stored in an ld_container and simulates the most minimal JSON-LD object possible - if parent.__class__.__name__ not in ("ld_dict", "SoftwareMetadata", "ld_merge_dict"): + if not "ld_dict" in [sub_cls.__name__ for sub_cls in type(parent).mro()]: key = self.ld_proc.expand_iri(parent.active_ctx, parent.key) parent = ld_container([{key: parent._data}]) path.append(0) @@ -277,7 +277,7 @@ def _to_expanded_json( [(new_key, temp) for new_key in temp.keys() if isinstance(temp[new_key], special_types)] ) elif isinstance(temp, ld_container): - if temp.__class__.__name__ in ("ld_list", "ld_merge_list") and temp.container_type == "@set": + if "ld_list" in [sub_cls.__name__ for sub_cls in type(temp).mro()] and temp.container_type == "@set": ref[key] = temp._data else: ref[key] = temp._data[0] From 520ef39bf267643f32ab13da06d10db22a014565 Mon Sep 17 00:00:00 2001 From: Michael Fritzsche Date: Mon, 9 Feb 2026 09:26:51 +0100 Subject: [PATCH 11/19] improved flake8 rating --- src/hermes/model/merge/__init__.py | 2 +- src/hermes/model/types/ld_container.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/hermes/model/merge/__init__.py b/src/hermes/model/merge/__init__.py index 1741dca8..faf5a2f5 100644 --- a/src/hermes/model/merge/__init__.py +++ b/src/hermes/model/merge/__init__.py @@ -1,3 +1,3 @@ # SPDX-FileCopyrightText: 2022 German Aerospace Center (DLR) # -# SPDX-License-Identifier: Apache-2.0 \ No newline at end of file +# SPDX-License-Identifier: Apache-2.0 diff --git a/src/hermes/model/types/ld_container.py b/src/hermes/model/types/ld_container.py index 756f2033..f30a212c 100644 --- a/src/hermes/model/types/ld_container.py +++ b/src/hermes/model/types/ld_container.py @@ -237,7 +237,7 @@ def _to_expanded_json( # while searching build a path such that it leads from the found ld_dicts ld_value to selfs data_dict/ item_list parent = self path = [] - while not "ld_dict" in [sub_cls.__name__ for sub_cls in type(parent).mro()]: + while "ld_dict" not in [sub_cls.__name__ for sub_cls in type(parent).mro()]: if parent.container_type == "@list": path.extend(["@list", 0]) elif parent.container_type == "@graph": @@ -250,7 +250,7 @@ def _to_expanded_json( # if neither self nor any of its parents is a ld_dict: # create a dict with the key of the outer most parent of self and this parents ld_value as a value # this dict is stored in an ld_container and simulates the most minimal JSON-LD object possible - if not "ld_dict" in [sub_cls.__name__ for sub_cls in type(parent).mro()]: + if "ld_dict" not in [sub_cls.__name__ for sub_cls in type(parent).mro()]: key = self.ld_proc.expand_iri(parent.active_ctx, parent.key) parent = ld_container([{key: parent._data}]) path.append(0) From bcdc82124a1a6f3cacd0398bcf3a978ae8a18b57 Mon Sep 17 00:00:00 2001 From: notactuallyfinn Date: Fri, 13 Feb 2026 13:54:47 +0100 Subject: [PATCH 12/19] added lots of comments and fixed small inconsistencies --- src/hermes/commands/deposit/invenio.py | 4 +- src/hermes/model/merge/container.py | 301 +++++++++++++++++++++++-- src/hermes/model/merge/match.py | 53 ++++- src/hermes/model/types/ld_container.py | 6 +- src/hermes/model/types/ld_list.py | 4 +- test/hermes_test/model/test_api_e2e.py | 9 +- 6 files changed, 342 insertions(+), 35 deletions(-) diff --git a/src/hermes/commands/deposit/invenio.py b/src/hermes/commands/deposit/invenio.py index 3915d536..ba45c146 100644 --- a/src/hermes/commands/deposit/invenio.py +++ b/src/hermes/commands/deposit/invenio.py @@ -513,7 +513,7 @@ def _codemeta_to_invenio_deposition(self) -> dict: creators = [] for author in metadata.get("author", []): - if not "Person" in author.get("@type", []): + if "Person" not in author.get("@type", []): continue creator = {} if len( @@ -527,7 +527,7 @@ def _codemeta_to_invenio_deposition(self) -> dict: raise HermesValidationError(f"Author has too many family names: {author}") if len(author.get("familyName", [])) == 1: given_names_str = " ".join(author.get("givenName", [])) - name = f"{author["familyName"][0]}, {given_names_str}" + name = f"{author['familyName'][0]}, {given_names_str}" elif len(author.get("name", [])) != 1: raise HermesValidationError(f"Author has too many or no names: {author}") else: diff --git a/src/hermes/model/merge/container.py b/src/hermes/model/merge/container.py index 80395d87..ec9fedd9 100644 --- a/src/hermes/model/merge/container.py +++ b/src/hermes/model/merge/container.py @@ -3,16 +3,49 @@ # SPDX-License-Identifier: Apache-2.0 # SPDX-FileContributor: Michael Meinel +# SPDX-FileContributor: Michael Fritzsche -from hermes.model.types import ld_context, ld_dict, ld_list +from typing import Callable, Union +from typing_extensions import Self + +from hermes.model.merge.action import MergeAction +from hermes.model.types import ld_container, ld_context, ld_dict, ld_list +from hermes.model.types.ld_container import ( + BASIC_TYPE, EXPANDED_JSON_LD_VALUE, JSON_LD_CONTEXT_DICT, JSON_LD_VALUE, TIME_TYPE +) from .strategy import CODEMETA_STRATEGY, PROV_STRATEGY, REPLACE_STRATEGY from ..types.pyld_util import bundled_loader class _ld_merge_container: - def _to_python(self, full_iri, ld_value): + """ + Abstract base class for ld_merge_dict and ld_merge_list, + providing the merge containers with overrides of ld_container._to_python(). + See also :class:`ld_dict`, :class:`ld_list` and :class:`ld_container`. + """ + + def _to_python( + self: Self, + full_iri: str, + ld_value: Union[EXPANDED_JSON_LD_VALUE, dict[str, EXPANDED_JSON_LD_VALUE], list[str], str] + ) -> Union["ld_merge_dict", "ld_merge_list", BASIC_TYPE, TIME_TYPE]: + """ + Returns a pythonized version of the given value pretending the value is in self and full_iri its key. + + :param self: the ld_container ld_value is considered to be in. + :type self: Self + :param full_iri: The expanded iri of the key of ld_value / self (later if self is not a dictionary). + :type full_iri: str + :param ld_value: The value thats pythonized value is requested. ld_value has to be valid expanded JSON-LD if it + was embeded in self._data. + :type ld_value: EXPANDED_JSON_LD_VALUE | dict[str, EXPANDED_JSON_LD_VALUE] | list[str] | str + + :return: The pythonized value of the ld_value. + :rtype: ld_merge_dict | ld_merge_list | BASIC_TYPE | TIME_TYPE + """ value = super()._to_python(full_iri, ld_value) + # replace ld_dicts with ld_merge_dicts if isinstance(value, ld_dict) and not isinstance(value, ld_merge_dict): value = ld_merge_dict( value.ld_value, @@ -21,6 +54,7 @@ def _to_python(self, full_iri, ld_value): index=value.index, context=value.context ) + # replace ld_lists with ld_merge_lists if isinstance(value, ld_list) and not isinstance(value, ld_merge_list): value = ld_merge_list( value.ld_value, @@ -33,21 +67,108 @@ def _to_python(self, full_iri, ld_value): class ld_merge_list(_ld_merge_container, ld_list): - def __init__(self, data, *, parent=None, key=None, index=None, context=None): + """ + ld_list wrapper to ensure the 'merge_container'-property does not get lost, while merging. + See also :class:`ld_list` and :class:`ld_merge_container`. + """ + + def __init__( + self: "ld_merge_list", + data: Union[list[str], list[dict[str, EXPANDED_JSON_LD_VALUE]]], + *, + parent: Union[ld_container, None] = None, + key: Union[str, None] = None, + index: Union[int, None] = None, + context: Union[list[Union[str, JSON_LD_CONTEXT_DICT]], None] = None + ) -> None: + """ + Create a new ld_merge_list. + For further information on this function and the errors it throws see :meth:`ld_list.__init__`. + + :param self: The instance of ld_merge_list to be initialized. + :type self: Self + :param data: The expanded json-ld data that is mapped (must be valid for @set, @list or @graph) + :type data: list[str] | list[dict[str, BASIC_TYPE | EXPANDED_JSON_LD_VALUE]] + :param parent: parent node of this container. + :type parent: ld_container | None + :param key: key into the parent container. + :type key: str | None + :param index: index into the parent container. + :type index: int | None + :param context: local context for this container. + :type context: list[str | JSON_LD_CONTEXT_DICT] | None + + :return: + :rtype: None + """ super().__init__(data, parent=parent, key=key, index=index, context=context) class ld_merge_dict(_ld_merge_container, ld_dict): - def __init__(self, data, *, parent=None, key=None, index=None, context=None): + """ + ld_dict wrapper providing methods to merge an object of this class with an ld_dict object. + See also :class:`ld_dict` and :class:`ld_merge_container`. + + :ivar strategies: The strategies for merging different types of values in the ld_dicts. + :ivartype strategies: dict[str | None, dict[str | None, MergeAction]] + """ + + def __init__( + self: Self, + data: list[dict[str, EXPANDED_JSON_LD_VALUE]], + *, + parent: Union[ld_dict, ld_list, None] = None, + key: Union[str, None] = None, + index: Union[int, None] = None, + context: Union[list[Union[str, JSON_LD_CONTEXT_DICT]], None] = None + ) -> None: + """ + Create a new instance of an ld_merge_dict. + See also :meth:`ld_dict.__init__`. + + :param self: The instance of ld_container to be initialized. + :type self: Self + :param data: The expanded json-ld data that is mapped. + :type data: EXPANDED_JSON_LD_VALUE + :param parent: parent node of this container. + :type parent: ld_dict | ld_list | None + :param key: key into the parent container. + :type key: str | None + :param index: index into the parent container. + :type index: int | None + :param context: local context for this container. + :type context: list[str | JSON_LD_CONTEXT_DICT] | None + + :return: + :rtype: None + + :raises ValueError: If the given data doesn't represent an ld_dict. + """ super().__init__(data, parent=parent, key=key, index=index, context=context) + # add provernance context self.update_context(ld_context.HERMES_PROV_CONTEXT) + # add strategies self.strategies = {**REPLACE_STRATEGY} self.add_strategy(CODEMETA_STRATEGY) self.add_strategy(PROV_STRATEGY) - def update_context(self, other_context): + def update_context( + self: Self, other_context: Union[list[Union[str, JSON_LD_CONTEXT_DICT]], None] + ) -> None: + """ + Updates selfs context with other_context. + JSON-LD processing prioritizes the context values in order (first least important, last most important). + + :param self: The instance of the ld_merge_dict context is added to. + :type self: Self + :param other_context: The context object that is added to selfs context. + :type other_context: list[str | JSON_LD_CONTEXT_DICT] | None + + :return: + :rtype: None + """ if other_context: if len(self.context) < 1 or not isinstance(self.context[-1], dict): self.context.append({}) @@ -56,7 +177,7 @@ def update_context(self, other_context): other_context = [other_context] for ctx in other_context: if isinstance(ctx, dict): - # FIXME: Shouldn't the dict be appended instead? + # FIXME #471: Shouldn't the dict be appended instead? # How it is implemented currently results in anomalies like this: # other_context = [{"codemeta": "https://doi.org/10.5063/schema/codemeta-1.0/"}] # self.context = [{"codemeta": "https://doi.org/10.5063/schema/codemeta-2.0/"}] @@ -64,53 +185,187 @@ def update_context(self, other_context): # values that start with "https://doi.org/10.5063/schema/codemeta-2.0/" can't be compacted anymore self.context[-1].update(ctx) elif ctx not in self.context: + # FIXME #471: If multiple string values are in self.context, the others are prefered + # if the new one is inserted at the beginning. But with the dictionaries the order is reversed. self.context.insert(0, ctx) + # update the active context that is used for compaction/ expansion self.active_ctx = self.ld_proc.initial_ctx(self.context, {"documentLoader": bundled_loader}) - def update(self, other): + def update(self: Self, other: ld_dict) -> None: + """ + Updates/ Merges this ld_merge dict with the given ld_dict other. + This overwrites :meth:`ld_dict.update`, and may cause unexpected behavior if not used carefully. + + :param self: The ld_merge_dict that is updated with other. + :type self: Self + :param other: The ld_container that is merged into self. + :type other: ld_dict + + :return: + :rtype: None + """ + # update add all new context if isinstance(other, ld_dict): self.update_context(other.context) + # add the acutal values based on the MergeAction strategies + # this works implicitly because ld_dict.update invokes self.__setitem__ which is overwritten by ld_merge_dict super().update(other) - def add_strategy(self, strategy): + def add_strategy(self: Self, strategy: dict[Union[str, None], dict[Union[str, None], MergeAction]]) -> None: + """ + Adds the given strategy to the self.strategies. + + :param self: The ld_merge_dict the strategy is added to. + :type self: Self + :param strategy: The object describing how which object types are supposed to be merged. + :type strategy: dict[str | None, dict[str | None, MergeAction]] + """ for key, value in strategy.items(): self.strategies[key] = {**value, **self.strategies.get(key, {})} - def __setitem__(self, key, value): + def __setitem__(self: Self, key: str, value: Union[JSON_LD_VALUE, BASIC_TYPE, TIME_TYPE, ld_dict, ld_list]): + """ + Creates the new entry for self[key] using self.strategies on the values in self[key] and value. + Wraps :meth:`ld_dict.__setitem__`, and may cause unexpected behavior if not used carefully. + + :param self: The ld_merge_dict whose value at key gets updated/ merged with value. + :type self: Self + :param key: The key at whicht the value is updated/ merged at in self. + :type key: str + :param value: The value that is merged into self[key]. + :type value: JSON_LD_VALUE | BASIC_TYPE | TIME_TYPE | ld_dict | ld_list + """ + # create the new item if self[key] and value have to be merged. if key in self: value = self._merge_item(key, value) + # update the entry of self[key] super().__setitem__(key, value) - def match(self, key, value, match): - for index, item in enumerate(self[key]): + def match( + self: Self, + key: str, + value: Union[BASIC_TYPE, TIME_TYPE, ld_dict, ld_list], + match: Union[ + Callable[ + [ + Union[BASIC_TYPE, TIME_TYPE, "ld_merge_dict", ld_merge_list], + Union[BASIC_TYPE, TIME_TYPE, ld_dict, ld_list] + ], + bool + ], + Callable[["ld_merge_dict", ld_dict], bool] + ] + ) -> Union[BASIC_TYPE, TIME_TYPE, "ld_merge_dict", ld_merge_list]: + """ + Returns the first item in self[key] for which match(item, value) returns true. + If no such item is found None is returned instead. + + :param self: The ld_merge_dict in whose entry for key a match for value is searched. + :type self: Self + :param key: The key to the items in self in which a match for value is searched. + :type key: str + :param value: The value a match is searched for in self[key]. + :type value: Union[JSON_LD_VALUE, BASIC_TYPE, TIME_TYPE, ld_dict, ld_list] + :param match: The method defining if two objects are a match. + :type match: Callable[ + [ + BASIC_TYPE | TIME_TYPE | ld_merge_dict | ld_merge_list, + BASIC_TYPE | TIME_TYPE | ld_dict | ld_list + ], + bool + ] | Callable[[ld_merge_dict, ld_dict], bool] + + :return: The item in self[key] that is a match to value if one exists else None + :rtype: BASIC_TYPE | TIME_TYPE | ld_merge_dict | ld_merge_list + """ + # iterate over all items in self[key] and return the first that is a match + for item in self[key]: if match(item, value): - if isinstance(item, ld_dict) and not isinstance(item, ld_merge_dict): - item = ld_merge_dict( - item.ld_value, parent=item.parent, key=item.key, index=index, context=item.context - ) - elif isinstance(item, ld_list) and not isinstance(item, ld_merge_list): - item = ld_merge_list( - item.ld_value, parent=item.parent, key=item.key, index=index, context=item.context - ) return item - def _merge_item(self, key, value): + def _merge_item( + self: Self, key: str, value: Union[BASIC_TYPE, TIME_TYPE, ld_dict, ld_list] + ) -> Union[BASIC_TYPE, TIME_TYPE, "ld_merge_dict", ld_merge_list]: + """ + Applies the most suitable merge strategy to merge self[key] and value and then returns the result. + + :param self: The ld_merge_dict whose entry at key is to be merged with value. + :type self: Self + :param key: The key to the entry in self that is to be merged with value. + :type key: str + :param value: The value that is to be merged with self[key]. + :type value: BASIC_TYPE | TIME_TYPE | ld_dict | ld_list + + :return: The result of the merge from self[key] with value. + :rtype: BASIC_TYPE | TIME_TYPE | ld_merge_dict | ld_merge_list + """ + # search for all applicable strategies strategy = {**self.strategies[None]} ld_types = self.data_dict.get('@type', []) for ld_type in ld_types: strategy.update(self.strategies.get(ld_type, {})) + # choose one merge strategy and return the item returned by following the merge startegy merger = strategy.get(key, strategy[None]) return merger.merge(self, [*self.path, key], self[key], value) - def _add_related(self, rel, key, value): + def _add_related( + self: Self, rel: str, key: str, value: Union[BASIC_TYPE, TIME_TYPE, ld_dict, ld_list] + ) -> None: + """ + Adds an entry for rel to self containing which key and value is affected. + + :param self: The ld_merge_container the special entry is added to. + :type self: Self + :param rel: The "type" of the special entry (used as the key). + :type rel: str + :param key: The key of the affected key, value pair in self. + :type key: str + :param value: The value of the affected key, value pair in self. + :type value: BASIC_TYPE | TIME_TYPE | ld_dict | ld_list + + :return: + :rtype: None + """ + # make sure appending is possible self.emplace(rel) + # append the new entry self[rel].append({"@type": "schema:PropertyValue", "schema:name": str(key), "schema:value": str(value)}) - def reject(self, key, value): + def reject(self: Self, key: str, value: Union[BASIC_TYPE, TIME_TYPE, ld_dict, ld_list]) -> None: + """ + Adds an entry to self containing containing information that the key, value pair + key, value has been rejected in the merge. + For further information see :meth:`ld_merge_dict._add_related`. + + :param self: The ld_merge_container the special entry is added to. + :type self: Self + :param key: The key of the rejected key, value pair in self. + :type key: str + :param value: The value of the rejected key, value pair in self. + :type value: BASIC_TYPE | TIME_TYPE | ld_dict | ld_list + + :return: + :rtype: None + """ self._add_related("hermes-rt:reject", key, value) - def replace(self, key, value): + def replace(self: Self, key: str, value: Union[BASIC_TYPE, TIME_TYPE, ld_dict, ld_list]) -> None: + """ + Adds an entry to self containing containing information that the key, value pair + key, value was replaced in the merge. + For further information see :meth:`ld_merge_dict._add_related`. + + :param self: The ld_merge_container the special entry is added to. + :type self: Self + :param key: The key of the old key, value pair in self. + :type key: str + :param value: The value of the old key, value pair in self. + :type value: BASIC_TYPE | TIME_TYPE | ld_dict | ld_list + + :return: + :rtype: None + """ self._add_related("hermes-rt:replace", key, value) diff --git a/src/hermes/model/merge/match.py b/src/hermes/model/merge/match.py index 03b9f9ef..77abca35 100644 --- a/src/hermes/model/merge/match.py +++ b/src/hermes/model/merge/match.py @@ -4,14 +4,61 @@ # SPDX-FileContributor: Michael Meinel +from typing import Any, Callable -def match_equals(a, b): +from hermes.model.merge.container import ld_merge_dict +from hermes.model.types import ld_dict + + +def match_equals(a: Any, b: Any) -> bool: + """ + Wrapper method for normal == comparison. + + :param a: First item for the comparison. + :type a: Any + :param b: Second item for the comparison. + :type b: Any + + :return: Truth value of a == b. + :rtype: bool + """ return a == b -def match_keys(*keys): - def match_func(left, right): +def match_keys( + *keys: list[str] +) -> Callable[[ld_merge_dict, ld_dict], bool]: + """ + Creates a function taking to parameters that returns true + if both given parameter have at least one common key in the given list of keys + and for all common keys in the given list of keys the values of both objects are the same. + + :param keys: The list of important keys for the comparison method. + :type keys: list[str] + + :return: A function comparing two given objects values for the keys in keys. + :rtype: Callable[[ld_merge_dict, ld_dict], bool] + """ + + # create and return the match function using the given keys + def match_func(left: ld_merge_dict, right: ld_dict) -> bool: + """ + Compares left to right by checking if a) they have at least one common key in a predetermined list of keys and + b) testing if both objects have equal values for all common keys in the predetermined key list. + + :param left: The first object for the comparison. + :type left: ld_merge_dict + :param right: The second object for the comparison. + :type right: ld_dict + + :return: The result of the comparison. + :rtype: bool + """ + # create a list of all common important keys active_keys = [key for key in keys if key in left and key in right] + # check if both objects have the same values for all active keys pairs = [(left[key] == right[key]) for key in active_keys] + # return whether or not both objects had the same values for all active keys + # and there was at least one active key return len(active_keys) > 0 and all(pairs) return match_func diff --git a/src/hermes/model/types/ld_container.py b/src/hermes/model/types/ld_container.py index f30a212c..b2456017 100644 --- a/src/hermes/model/types/ld_container.py +++ b/src/hermes/model/types/ld_container.py @@ -176,7 +176,9 @@ def ld_value(self: Self) -> EXPANDED_JSON_LD_VALUE: return self._data def _to_python( - self: Self, full_iri: str, ld_value: Union[list, dict, str] + self: Self, + full_iri: str, + ld_value: Union[EXPANDED_JSON_LD_VALUE, dict[str, EXPANDED_JSON_LD_VALUE], list[str], str] ) -> Union["ld_container", BASIC_TYPE, TIME_TYPE]: """ Returns a pythonized version of the given value pretending the value is in self and full_iri its key. @@ -187,7 +189,7 @@ def _to_python( :type full_iri: str :param ld_value: The value thats pythonized value is requested. ld_value has to be valid expanded JSON-LD if it was embeded in self._data. - :type ld_value: list | dict | str + :type ld_value: EXPANDED_JSON_LD_VALUE | dict[str, EXPANDED_JSON_LD_VALUE] | list[str] | str :return: The pythonized value of the ld_value. :rtype: ld_container | BASIC_TYPE | TIME_TYPE diff --git a/src/hermes/model/types/ld_list.py b/src/hermes/model/types/ld_list.py index c4d1c450..a76db3b6 100644 --- a/src/hermes/model/types/ld_list.py +++ b/src/hermes/model/types/ld_list.py @@ -23,7 +23,7 @@ class ld_list(ld_container): """ An JSON-LD container resembling a list ("@set", "@list" or "@graph"). - See also :class:`ld_container` + See also :class:`ld_container`. :ivar container_type: The type of JSON-LD container the list is representing. ("@set", "@list", "graph") :ivartype container_type: str @@ -35,7 +35,7 @@ def __init__( self: Self, data: Union[list[str], list[dict[str, EXPANDED_JSON_LD_VALUE]]], *, - parent: Union["ld_container", None] = None, + parent: Union[ld_container, None] = None, key: Union[str, None] = None, index: Union[int, None] = None, context: Union[list[Union[str, JSON_LD_CONTEXT_DICT]], None] = None, diff --git a/test/hermes_test/model/test_api_e2e.py b/test/hermes_test/model/test_api_e2e.py index 7a65098b..f756f101 100644 --- a/test/hermes_test/model/test_api_e2e.py +++ b/test/hermes_test/model/test_api_e2e.py @@ -4,6 +4,7 @@ # SPDX-FileContributor: Michael Fritzsche +from datetime import date import json import pytest import sys @@ -422,7 +423,7 @@ def test_file_deposit(tmp_path, monkeypatch, metadata): }), { "upload_type": "software", - "publication_date": "2026-02-02", + "publication_date": date.today().isoformat(), "title": "Test", "creators": [{"name": "Test, Testi"}], "description": "for testing", @@ -445,6 +446,8 @@ def test_invenio_deposit(tmp_path, monkeypatch, sandbox_auth, metadata, invenio_ cache["codemeta"] = metadata.compact() manager.finalize_step("curate") + (tmp_path / "test.txt").write_text("Test, oh wonderful test!\n") + config_file = tmp_path / "hermes.toml" config_file.write_text(f"""[deposit] target = "invenio" @@ -452,7 +455,7 @@ def test_invenio_deposit(tmp_path, monkeypatch, sandbox_auth, metadata, invenio_ site_url = "https://sandbox.zenodo.org" access_right = "closed" auth_token = "{sandbox_auth}" -files = ["hermes.toml"] +files = ["test.txt"] [deposit.invenio.api_paths] licenses = "api/vocabularies/licenses" """) @@ -572,7 +575,7 @@ def test_process(tmp_path, monkeypatch, metadata_in, metadata_out): manager.finalize_step("harvest") config_file = tmp_path / "hermes.toml" - config_file.write_text(f"[harvest]\nsources = [{", ".join(f"\"{harvester}\"" for harvester in metadata_in)}]") + config_file.write_text(f"[harvest]\nsources = [{', '.join(f'\"{harvester}\"' for harvester in metadata_in)}]") orig_argv = sys.argv[:] sys.argv = ["hermes", "process", "--path", str(tmp_path), "--config", str(config_file)] From 1c10dcab898d4e5c31b33b383a64af4b7430ba20 Mon Sep 17 00:00:00 2001 From: notactuallyfinn Date: Fri, 27 Feb 2026 12:30:41 +0100 Subject: [PATCH 13/19] added coments and fix small bug --- src/hermes/model/api.py | 7 + src/hermes/model/merge/action.py | 259 ++++++++++++++++++++++--- src/hermes/model/merge/container.py | 44 ++--- src/hermes/model/merge/match.py | 14 +- src/hermes/model/merge/strategy.py | 13 +- src/hermes/model/types/ld_container.py | 14 +- test/hermes_test/model/test_api.py | 7 + 7 files changed, 280 insertions(+), 78 deletions(-) diff --git a/src/hermes/model/api.py b/src/hermes/model/api.py index 24f1405e..db582656 100644 --- a/src/hermes/model/api.py +++ b/src/hermes/model/api.py @@ -1,3 +1,10 @@ +# SPDX-FileCopyrightText: 2026 German Aerospace Center (DLR) +# +# SPDX-License-Identifier: Apache-2.0 + +# SPDX-FileContributor: Michael Fritzsche +# SPDX-FileContributor: Stephan Druskat + from hermes.model.context_manager import HermesContext, HermesContexError from hermes.model.types import ld_dict from hermes.model.types.ld_context import ALL_CONTEXTS diff --git a/src/hermes/model/merge/action.py b/src/hermes/model/merge/action.py index 80f45591..08a2c084 100644 --- a/src/hermes/model/merge/action.py +++ b/src/hermes/model/merge/action.py @@ -3,81 +3,282 @@ # SPDX-License-Identifier: Apache-2.0 # SPDX-FileContributor: Michael Meinel +# SPDX-FileContributor: Michael Fritzsche -from hermes.model.types import ld_list +from __future__ import annotations + +from typing import TYPE_CHECKING, Callable, Union +from typing_extensions import Self + +from ..types import ld_dict, ld_list +from ..types.ld_container import BASIC_TYPE, JSON_LD_VALUE, TIME_TYPE + +if TYPE_CHECKING: + from .container import ld_merge_dict, ld_merge_list class MergeError(ValueError): + """ Class for any error while merging. """ pass class MergeAction: - def merge(self, target, key, value, update): + """ Base class for the different actions occuring druing a merge. """ + def merge( + self: Self, + target: ld_merge_dict, + key: list[Union[str, int]], + value: ld_merge_list, + update: Union[BASIC_TYPE, TIME_TYPE, ld_dict, ld_list] + ) -> Union[JSON_LD_VALUE, BASIC_TYPE, TIME_TYPE, ld_dict, ld_list]: + """ + An abstract method that needs to be implemented by all subclasses + to have a generic way to use the merge actions. + + :param target: The ld_merge_dict inside of which the items are merged. + :type target: ld_merge_dict + :param key: The "path" of keys so that parent[key[-1]] is value and + for the outermost parent of target out_parent out_parent[key[0]]...[key[-1]] results in value. + :type key: list[str | int] + :param value: The value inside target that is to be merged with update. + :type value: ld_merge_list + :param update: The value that is to be merged into target with value. + :type update: BASIC_TYPE | TIME_TYPE | ld_dict | ld_list + + :return: The merged value in an arbitrary format that is supported by :meth:`ld_dict.__setitem__`. + :rtype: JSON_LD_VALUE | BASIC_TYPE | TIME_TYPE | ld_dict | ld_list + """ raise NotImplementedError() class Reject(MergeAction): - @classmethod - def merge(cls, target, key, value, update): + def merge( + self: Self, + target: ld_merge_dict, + key: list[Union[str, int]], + value: ld_merge_list, + update: Union[BASIC_TYPE, TIME_TYPE, ld_dict, ld_list] + ) -> ld_merge_list: + """ + Rejects the new data ``update`` and lets target add an entry to itself documenting what data has been rejected. + + :param target: The ld_merge_dict inside of which the items are merged. + :type target: ld_merge_dict + :param key: The "path" of keys so that parent[key[-1]] is value and + for the outermost parent of target out_parent out_parent[key[0]]...[key[-1]] results in value. + :type key: list[str | int] + :param value: The value inside target that is to be merged with update.
This value won't be changed. + :type value: ld_merge_list + :param update: The value that is to be merged into target with value.
This value will be rejected. + :type update: BASIC_TYPE | TIME_TYPE | ld_dict | ld_list + + :return: The merged value.
+ This value will always be value. + :rtype: ld_merge_list + """ + # If necessary, add the entry that data has been rejected. if value != update: target.reject(key, update) + # Return value unchanged. return value class Replace(MergeAction): - @classmethod - def merge(cls, target, key, value, update): + def merge( + self: Self, + target: ld_merge_dict, + key: list[Union[str, int]], + value: ld_merge_list, + update: Union[BASIC_TYPE, TIME_TYPE, ld_dict, ld_list] + ) -> Union[BASIC_TYPE, TIME_TYPE, ld_dict, ld_list]: + """ + Replaces the old data ``value`` with the new data ``update`` + and lets target add an entry to itself documenting what data has been replaced. + + :param target: The ld_merge_dict inside of which the items are merged. + :type target: ld_merge_dict + :param key: The "path" of keys so that parent[key[-1]] is value and + for the outermost parent of target out_parent out_parent[key[0]]...[key[-1]] results in value. + :type key: list[str | int] + :param value: The value inside target that is to be merged with update.
This value will bew replaced. + :type value: ld_merge_list + :param update: The value that is to be merged into target with value.
+ This value will be used instead of value. + :type update: BASIC_TYPE | TIME_TYPE | ld_dict | ld_list + + :return: The merged value.
+ This value will be update. + :rtype: BASIC_TYPE | TIME_TYPE | ld_dict | ld_list + """ + # If necessary, add the entry that data has been replaced. if value != update: target.replace(key, value) + # Return the new value. return update class Concat(MergeAction): - @classmethod - def merge(cls, target, key, value, update): - return cls.merge_to_list(value, update) - - @classmethod - def merge_to_list(cls, head, tail): - if not isinstance(head, (list, ld_list)): - head = [head] - if not isinstance(tail, (list, ld_list)): - head.append(tail) + def merge( + self: Self, + target: ld_merge_dict, + key: list[Union[str, int]], + value: ld_merge_list, + update: Union[BASIC_TYPE, TIME_TYPE, ld_dict, ld_list] + ) -> ld_merge_list: + """ + Concatenates the new data ``update`` to the old data ``value``. + + :param target: The ld_merge_dict inside of which the items are merged. + :type target: ld_merge_dict + :param key: The "path" of keys so that parent[key[-1]] is value and + for the outermost parent of target out_parent out_parent[key[0]]...[key[-1]] results in value. + :type key: list[str | int] + :param value: The value inside target that is to be merged with update. + :type value: ld_merge_list + :param update: The value that is to be merged into target with value. + :type update: BASIC_TYPE | TIME_TYPE | ld_dict | ld_list + + :return: The merged value.
+ ``value`` concatenated with ``update``. + :rtype: ld_merge_list + """ + # Concatenate the items and return the result. + if isinstance(update, (list, ld_list)): + value.extend(update) else: - head.extend(tail) - return head + value.append(update) + return value class Collect(MergeAction): - def __init__(self, match): + def __init__( + self: Self, + match: Union[ + Callable[ + [ + Union[BASIC_TYPE, TIME_TYPE, ld_merge_dict, ld_merge_list], + Union[BASIC_TYPE, TIME_TYPE, ld_dict, ld_list] + ], + bool + ], + Callable[[ld_merge_dict, ld_dict], bool] + ] + ) -> None: + """ + Set the match function for this collect merge action. + + :param match: The function used to evaluate equality while merging. + :type match: Callable[ + [BASIC_TYPE | TIME_TYPE | ld_merge_dict | ld_merge_list, BASIC_TYPE | TIME_TYPE | ld_dict | ld_list], + bool + ] | Callable[[ld_merge_dict, ld_dict], bool] + + :return: + :rtype: None + """ self.match = match - def merge(self, target, key, value, update): - if not isinstance(value, list): - value = [value] - if not isinstance(update, list): + def merge( + self: Self, + target: ld_merge_dict, + key: list[Union[str, int]], + value: ld_merge_list, + update: Union[BASIC_TYPE, TIME_TYPE, ld_dict, ld_list] + ) -> ld_merge_list: + """ + Collects the unique items (according to :attr:`match`) from ``value`` and ``update``. + + :param target: The ld_merge_dict inside of which the items are merged. + :type target: ld_merge_dict + :param key: The "path" of keys so that parent[key[-1]] is value and + for the outermost parent of target out_parent out_parent[key[0]]...[key[-1]] results in value. + :type key: list[str | int] + :param value: The value inside target that is to be merged with update. + :type value: ld_merge_list + :param update: The value that is to be merged into target with value. + :type update: BASIC_TYPE | TIME_TYPE | ld_dict | ld_list + + :return: The merged value. + :rtype: ld_merge_list + """ + if not isinstance(update, (list, ld_list)): update = [update] + # iterate over all new items for update_item in update: + # If the current new item has no occurence in value (according to self.match) add it to value. if not any(self.match(item, update_item) for item in value): value.append(update_item) - if len(value) == 1: - return value[0] - else: - return value + return value class MergeSet(MergeAction): - def __init__(self, match, merge_items=True): + def __init__( + self: Self, + match: Union[ + Callable[ + [ + Union[BASIC_TYPE, TIME_TYPE, ld_merge_dict, ld_merge_list], + Union[BASIC_TYPE, TIME_TYPE, ld_dict, ld_list] + ], + bool + ], + Callable[[ld_merge_dict, ld_dict], bool] + ], + merge_items: bool = True + ) -> None: + """ + Set the match function for this collect merge action. + + :param match: The function used to evaluate equality while merging. + :type match: Callable[ + [BASIC_TYPE | TIME_TYPE | ld_merge_dict | ld_merge_list, BASIC_TYPE | TIME_TYPE | ld_dict | ld_list], + bool + ] | Callable[[ld_merge_dict, ld_dict], bool] + :param merge_items: Whether or to to merge similar items. (If false this is basically :class:`Concat`) + :type merge_items: bool + + :return: + :rtype: None + """ self.match = match self.merge_items = merge_items - def merge(self, target, key, value, update): + def merge( + self: Self, + target: ld_merge_dict, + key: list[Union[str, int]], + value: ld_merge_list, + update: Union[BASIC_TYPE, TIME_TYPE, ld_dict, ld_list] + ) -> ld_merge_list: + """ + Merges similar items (according to :attr:`match`) from ``value`` and ``update``. + + :param target: The ld_merge_dict inside of which the items are merged. + :type target: ld_merge_dict + :param key: The "path" of keys so that parent[key[-1]] is value and + for the outermost parent of target out_parent out_parent[key[0]]...[key[-1]] results in value. + :type key: list[str | int] + :param value: The value inside target that is to be merged with update. + :type value: ld_merge_list + :param update: The value that is to be merged into target with value. + :type update: BASIC_TYPE | TIME_TYPE | ld_dict | ld_list + + :return: The merged value. + :rtype: ld_merge_list + """ + if not isinstance(update, (list, ld_list)): + update = [update] + for item in update: + # For each new item merge it into a similar item (according to match) inside target[key[-1]] + # (aka inside value) if such an item exists and merging is permitted. + # Otherwise append it to target[key[-1]] (aka to value). target_item = target.match(key[-1], item, self.match) if target_item and self.merge_items: target_item.update(item) else: value.append(item) + # Return the merged values. return value diff --git a/src/hermes/model/merge/container.py b/src/hermes/model/merge/container.py index ec9fedd9..30af9aea 100644 --- a/src/hermes/model/merge/container.py +++ b/src/hermes/model/merge/container.py @@ -5,17 +5,20 @@ # SPDX-FileContributor: Michael Meinel # SPDX-FileContributor: Michael Fritzsche -from typing import Callable, Union +from __future__ import annotations + +from typing import Callable, Union, TYPE_CHECKING from typing_extensions import Self -from hermes.model.merge.action import MergeAction -from hermes.model.types import ld_container, ld_context, ld_dict, ld_list -from hermes.model.types.ld_container import ( +from ..types import ld_container, ld_context, ld_dict, ld_list +from ..types.ld_container import ( BASIC_TYPE, EXPANDED_JSON_LD_VALUE, JSON_LD_CONTEXT_DICT, JSON_LD_VALUE, TIME_TYPE ) - -from .strategy import CODEMETA_STRATEGY, PROV_STRATEGY, REPLACE_STRATEGY from ..types.pyld_util import bundled_loader +from .strategy import CODEMETA_STRATEGY, PROV_STRATEGY, REPLACE_STRATEGY + +if TYPE_CHECKING: + from .action import MergeAction class _ld_merge_container: @@ -170,24 +173,12 @@ def update_context( :rtype: None """ if other_context: - if len(self.context) < 1 or not isinstance(self.context[-1], dict): - self.context.append({}) - - if not isinstance(other_context, list): - other_context = [other_context] - for ctx in other_context: - if isinstance(ctx, dict): - # FIXME #471: Shouldn't the dict be appended instead? - # How it is implemented currently results in anomalies like this: - # other_context = [{"codemeta": "https://doi.org/10.5063/schema/codemeta-1.0/"}] - # self.context = [{"codemeta": "https://doi.org/10.5063/schema/codemeta-2.0/"}] - # resulting context is only [{"codemeta": "https://doi.org/10.5063/schema/codemeta-1.0/"}] - # values that start with "https://doi.org/10.5063/schema/codemeta-2.0/" can't be compacted anymore - self.context[-1].update(ctx) - elif ctx not in self.context: - # FIXME #471: If multiple string values are in self.context, the others are prefered - # if the new one is inserted at the beginning. But with the dictionaries the order is reversed. - self.context.insert(0, ctx) + if not isinstance(self.context, list): + self.context = [self.context] + if isinstance(other_context, list): + self.context = [*other_context, *self.context] + else: + self.context = [other_context, *self.context] # update the active context that is used for compaction/ expansion self.active_ctx = self.ld_proc.initial_ctx(self.context, {"documentLoader": bundled_loader}) @@ -270,10 +261,7 @@ def match( :type value: Union[JSON_LD_VALUE, BASIC_TYPE, TIME_TYPE, ld_dict, ld_list] :param match: The method defining if two objects are a match. :type match: Callable[ - [ - BASIC_TYPE | TIME_TYPE | ld_merge_dict | ld_merge_list, - BASIC_TYPE | TIME_TYPE | ld_dict | ld_list - ], + [BASIC_TYPE | TIME_TYPE | ld_merge_dict | ld_merge_list, BASIC_TYPE | TIME_TYPE | ld_dict | ld_list], bool ] | Callable[[ld_merge_dict, ld_dict], bool] diff --git a/src/hermes/model/merge/match.py b/src/hermes/model/merge/match.py index 77abca35..453bfba1 100644 --- a/src/hermes/model/merge/match.py +++ b/src/hermes/model/merge/match.py @@ -3,11 +3,16 @@ # SPDX-License-Identifier: Apache-2.0 # SPDX-FileContributor: Michael Meinel +# SPDX-FileContributor: Michael Fritzsche -from typing import Any, Callable +from __future__ import annotations -from hermes.model.merge.container import ld_merge_dict -from hermes.model.types import ld_dict +from typing import Any, Callable, TYPE_CHECKING + +from ..types import ld_dict + +if TYPE_CHECKING: + from .container import ld_merge_dict def match_equals(a: Any, b: Any) -> bool: @@ -22,6 +27,8 @@ def match_equals(a: Any, b: Any) -> bool: :return: Truth value of a == b. :rtype: bool """ + print(f"a: {a}") + print(f"b: {b}") return a == b @@ -54,6 +61,7 @@ def match_func(left: ld_merge_dict, right: ld_dict) -> bool: :return: The result of the comparison. :rtype: bool """ + # TODO: This method maybe should try == comparison instead of returning false if active_keys == []. # create a list of all common important keys active_keys = [key for key in keys if key in left and key in right] # check if both objects have the same values for all active keys diff --git a/src/hermes/model/merge/strategy.py b/src/hermes/model/merge/strategy.py index 12681fe6..40c7757d 100644 --- a/src/hermes/model/merge/strategy.py +++ b/src/hermes/model/merge/strategy.py @@ -4,15 +4,14 @@ # SPDX-FileContributor: Michael Meinel -from hermes.model.types.ld_context import iri_map as iri - +from ..types.ld_context import iri_map as iri from .action import Reject, Replace, Collect, Concat, MergeSet from .match import match_equals, match_keys REPLACE_STRATEGY = { None: { - None: Replace, + None: Replace(), "@type": Collect(match_equals), }, } @@ -20,7 +19,7 @@ REJECT_STRATEGY = { None: { - None: Reject, + None: Reject(), "@type": Collect(match_equals), }, } @@ -28,9 +27,9 @@ PROV_STRATEGY = { None: { - iri["hermes-rt:graph"]: Concat, - iri["hermes-rt:replace"]: Concat, - iri["hermes-rt:reject"]: Concat, + iri["hermes-rt:graph"]: Concat(), + iri["hermes-rt:replace"]: Concat(), + iri["hermes-rt:reject"]: Concat(), }, } diff --git a/src/hermes/model/types/ld_container.py b/src/hermes/model/types/ld_container.py index b2456017..14f16161 100644 --- a/src/hermes/model/types/ld_container.py +++ b/src/hermes/model/types/ld_container.py @@ -96,17 +96,9 @@ def __init__( self.context = context or [] - # Create active context (to use with pyld) depending on the initial variables - # Re-use active context from parent if available - if self.parent: - if self.context: - self.active_ctx = self.ld_proc.process_context( - self.parent.active_ctx, self.context, {"documentLoader": bundled_loader} - ) - else: - self.active_ctx = parent.active_ctx - else: - self.active_ctx = self.ld_proc.initial_ctx(self.full_context, {"documentLoader": bundled_loader}) + # Create active context (to use with pyld) depending on the initial variables. + # Don't re-use active context from parent (created some weird in the process step when context is often added). + self.active_ctx = self.ld_proc.initial_ctx(self.full_context, {"documentLoader": bundled_loader}) def add_context(self: Self, context: list[Union[str | JSON_LD_CONTEXT_DICT]]) -> None: """ diff --git a/test/hermes_test/model/test_api.py b/test/hermes_test/model/test_api.py index 895968d7..a7495c4f 100644 --- a/test/hermes_test/model/test_api.py +++ b/test/hermes_test/model/test_api.py @@ -1,3 +1,10 @@ +# SPDX-FileCopyrightText: 2026 German Aerospace Center (DLR) +# +# SPDX-License-Identifier: Apache-2.0 + +# SPDX-FileContributor: Michael Fritzsche +# SPDX-FileContributor: Stephan Druskat + import pytest from hermes.model import SoftwareMetadata From aa4284ebf81bfb2a1cc7de383401ba069e03153c Mon Sep 17 00:00:00 2001 From: notactuallyfinn Date: Fri, 27 Feb 2026 13:14:05 +0100 Subject: [PATCH 14/19] removed unnecessary print statements --- src/hermes/model/merge/match.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/hermes/model/merge/match.py b/src/hermes/model/merge/match.py index 453bfba1..629fbee2 100644 --- a/src/hermes/model/merge/match.py +++ b/src/hermes/model/merge/match.py @@ -27,8 +27,6 @@ def match_equals(a: Any, b: Any) -> bool: :return: Truth value of a == b. :rtype: bool """ - print(f"a: {a}") - print(f"b: {b}") return a == b From 4080091be9b8bdab0de237d802fc17402f6cbcf4 Mon Sep 17 00:00:00 2001 From: notactuallyfinn Date: Tue, 3 Mar 2026 14:28:21 +0100 Subject: [PATCH 15/19] json_ids are now returned as ld_dicts instead of the id string --- src/hermes/model/types/__init__.py | 1 - src/hermes/model/types/ld_dict.py | 3 --- .../model/types/test_ld_container.py | 2 +- test/hermes_test/model/types/test_ld_dict.py | 17 ++++++++++------- 4 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/hermes/model/types/__init__.py b/src/hermes/model/types/__init__.py index 9e4b1bf5..ba6085f7 100644 --- a/src/hermes/model/types/__init__.py +++ b/src/hermes/model/types/__init__.py @@ -30,7 +30,6 @@ (lambda c: isinstance(c, list), {"ld_container": lambda c, **kw: ld_list(c, **kw)}), # pythonize items from lists (expanded set is already handled above) - (ld_container.is_json_id, {"python": lambda c, **_: c["@id"]}), (ld_container.is_typed_json_value, {"python": lambda c, **kw: ld_container.typed_ld_to_py([c], **kw)}), (ld_container.is_json_value, {"python": lambda c, **_: c["@value"]}), (ld_list.is_container, {"ld_container": lambda c, **kw: ld_list([c], **kw)}), diff --git a/src/hermes/model/types/ld_dict.py b/src/hermes/model/types/ld_dict.py index f368ec73..42bc3ed9 100644 --- a/src/hermes/model/types/ld_dict.py +++ b/src/hermes/model/types/ld_dict.py @@ -149,7 +149,4 @@ def is_json_dict(cls, ld_value): if any(k in ld_value for k in ["@set", "@graph", "@list", "@value"]): return False - if ['@id'] == [*ld_value.keys()]: - return False - return True diff --git a/test/hermes_test/model/types/test_ld_container.py b/test/hermes_test/model/types/test_ld_container.py index f73fdcd9..f0844ecd 100644 --- a/test/hermes_test/model/types/test_ld_container.py +++ b/test/hermes_test/model/types/test_ld_container.py @@ -107,7 +107,7 @@ def test_to_python_id_value(self, mock_context): assert cont._to_python("http://spam.eggs/ham", [{"@id": "http://spam.eggs/spam"}]) == [{"@id": "http://spam.eggs/spam"}] assert cont._to_python("http://spam.eggs/ham", - {"@id": "http://spam.eggs/identifier"}) == "http://spam.eggs/identifier" + {"@id": "http://spam.eggs/identifier"}) == {"@id": "http://spam.eggs/identifier"} def test_to_python_basic_value(self, mock_context): cont = ld_container([{}], context=[mock_context]) diff --git a/test/hermes_test/model/types/test_ld_dict.py b/test/hermes_test/model/types/test_ld_dict.py index 8736439d..239f92ed 100644 --- a/test/hermes_test/model/types/test_ld_dict.py +++ b/test/hermes_test/model/types/test_ld_dict.py @@ -299,13 +299,13 @@ def test_to_python(): inner_di = ld_dict([{}], parent=di) inner_di.update({"xmlns:foobar": "bar", "http://xmlns.com/foaf/0.1/barfoo": {"@id": "foo"}}) di.update({"http://xmlns.com/foaf/0.1/name": "foo", "xmlns:homepage": {"@id": "bar"}, "xmlns:foo": inner_di}) - assert di.to_python() == {"xmlns:name": ["foo"], "xmlns:homepage": ["bar"], - "xmlns:foo": [{"xmlns:foobar": ["bar"], "xmlns:barfoo": ["foo"]}]} + assert di.to_python() == {"xmlns:name": ["foo"], "xmlns:homepage": [{"@id": "bar"}], + "xmlns:foo": [{"xmlns:foobar": ["bar"], "xmlns:barfoo": [{"@id": "foo"}]}]} di.update({"http://spam.eggs/eggs": { "@value": "2022-02-22T00:00:00", "@type": "https://schema.org/DateTime" }}) - assert di.to_python() == {"xmlns:name": ["foo"], "xmlns:homepage": ["bar"], - "xmlns:foo": [{"xmlns:foobar": ["bar"], "xmlns:barfoo": ["foo"]}], + assert di.to_python() == {"xmlns:name": ["foo"], "xmlns:homepage": [{"@id": "bar"}], + "xmlns:foo": [{"xmlns:foobar": ["bar"], "xmlns:barfoo": [{"@id": "foo"}]}], "http://spam.eggs/eggs": ["2022-02-22T00:00:00"]} @@ -376,13 +376,16 @@ def test_from_dict(): def test_is_ld_dict(): assert not any(ld_dict.is_ld_dict(item) for item in [{}, {"foo": "bar"}, {"@id": "foo"}]) - assert not any(ld_dict.is_ld_dict(item) for item in [[{"@id": "foo"}], [{"@set": "foo"}], [{}, {}], [], [""]]) - assert all(ld_dict.is_ld_dict([item]) for item in [{"@id": "foo", "foobar": "bar"}, {"foo": "bar"}]) + assert not any(ld_dict.is_ld_dict(item) for item in [[{"@set": "foo"}], [{}, {}], [], [""]]) + assert all( + ld_dict.is_ld_dict([item]) + for item in [{"@id": "foo"}, {"@id": "foo", "foobar": "bar"}, {"foo": "bar"}] + ) def test_is_json_dict(): assert not any(ld_dict.is_json_dict(item) for item in [1, "", [], {""}, ld_dict([{}])]) assert not any(ld_dict.is_json_dict({key: [], "foo": "bar"}) for key in ["@set", "@graph", "@list", "@value"]) - assert not ld_dict.is_json_dict({"@id": "foo"}) + assert ld_dict.is_json_dict({"@id": "foo"}) assert ld_dict.is_json_dict({"@id": "foo", "foobar": "bar"}) assert ld_dict.is_json_dict({"foo": "bar"}) From b7543ee0d374b722807ced29b069ec2c3eb7a9c5 Mon Sep 17 00:00:00 2001 From: Michael Fritzsche Date: Thu, 5 Mar 2026 14:35:36 +0100 Subject: [PATCH 16/19] reworked merging and added strategies --- src/hermes/model/merge/action.py | 84 ++-- src/hermes/model/merge/container.py | 26 +- src/hermes/model/merge/match.py | 39 +- src/hermes/model/merge/strategy.py | 616 ++++++++++++++++++++++++- src/hermes/model/types/ld_dict.py | 3 + test/hermes_test/model/test_api_e2e.py | 79 +++- 6 files changed, 731 insertions(+), 116 deletions(-) diff --git a/src/hermes/model/merge/action.py b/src/hermes/model/merge/action.py index 08a2c084..6108b9ea 100644 --- a/src/hermes/model/merge/action.py +++ b/src/hermes/model/merge/action.py @@ -7,7 +7,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Callable, Union +from typing import TYPE_CHECKING, Any, Callable, Union from typing_extensions import Self from ..types import ld_dict, ld_list @@ -76,9 +76,8 @@ def merge( This value will always be value. :rtype: ld_merge_list """ - # If necessary, add the entry that data has been rejected. - if value != update: - target.reject(key, update) + # Add the entry that data has been rejected. + target.reject(key, update) # Return value unchanged. return value @@ -111,8 +110,7 @@ def merge( :rtype: BASIC_TYPE | TIME_TYPE | ld_dict | ld_list """ # If necessary, add the entry that data has been replaced. - if value != update: - target.replace(key, value) + target.replace(key, value) # Return the new value. return update @@ -151,32 +149,21 @@ def merge( class Collect(MergeAction): - def __init__( - self: Self, - match: Union[ - Callable[ - [ - Union[BASIC_TYPE, TIME_TYPE, ld_merge_dict, ld_merge_list], - Union[BASIC_TYPE, TIME_TYPE, ld_dict, ld_list] - ], - bool - ], - Callable[[ld_merge_dict, ld_dict], bool] - ] - ) -> None: + def __init__(self: Self, match: Callable[[Any, Any], bool], reject_incoming: bool = True) -> None: """ - Set the match function for this collect merge action. + Set the match function for this collect merge action. And the behaivior for matches. :param match: The function used to evaluate equality while merging. - :type match: Callable[ - [BASIC_TYPE | TIME_TYPE | ld_merge_dict | ld_merge_list, BASIC_TYPE | TIME_TYPE | ld_dict | ld_list], - bool - ] | Callable[[ld_merge_dict, ld_dict], bool] + :type match: Callable[[Any, Any], bool] + :param reject_incoming: If an incoming item matches an already collected one, if ``reject_incoming`` True, + the incoming item gets rejected, if ``reject_incoming`` False, the match of the incoming item gets replaced. + :type reject_incoming: bool :return: :rtype: None """ self.match = match + self.reject_incoming = reject_incoming def merge( self: Self, @@ -206,44 +193,31 @@ def merge( # iterate over all new items for update_item in update: - # If the current new item has no occurence in value (according to self.match) add it to value. - if not any(self.match(item, update_item) for item in value): + # Iterate over all items in value and if a match is found replace the first one or reject update_item. + for index, item in enumerate(value): + if self.match(item, update_item): + if not self.reject_incoming: + value[index] = update_item + break + else: + # If the current new item has no occurence in value (according to self.match) add it to value. value.append(update_item) return value class MergeSet(MergeAction): - def __init__( - self: Self, - match: Union[ - Callable[ - [ - Union[BASIC_TYPE, TIME_TYPE, ld_merge_dict, ld_merge_list], - Union[BASIC_TYPE, TIME_TYPE, ld_dict, ld_list] - ], - bool - ], - Callable[[ld_merge_dict, ld_dict], bool] - ], - merge_items: bool = True - ) -> None: + def __init__(self: Self, match: Callable[[Any, Any], bool]) -> None: """ Set the match function for this collect merge action. :param match: The function used to evaluate equality while merging. - :type match: Callable[ - [BASIC_TYPE | TIME_TYPE | ld_merge_dict | ld_merge_list, BASIC_TYPE | TIME_TYPE | ld_dict | ld_list], - bool - ] | Callable[[ld_merge_dict, ld_dict], bool] - :param merge_items: Whether or to to merge similar items. (If false this is basically :class:`Concat`) - :type merge_items: bool + :type match: Callable[[ANy, Any], bool] :return: :rtype: None """ self.match = match - self.merge_items = merge_items def merge( self: Self, @@ -271,13 +245,19 @@ def merge( if not isinstance(update, (list, ld_list)): update = [update] - for item in update: + for update_item in update: # For each new item merge it into a similar item (according to match) inside target[key[-1]] - # (aka inside value) if such an item exists and merging is permitted. + # (aka inside value) if such an item exists. # Otherwise append it to target[key[-1]] (aka to value). - target_item = target.match(key[-1], item, self.match) - if target_item and self.merge_items: - target_item.update(item) + for index, item in enumerate(value): + if self.match(item, update_item): + if isinstance(item, ld_dict) and isinstance(update_item, ld_dict): + item.update(update_item) + elif isinstance(item, ld_list) and isinstance(update_item, ld_list): + self.merge(target, [*key, index], item, update_item) + elif isinstance(item, (ld_dict, ld_list)) or isinstance(update_item, (ld_dict, ld_list)): + """ FIXME: log error """ + break else: value.append(item) # Return the merged values. diff --git a/src/hermes/model/merge/container.py b/src/hermes/model/merge/container.py index 30af9aea..2be14694 100644 --- a/src/hermes/model/merge/container.py +++ b/src/hermes/model/merge/container.py @@ -7,7 +7,7 @@ from __future__ import annotations -from typing import Callable, Union, TYPE_CHECKING +from typing import TYPE_CHECKING, Any, Callable, Union from typing_extensions import Self from ..types import ld_container, ld_context, ld_dict, ld_list @@ -15,7 +15,7 @@ BASIC_TYPE, EXPANDED_JSON_LD_VALUE, JSON_LD_CONTEXT_DICT, JSON_LD_VALUE, TIME_TYPE ) from ..types.pyld_util import bundled_loader -from .strategy import CODEMETA_STRATEGY, PROV_STRATEGY, REPLACE_STRATEGY +from .strategy import CODEMETA_STRATEGY, PROV_STRATEGY if TYPE_CHECKING: from .action import MergeAction @@ -153,8 +153,7 @@ def __init__( self.update_context(ld_context.HERMES_PROV_CONTEXT) # add strategies - self.strategies = {**REPLACE_STRATEGY} - self.add_strategy(CODEMETA_STRATEGY) + self.strategies = {**CODEMETA_STRATEGY} self.add_strategy(PROV_STRATEGY) def update_context( @@ -238,16 +237,7 @@ def match( self: Self, key: str, value: Union[BASIC_TYPE, TIME_TYPE, ld_dict, ld_list], - match: Union[ - Callable[ - [ - Union[BASIC_TYPE, TIME_TYPE, "ld_merge_dict", ld_merge_list], - Union[BASIC_TYPE, TIME_TYPE, ld_dict, ld_list] - ], - bool - ], - Callable[["ld_merge_dict", ld_dict], bool] - ] + match: Callable[[Any, Any], bool] ) -> Union[BASIC_TYPE, TIME_TYPE, "ld_merge_dict", ld_merge_list]: """ Returns the first item in self[key] for which match(item, value) returns true. @@ -260,10 +250,7 @@ def match( :param value: The value a match is searched for in self[key]. :type value: Union[JSON_LD_VALUE, BASIC_TYPE, TIME_TYPE, ld_dict, ld_list] :param match: The method defining if two objects are a match. - :type match: Callable[ - [BASIC_TYPE | TIME_TYPE | ld_merge_dict | ld_merge_list, BASIC_TYPE | TIME_TYPE | ld_dict | ld_list], - bool - ] | Callable[[ld_merge_dict, ld_dict], bool] + :type match: Callable[[Any, Any], bool] :return: The item in self[key] that is a match to value if one exists else None :rtype: BASIC_TYPE | TIME_TYPE | ld_merge_dict | ld_merge_list @@ -317,6 +304,7 @@ def _add_related( :return: :rtype: None """ + # FIXME: key not only string # make sure appending is possible self.emplace(rel) # append the new entry @@ -338,6 +326,7 @@ def reject(self: Self, key: str, value: Union[BASIC_TYPE, TIME_TYPE, ld_dict, ld :return: :rtype: None """ + # FIXME: key not only string self._add_related("hermes-rt:reject", key, value) def replace(self: Self, key: str, value: Union[BASIC_TYPE, TIME_TYPE, ld_dict, ld_list]) -> None: @@ -356,4 +345,5 @@ def replace(self: Self, key: str, value: Union[BASIC_TYPE, TIME_TYPE, ld_dict, l :return: :rtype: None """ + # FIXME: key not only string self._add_related("hermes-rt:replace", key, value) diff --git a/src/hermes/model/merge/match.py b/src/hermes/model/merge/match.py index 629fbee2..3934b785 100644 --- a/src/hermes/model/merge/match.py +++ b/src/hermes/model/merge/match.py @@ -5,15 +5,10 @@ # SPDX-FileContributor: Michael Meinel # SPDX-FileContributor: Michael Fritzsche -from __future__ import annotations - -from typing import Any, Callable, TYPE_CHECKING +from typing import Any, Callable from ..types import ld_dict -if TYPE_CHECKING: - from .container import ld_merge_dict - def match_equals(a: Any, b: Any) -> bool: """ @@ -30,26 +25,29 @@ def match_equals(a: Any, b: Any) -> bool: return a == b -def match_keys( - *keys: list[str] -) -> Callable[[ld_merge_dict, ld_dict], bool]: +def match_keys(*keys: list[str], fall_back_to_equals: bool = False) -> Callable[[Any, Any], bool]: """ Creates a function taking to parameters that returns true if both given parameter have at least one common key in the given list of keys - and for all common keys in the given list of keys the values of both objects are the same. + and for all common keys in the given list of keys the values of both objects are the same.
+ If fall_back_to_equals is True, the returned function returns the value of normal == comparison + if no key from keys is in both objects. :param keys: The list of important keys for the comparison method. :type keys: list[str] + :param fall_back_to_equals: Whether or not a fall back option should be used. + :type fall_back_to_equals: bool :return: A function comparing two given objects values for the keys in keys. :rtype: Callable[[ld_merge_dict, ld_dict], bool] """ # create and return the match function using the given keys - def match_func(left: ld_merge_dict, right: ld_dict) -> bool: + def match_func(left: Any, right: Any) -> bool: """ Compares left to right by checking if a) they have at least one common key in a predetermined list of keys and - b) testing if both objects have equal values for all common keys in the predetermined key list. + b) testing if both objects have equal values for all common keys in the predetermined key list.
+ It may fall back on == if no common key in the predetermined list of keys exists. :param left: The first object for the comparison. :type left: ld_merge_dict @@ -59,12 +57,27 @@ def match_func(left: ld_merge_dict, right: ld_dict) -> bool: :return: The result of the comparison. :rtype: bool """ - # TODO: This method maybe should try == comparison instead of returning false if active_keys == []. + if not (isinstance(left, ld_dict) and isinstance(right, ld_dict)): + return fall_back_to_equals and (left == right) # create a list of all common important keys active_keys = [key for key in keys if key in left and key in right] + # fall back to == if no active keys + if fall_back_to_equals and not active_keys: + return left == right # check if both objects have the same values for all active keys pairs = [(left[key] == right[key]) for key in active_keys] # return whether or not both objects had the same values for all active keys # and there was at least one active key return len(active_keys) > 0 and all(pairs) return match_func + + +def match_person(left: Any, right: Any) -> bool: + if not (isinstance(left, ld_dict) and isinstance(right, ld_dict)): + return left == right + if "@id" in left and "@id" in right: + return left["@id"] == right["@id"] + if "schema:email" in left and "schema:email" in right: + mails_right = right["schema:email"] + return any((mail in mails_right) for mail in left["schema:email"]) + return left == right diff --git a/src/hermes/model/merge/strategy.py b/src/hermes/model/merge/strategy.py index 40c7757d..e928a4fc 100644 --- a/src/hermes/model/merge/strategy.py +++ b/src/hermes/model/merge/strategy.py @@ -3,39 +3,611 @@ # SPDX-License-Identifier: Apache-2.0 # SPDX-FileContributor: Michael Meinel +# SPDX-FileContributor: Michael Fritzsche from ..types.ld_context import iri_map as iri -from .action import Reject, Replace, Collect, Concat, MergeSet -from .match import match_equals, match_keys +from .action import Concat, MergeSet +from .match import match_keys, match_person -REPLACE_STRATEGY = { - None: { - None: Replace(), - "@type": Collect(match_equals), - }, +ACTIONS = { + "default": MergeSet(match_keys("@id", fall_back_to_equals=True)), + "merge_match_person": MergeSet(match_person) } -REJECT_STRATEGY = { - None: { - None: Reject(), - "@type": Collect(match_equals), - }, +PROV_STRATEGY = { + None: {iri["hermes-rt:graph"]: Concat(), iri["hermes-rt:replace"]: Concat(), iri["hermes-rt:reject"]: Concat()} } +# All troublesome marked entries can contain objects of different types, e.g. Person and Organization. +# This is troublesome because Persons may be compared using a different method than Organizations. -PROV_STRATEGY = { - None: { - iri["hermes-rt:graph"]: Concat(), - iri["hermes-rt:replace"]: Concat(), - iri["hermes-rt:reject"]: Concat(), - }, +# Filled with entries for every schema-type that can be found inside an JSON-LD dict of type +# SoftwareSourceCode or SoftwareApplication. +CODEMETA_STRATEGY = {None: {None: ACTIONS["default"]}} + +CODEMETA_STRATEGY[iri["schema:Thing"]] = {iri["schema:owner"]: None} # FIXME: troublesome Organization or Person + +CODEMETA_STRATEGY[iri["schema:CreativeWork"]] = { + **CODEMETA_STRATEGY[iri["schema:Thing"]], + iri["schema:accountablePerson"]: ACTIONS["merge_match_person"], + iri["schema:audio"]: None, # FIXME: troublesome AudioObject or Clip or MusicRecording + iri["schema:author"]: None, # FIXME: troublesome Organization or Person + iri["schema:character"]: ACTIONS["merge_match_person"], + iri["schema:contributor"]: None, # FIXME: troublesome Organization or Person + iri["schema:copyrightHolder"]: None, # FIXME: troublesome Organization or Person + iri["schema:creator"]: None, # FIXME: troublesome Organization or Person + iri["schema:editor"]: ACTIONS["merge_match_person"], + iri["schema:funder"]: None, # FIXME: troublesome Organization or Person + iri["schema:isBasedOn"]: None, # FIXME: troublesome CreativeWork or Product + iri["schema:maintainer"]: None, # FIXME: troublesome Organization or Person + iri["schema:offers"]: None, # FIXME: troublesome Demand or Offer + iri["schema:producer"]: None, # FIXME: troublesome Organization or Person + iri["schema:provider"]: None, # FIXME: troublesome Organization or Person + iri["schema:publisher"]: None, # FIXME: troublesome Organization or Person + iri["schema:sdPublisher"]: None, # FIXME: troublesome Organization or Person + iri["schema:size"]: None, # FIXME: troublesome DefinedTerm or QuantitativeValue or SizeSpecification + iri["schema:sponsor"]: None, # FIXME: troublesome Organization or Person + iri["schema:translator"]: None, # FIXME: troublesome Organization or Person + iri["schema:video"]: None # FIXME: troublesome Clip or VideoObject +} +CODEMETA_STRATEGY[iri["schema:SoftwareSourceCode"]] = { + **CODEMETA_STRATEGY[iri["schema:CreativeWork"]], + iri["maintainer"]: ACTIONS["merge_match_person"] +} +CODEMETA_STRATEGY[iri["schema:MediaObject"]] = { + **CODEMETA_STRATEGY[iri["schema:CreativeWork"]], + iri["schema:duration"]: None, # FIXME: troublesome Duration or QuantitativeValue + iri["schema:height"]: None, # FIXME: troublesome Distance or QuantitativeValue + iri["schema:ineligibleRegion"]: None, # FIXME: troublesome GeoShape or Place + iri["schema:width"]: None # FIXME: troublesome Distance or QuantitativeValue +} +CODEMETA_STRATEGY[iri["schema:AudioObject"]] = {**CODEMETA_STRATEGY[iri["schema:MediaObject"]]} +CODEMETA_STRATEGY[iri["schema:ImageObject"]] = {**CODEMETA_STRATEGY[iri["schema:MediaObject"]]} +CODEMETA_STRATEGY[iri["schema:VideoObject"]] = { + **CODEMETA_STRATEGY[iri["schema:MediaObject"]], + iri["schema:actor"]: None, # FIXME: troublesome PerformingGroup or Person + iri["schema:dircetor"]: ACTIONS["merge_match_person"], + iri["schema:musicBy"]: None # FIXME: troublesome MusicGroup or Person +} +CODEMETA_STRATEGY[iri["schema:DataDownload"]] = {**CODEMETA_STRATEGY[iri["schema:MediaObject"]]} +CODEMETA_STRATEGY[iri["schema:Certification"]] = {**CODEMETA_STRATEGY[iri["schema:CreativeWork"]]} +CODEMETA_STRATEGY[iri["schema:Claim"]] = { + **CODEMETA_STRATEGY[iri["schema:CreativeWork"]], + iri["schema:claimInterpreter"]: None # FIXME: troublesome Organization or Person +} +CODEMETA_STRATEGY[iri["schema:Clip"]] = { + **CODEMETA_STRATEGY[iri["schema:CreativeWork"]], + iri["schema:actor"]: None, # FIXME: troublesome PerformingGroup or Person + iri["schema:dircetor"]: ACTIONS["merge_match_person"], + iri["schema:musicBy"]: None # FIXME: troublesome MusicGroup or Person +} +CODEMETA_STRATEGY[iri["schema:Comment"]] = { + **CODEMETA_STRATEGY[iri["schema:CreativeWork"]], + iri["schema:parentItem"]: None # FIXME: troublesome Comment or CreativeWork +} +CODEMETA_STRATEGY[iri["schema:CorrectionComment"]] = {**CODEMETA_STRATEGY[iri["schema:Comment"]]} +CODEMETA_STRATEGY[iri["schema:CreativeWorkSeason"]] = { + **CODEMETA_STRATEGY[iri["schema:CreativeWork"]], + iri["schema:actor"]: None # FIXME: troublesome PerformingGroup or Person +} +CODEMETA_STRATEGY[iri["schema:DefinedTermSet"]] = {**CODEMETA_STRATEGY[iri["schema:CreativeWork"]]} +CODEMETA_STRATEGY[iri["schema:CategoryCodeSet"]] = {**CODEMETA_STRATEGY[iri["schema:DefinedTermSet"]]} +CODEMETA_STRATEGY[iri["schema:Episode"]] = { + **CODEMETA_STRATEGY[iri["schema:CreativeWork"]], + iri["schema:actor"]: None, # FIXME: troublesome PerformingGroup or Person + iri["schema:dircetor"]: ACTIONS["merge_match_person"], + iri["schema:duration"]: None, # FIXME: troublesome Duration or QuantitativeValue + iri["schema:musicBy"]: None # FIXME: troublesome MusicGroup or Person +} +CODEMETA_STRATEGY[iri["schema:HowTo"]] = { + **CODEMETA_STRATEGY[iri["schema:CreativeWork"]], + iri["schema:step"]: None # FIXME: troublesome CreativeWork or HowToSection or HowToStep +} +CODEMETA_STRATEGY[iri["schema:HyperTocEntry"]] = {**CODEMETA_STRATEGY[iri["schema:CreativeWork"]]} +CODEMETA_STRATEGY[iri["schema:Map"]] = {**CODEMETA_STRATEGY[iri["schema:CreativeWork"]]} +CODEMETA_STRATEGY[iri["schema:MenuSection"]] = {**CODEMETA_STRATEGY[iri["schema:CreativeWork"]]} +CODEMETA_STRATEGY[iri["schema:MusicRecording"]] = { + **CODEMETA_STRATEGY[iri["schema:CreativeWork"]], + iri["schema:byArtist"]: None, # FIXME: troublesome MusicGroup or Person + iri["schema:duration"]: None # FIXME: troublesome Duration or QuantitativeValue +} +CODEMETA_STRATEGY[iri["schema:WebPage"]] = { + **CODEMETA_STRATEGY[iri["schema:CreativeWork"]], + iri["schema:reviewedBy"]: None # FIXME: troublesome Organization or Person +} +CODEMETA_STRATEGY[iri["schema:AboutPage"]] = {**CODEMETA_STRATEGY[iri["schema:WebPage"]]} +CODEMETA_STRATEGY[iri["schema:Article"]] = {**CODEMETA_STRATEGY[iri["schema:CreativeWork"]]} +CODEMETA_STRATEGY[iri["schema:NewsArticle"]] = {**CODEMETA_STRATEGY[iri["schema:Article"]]} +CODEMETA_STRATEGY[iri["schema:ScholarlyArticle"]] = {**CODEMETA_STRATEGY[iri["schema:Article"]]} +CODEMETA_STRATEGY[iri["schema:WebPageElement"]] = {**CODEMETA_STRATEGY[iri["schema:CreativeWork"]]} +CODEMETA_STRATEGY[iri["schema:EducationalOccupationalCredential"]] = {**CODEMETA_STRATEGY[iri["schema:CreativeWork"]]} +CODEMETA_STRATEGY[iri["schema:MusicPlaylist"]] = { + **CODEMETA_STRATEGY[iri["schema:CreativeWork"]], + iri["schema:track"]: None # FIXME: troublesome ItemList or MusicRecording +} +CODEMETA_STRATEGY[iri["schema:MusicAlbum"]] = { + **CODEMETA_STRATEGY[iri["schema:MusicPlaylist"]], + iri["schema:byArtist"]: None, # FIXME: troublesome MusicGroup or Person +} +CODEMETA_STRATEGY[iri["schema:MusicRelease"]] = { + **CODEMETA_STRATEGY[iri["schema:MusicPlaylist"]], + iri["schema:creditedTo"]: None, # FIXME: troublesome Organization or Person + iri["schema:duration"]: None # FIXME: troublesome Duration or QuantitativeValue +} +CODEMETA_STRATEGY[iri["schema:MusicComposition"]] = { + **CODEMETA_STRATEGY[iri["schema:CreativeWork"]], + iri["schema:composer"]: None, # FIXME: troublesome Organization or Person + iri["schema:lyricist"]: ACTIONS["merge_match_person"], +} +CODEMETA_STRATEGY[iri["schema:Photograph"]] = {**CODEMETA_STRATEGY[iri["schema:CreativeWork"]]} +CODEMETA_STRATEGY[iri["schema:Review"]] = { + **CODEMETA_STRATEGY[iri["schema:CreativeWork"]], + iri["schema:negativeNotes"]: None, # FIXME: troublesome ItemList or ListItem or WebContent + iri["schema:positiveNotes"]: None # FIXME: troublesome ItemList or ListItem or WebContent +} +CODEMETA_STRATEGY[iri["schema:SoftwareApplication"]] = {**CODEMETA_STRATEGY[iri["schema:CreativeWork"]]} +CODEMETA_STRATEGY[iri["schema:RuntimePlatform"]] = {**CODEMETA_STRATEGY[iri["schema:SoftwareApplication"]]} +CODEMETA_STRATEGY[iri["schema:OperatingSystem"]] = {**CODEMETA_STRATEGY[iri["schema:SoftwareApplication"]]} +CODEMETA_STRATEGY[iri["schema:WebSite"]] = {**CODEMETA_STRATEGY[iri["schema:CreativeWork"]]} +CODEMETA_STRATEGY[iri["schema:WebContent"]] = {**CODEMETA_STRATEGY[iri["schema:CreativeWork"]]} +CODEMETA_STRATEGY[iri["schema:DataCatalog"]] = {**CODEMETA_STRATEGY[iri["schema:CreativeWork"]]} +CODEMETA_STRATEGY[iri["schema:Dataset"]] = { + **CODEMETA_STRATEGY[iri["schema:CreativeWork"]], + iri["schema:variableMeasured"]: None # FIXME: troublesome Property or PropertyValue or StatisticalVariable +} +CODEMETA_STRATEGY[iri["schema:DataFeed"]] = { + **CODEMETA_STRATEGY[iri["schema:Dataset"]], + iri["schema:dataFeedElement"]: None # FIXME: troublesome DataFeedItem or Thing } +CODEMETA_STRATEGY[iri["schema:Action"]] = { + **CODEMETA_STRATEGY[iri["schema:Thing"]], + iri["schema:agent"]: None, # FIXME: troublesome Organization or Person + iri["schema:location"]: None, # FIXME: troublesome Place or PostalAddress or VirtualLocation + iri["schema:participant"]: None, # FIXME: troublesome Organization or Person + iri["schema:provider"]: None # FIXME: troublesome Organization or Person +} -CODEMETA_STRATEGY = { - iri["schema:SoftwareSourceCode"]: { - iri["schema:author"]: MergeSet(match_keys('@id', iri['schema:email'])), - }, +CODEMETA_STRATEGY[iri["schema:Intangible"]] = {**CODEMETA_STRATEGY[iri["schema:Thing"]]} +CODEMETA_STRATEGY[iri["schema:Rating"]] = { + **CODEMETA_STRATEGY[iri["schema:Intangible"]], + iri["schema:author"]: None # FIXME: troublesome Organization or Person +} +CODEMETA_STRATEGY[iri["schema:AggregateRating"]] = {**CODEMETA_STRATEGY[iri["schema:Rating"]]} +CODEMETA_STRATEGY[iri["schema:AlignmentObject"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} +CODEMETA_STRATEGY[iri["schema:Audience"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} +CODEMETA_STRATEGY[iri["schema:ComputerLanguage"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} +CODEMETA_STRATEGY[iri["schema:Series"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} +CODEMETA_STRATEGY[iri["schema:DefinedTerm"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} +CODEMETA_STRATEGY[iri["schema:CategoryCode"]] = {**CODEMETA_STRATEGY[iri["schema:DefinedTerm"]]} +CODEMETA_STRATEGY[iri["schema:Demand"]] = { + **CODEMETA_STRATEGY[iri["schema:Intangible"]], + iri["schema:acceptedPaymentMethod"]: None, # FIXME: troublesome LoanOrCredit or PaymentMethod + iri["schema:areaServed"]: None, # FIXME: troublesome AdministrativeArea or GeoShape or Place + iri["schema:eligibleRegion"]: None, # FIXME: troublesome GeoShape or Place + iri["schema:ineligibleRegion"]: None, # FIXME: troublesome GeoShape or Place + iri["schema:itemOffered"]: None, # FIXME: troublesome AggregateOffer or CreativeWork or Event or MenuItem or Product or Service or Trip + iri["schema:seller"]: None # FIXME: troublesome Organization or Person +} +CODEMETA_STRATEGY[iri["schema:Offer"]] = { + **CODEMETA_STRATEGY[iri["schema:Intangible"]], + iri["schema:acceptedPaymentMethod"]: None, # FIXME: troublesome LoanOrCredit or PaymentMethod + iri["schema:areaServed"]: None, # FIXME: troublesome AdministrativeArea or GeoShape or Place + iri["schema:category"]: None, # FIXME: troublesome CategoryCode or Thing + iri["schema:eligibleRegion"]: None, # FIXME: troublesome GeoShape or Place + iri["schema:ineligibleRegion"]: None, # FIXME: troublesome GeoShape or Place + iri["schema:itemOffered"]: None, # FIXME: troublesome AggregateOffer or CreativeWork or Event or MenuItem or Product or Service or Trip + iri["schema:leaseLength"]: None, # FIXME: troublesome Duration or QuantitativeValue + iri["schema:offeredBy"]: None, # FIXME: troublesome Organization or Person + iri["schema:seller"]: None, # FIXME: troublesome Organization or Person +} +CODEMETA_STRATEGY[iri["schema:AggregateOffer"]] = { + **CODEMETA_STRATEGY[iri["schema:Offer"]], + iri["schema:offers"]: None # FIXME: troublesome Demand or Offer +} +CODEMETA_STRATEGY[iri["schema:Quantity"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} +CODEMETA_STRATEGY[iri["schema:Duration"]] = {**CODEMETA_STRATEGY[iri["schema:Quantity"]]} +CODEMETA_STRATEGY[iri["schema:Energy"]] = {**CODEMETA_STRATEGY[iri["schema:Quantity"]]} +CODEMETA_STRATEGY[iri["schema:Mass"]] = {**CODEMETA_STRATEGY[iri["schema:Quantity"]]} +CODEMETA_STRATEGY[iri["schema:EntryPoint"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} +CODEMETA_STRATEGY[iri["schema:StructuredValue"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} +CODEMETA_STRATEGY[iri["schema:GeoCoordinates"]] = {**CODEMETA_STRATEGY[iri["schema:StructuredValue"]]} +CODEMETA_STRATEGY[iri["schema:GeoShape"]] = {**CODEMETA_STRATEGY[iri["schema:StructuredValue"]]} +CODEMETA_STRATEGY[iri["schema:NutritionInformation"]] = {**CODEMETA_STRATEGY[iri["schema:StructuredValue"]]} +CODEMETA_STRATEGY[iri["schema:MonetaryAmount"]] = {**CODEMETA_STRATEGY[iri["schema:StructuredValue"]]} +CODEMETA_STRATEGY[iri["schema:Distance"]] = {**CODEMETA_STRATEGY[iri["schema:StructuredValue"]]} +CODEMETA_STRATEGY[iri["schema:PostalCodeRangeSpecification"]] = {**CODEMETA_STRATEGY[iri["schema:StructuredValue"]]} +CODEMETA_STRATEGY[iri["schema:OpeningHoursSpecification"]] = {**CODEMETA_STRATEGY[iri["schema:StructuredValue"]]} +CODEMETA_STRATEGY[iri["schema:RepaymentSpecification"]] = {**CODEMETA_STRATEGY[iri["schema:StructuredValue"]]} +CODEMETA_STRATEGY[iri["schema:WarrantyPromise"]] = {**CODEMETA_STRATEGY[iri["schema:StructuredValue"]]} +CODEMETA_STRATEGY[iri["schema:ShippingRateSettings"]] = { + **CODEMETA_STRATEGY[iri["schema:StructuredValue"]], + iri["schema:shippingRate"]: None # FIXME: troublesome MonetaryAmount or ShippingRateSettings +} +CODEMETA_STRATEGY[iri["schema:InteractionCounter"]] = { + **CODEMETA_STRATEGY[iri["schema:StructuredValue"]], + iri["schema:interactionService"]: None, # FIXME: troublesome SoftwareApplication or WebSite + iri["schema:location"]: None # FIXME: troublesome Place or PostalAddress or VirtualLocation +} +CODEMETA_STRATEGY[iri["schema:PropertyValue"]] = { + **CODEMETA_STRATEGY[iri["schema:StructuredValue"]], + iri["schema:valueReference"]: None # FIXME: troublesome DefinedTerm or Enumeration or PropertyValue or QualitativeValue or QuantitativeValue or StructuredValue +} +CODEMETA_STRATEGY[iri["schema:ContactPoint"]] = { + **CODEMETA_STRATEGY[iri["schema:StructuredValue"]], + iri["schema:areaServed"]: None, # FIXME: troublesome AdministrativeArea or GeoShape or Place +} +CODEMETA_STRATEGY[iri["schema:PostalAddress"]] = {**CODEMETA_STRATEGY[iri["schema:ContactPoint"]]} +CODEMETA_STRATEGY[iri["schema:OfferShippingDetails"]] = { + **CODEMETA_STRATEGY[iri["schema:StructuredValue"]], + iri["schema:depth"]: None, # FIXME: troublesome Distance or QuantitativeValue + iri["schema:height"]: None, # FIXME: troublesome Distance or QuantitativeValue + iri["schema:shippingRate"]: None, # FIXME: troublesome MonetaryAmount or ShippingRateSettings + iri["schema:weight"]: None, # FIXME: troublesome Mass or QuantitativeValue + iri["schema:width"]: None # FIXME: troublesome Distance or QuantitativeValue +} +CODEMETA_STRATEGY[iri["schema:ShippingDeliveryTime"]] = { + **CODEMETA_STRATEGY[iri["schema:StructuredValue"]], + iri["schema:handlingTime"]: None, # FIXME: troublesome QuantitativeValue or ServicePeriod + iri["schema:transitTime"]: None # FIXME: troublesome QuantitativeValue or ServicePeriod +} +CODEMETA_STRATEGY[iri["schema:TypeAndQuantityNode"]] = { + **CODEMETA_STRATEGY[iri["schema:StructuredValue"]], + iri["schema:typeOfGood"]: None # FIXME: troublesome Product or Service +} +CODEMETA_STRATEGY[iri["schema:ServicePeriod"]] = { + **CODEMETA_STRATEGY[iri["schema:StructuredValue"]], + iri["schema:duration"]: None # FIXME: troublesome Duration or QuantitativeValue +} +CODEMETA_STRATEGY[iri["schema:QuantitativeValue"]] = { + **CODEMETA_STRATEGY[iri["schema:StructuredValue"]], + iri["schema:valueReference"]: None # FIXME: troublesome DefinedTerm or Enumeration or PropertyValue or QualitativeValue or QuantitativeValue or StructuredValue +} +CODEMETA_STRATEGY[iri["schema:ShippingService"]] = { + **CODEMETA_STRATEGY[iri["schema:StructuredValue"]], + iri["schema:handlingTime"]: None # FIXME: troublesome QuantitativeValue or ServicePeriod +} +CODEMETA_STRATEGY[iri["schema:ShippingConditions"]] = { + **CODEMETA_STRATEGY[iri["schema:StructuredValue"]], + iri["schema:depth"]: None, # FIXME: troublesome Distance or QuantitativeValue + iri["schema:height"]: None, # FIXME: troublesome Distance or QuantitativeValue + iri["schema:shippingRate"]: None, # FIXME: troublesome MonetaryAmount or ShippingRateSettings + iri["schema:transitTime"]: None, # FIXME: troublesome QuantitativeValue or ServicePeriod + iri["schema:weight"]: None, # FIXME: troublesome Mass or QuantitativeValue + iri["schema:width"]: None # FIXME: troublesome Distance or QuantitativeValue +} +CODEMETA_STRATEGY[iri["schema:QuantitativeValueDistribution"]] = { + **CODEMETA_STRATEGY[iri["schema:StructuredValue"]], + iri["schema:duration"]: None # FIXME: troublesome Duration or QuantitativeValue +} +CODEMETA_STRATEGY[iri["schema:MonetaryAmountDistribution"]] = { + **CODEMETA_STRATEGY[iri["schema:QuantitativeValueDistribution"]] +} +CODEMETA_STRATEGY[iri["schema:PriceSpecification"]] = {**CODEMETA_STRATEGY[iri["schema:StructuredValue"]]} +CODEMETA_STRATEGY[iri["schema:UnitPriceSpecification"]] = { + **CODEMETA_STRATEGY[iri["schema:PriceSpecification"]], + iri["schema:billingDuration"]: None, # FIXME: troublesome Duration or QuantitativeValue +} +CODEMETA_STRATEGY[iri["schema:DeliveryChargeSpecification"]] = { + **CODEMETA_STRATEGY[iri["schema:PriceSpecification"]], + iri["schema:areaServed"]: None, # FIXME: troublesome AdministrativeArea or GeoShape or Place + iri["schema:eligibleRegion"]: None, # FIXME: troublesome GeoShape or Place + iri["schema:ineligibleRegion"]: None # FIXME: troublesome GeoShape or Place +} +CODEMETA_STRATEGY[iri["schema:LocationFeatureSpecification"]] = {**CODEMETA_STRATEGY[iri["schema:PropertyValue"]]} +CODEMETA_STRATEGY[iri["schema:GeospatialGeometry"]] = { + **CODEMETA_STRATEGY[iri["schema:Intangible"]], + iri["schema:geoContains"]: None, # FIXME: troublesome GeospatialGeometry or Place + iri["schema:geoCoveredBy"]: None, # FIXME: troublesome GeospatialGeometry or Place + iri["schema:geoCovers"]: None, # FIXME: troublesome GeospatialGeometry or Place + iri["schema:geoCrosses"]: None, # FIXME: troublesome GeospatialGeometry or Place + iri["schema:geoDisjoint"]: None, # FIXME: troublesome GeospatialGeometry or Place + iri["schema:geoEquals"]: None, # FIXME: troublesome GeospatialGeometry or Place + iri["schema:geoIntersects"]: None, # FIXME: troublesome GeospatialGeometry or Place + iri["schema:geoOverlaps"]: None, # FIXME: troublesome GeospatialGeometry or Place + iri["schema:geoTouches"]: None, # FIXME: troublesome GeospatialGeometry or Place + iri["schema:geoWithin"]: None # FIXME: troublesome GeospatialGeometry or Place +} +CODEMETA_STRATEGY[iri["schema:Grant"]] = { + **CODEMETA_STRATEGY[iri["schema:Intangible"]], + iri["schema:fundedItem"]: None, # FIXME: troublesome BioChemEntity or CreativeWork or Event or MedicalEntity or Organization or Person or Product + iri["schema:funder"]: None, # FIXME: troublesome Organization or Person + iri["schema:sponsor"]: None # FIXME: troublesome Organization or Person +} +CODEMETA_STRATEGY[iri["schema:ItemList"]] = { + **CODEMETA_STRATEGY[iri["schema:Intangible"]], + iri["schema:itemListElement"]: None # FIXME: troublesome ListItem or Thing +} +CODEMETA_STRATEGY[iri["schema:OfferCatalog"]] = {**CODEMETA_STRATEGY[iri["schema:ItemList"]]} +CODEMETA_STRATEGY[iri["schema:BreadcrumbList"]] = {**CODEMETA_STRATEGY[iri["schema:ItemList"]]} +CODEMETA_STRATEGY[iri["schema:Language"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} +CODEMETA_STRATEGY[iri["schema:Service"]] = { + **CODEMETA_STRATEGY[iri["schema:Intangible"]], + iri["schema:areaServed"]: None, # FIXME: troublesome AdministrativeArea or GeoShape or Place + iri["schema:brand"]: None, # FIXME: troublesome Brand or Organization + iri["schema:broker"]: None, # FIXME: troublesome Organization or Person + iri["schema:category"]: None, # FIXME: troublesome CategoryCode or Thing + iri["schema:isRelatedTo"]: None, # FIXME: troublesome Product or Service + iri["schema:isSimilarTo"]: None, # FIXME: troublesome Product or Service + iri["schema:offers Demand"]: None, # FIXME: troublesome or Offer + iri["schema:provider"]: None # FIXME: troublesome Organization or Person +} +CODEMETA_STRATEGY[iri["schema:FinancialProduct"]] = {**CODEMETA_STRATEGY[iri["schema:Service"]]} +CODEMETA_STRATEGY[iri["schema:BroadcastService"]] = {**CODEMETA_STRATEGY[iri["schema:Service"]]} +CODEMETA_STRATEGY[iri["schema:CableOrSatelliteService"]] = {**CODEMETA_STRATEGY[iri["schema:Service"]]} +CODEMETA_STRATEGY[iri["schema:LoanOrCredit"]] = {**CODEMETA_STRATEGY[iri["schema:FinancialProduct"]]} +CODEMETA_STRATEGY[iri["schema:MediaSubscription"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} +CODEMETA_STRATEGY[iri["schema:Brand"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} +CODEMETA_STRATEGY[iri["schema:HealthInsurancePlan"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} +CODEMETA_STRATEGY[iri["schema:ListItem"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} +CODEMETA_STRATEGY[iri["schema:HowToItem"]] = {**CODEMETA_STRATEGY[iri["schema:ListItem"]]} +CODEMETA_STRATEGY[iri["schema:HowToSupply"]] = {**CODEMETA_STRATEGY[iri["schema:HowToItem"]]} +CODEMETA_STRATEGY[iri["schema:HowToTool"]] = {**CODEMETA_STRATEGY[iri["schema:HowToItem"]]} +CODEMETA_STRATEGY[iri["schema:Enumeration"]] = { + **CODEMETA_STRATEGY[iri["schema:Intangible"]], + iri["schema:supersededBy"]: None # FIXME: troublesome Class or Enumeration } +CODEMETA_STRATEGY[iri["schema:QualitativeValue"]] = { + **CODEMETA_STRATEGY[iri["schema:Enumeration"]], + iri["schema:valueReference"]: None # FIXME: troublesome DefinedTerm or Enumeration or PropertyValue or QualitativeValue or QuantitativeValue or StructuredValue +} +CODEMETA_STRATEGY[iri["schema:SizeSpecification"]] = {**CODEMETA_STRATEGY[iri["schema:QualitativeValue"]]} +CODEMETA_STRATEGY[iri["schema:Class"]] = { + **CODEMETA_STRATEGY[iri["schema:Intangible"]], + iri["schema:supersededBy"]: None # FIXME: troublesome Class or Enumeration +} +CODEMETA_STRATEGY[iri["schema:HealthPlanFormulary"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} +CODEMETA_STRATEGY[iri["schema:HealthPlanCostSharingSpecification"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} +CODEMETA_STRATEGY[iri["schema:HealthPlanNetwork"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} +CODEMETA_STRATEGY[iri["schema:MemberProgramTier"]] = { + **CODEMETA_STRATEGY[iri["schema:Intangible"]], + iri["schema:hasTierRequirement"]: None # FIXME: troublesome CreditCard or MonetaryAmount or UnitPriceSpecification +} +CODEMETA_STRATEGY[iri["schema:MemberProgram"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} +CODEMETA_STRATEGY[iri["schema:MenuItem"]] = { + **CODEMETA_STRATEGY[iri["schema:Intangible"]], + iri["schema:menuAddOn"]: None, # FIXME: troublesome MenuItem or MenuSection + iri["schema:offers"]: None # FIXME: troublesome Demand or Offer +} +CODEMETA_STRATEGY[iri["schema:MerchantReturnPolicy"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} +CODEMETA_STRATEGY[iri["schema:MerchantReturnPolicySeasonalOverride"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} +CODEMETA_STRATEGY[iri["schema:SpeakableSpecification"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} +CODEMETA_STRATEGY[iri["schema:PaymentMethod"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} +CODEMETA_STRATEGY[iri["schema:ProgramMembership"]] = { + **CODEMETA_STRATEGY[iri["schema:Intangible"]], + iri["schema:member"]: None # FIXME: troublesome Organization or Person +} +CODEMETA_STRATEGY[iri["schema:Schedule"]] = { + **CODEMETA_STRATEGY[iri["schema:Intangible"]], + iri["schema:duration"]: None # FIXME: troublesome Duration or QuantitativeValue +} +CODEMETA_STRATEGY[iri["schema:ServiceChannel"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} +CODEMETA_STRATEGY[iri["schema:VirtualLocation"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} +CODEMETA_STRATEGY[iri["schema:Occupation"]] = { + **CODEMETA_STRATEGY[iri["schema:Intangible"]], + iri["schema:estimatedSalary"]: None # FIXME: troublesome MonetaryAmount or MonetaryAmountDistribution +} +CODEMETA_STRATEGY[iri["schema:EnergyConsumptionDetails"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} +CODEMETA_STRATEGY[iri["schema:OccupationalExperienceRequirements"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} +CODEMETA_STRATEGY[iri["schema:AlignmentObject"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} +CODEMETA_STRATEGY[iri["schema:BroadcastFrequencySpecification"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} +CODEMETA_STRATEGY[iri["schema:BroadcastChannel"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} +CODEMETA_STRATEGY[iri["schema:ConstraintNode"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} +CODEMETA_STRATEGY[iri["schema:StatisticalVariable"]] = {**CODEMETA_STRATEGY[iri["schema:ConstraintNode"]]} +CODEMETA_STRATEGY[iri["schema:Property"]] = { + **CODEMETA_STRATEGY[iri["schema:Intangible"]], + iri["schema:supersededBy"]: None, # FIXME: troublesome Class or Enumeration or Property +} + +CODEMETA_STRATEGY[iri["schema:Place"]] = { + **CODEMETA_STRATEGY[iri["schema:Thing"]], + iri["schema:geo"]: None, # FIXME: troublesome GeoCoordinates or GeoShape + iri["schema:geoContains"]: None, # FIXME: troublesome GeospatialGeometry or Place + iri["schema:geoCoveredBy"]: None, # FIXME: troublesome GeospatialGeometry or Place + iri["schema:geoCovers"]: None, # FIXME: troublesome GeospatialGeometry or Place + iri["schema:geoCrosses"]: None, # FIXME: troublesome GeospatialGeometry or Place + iri["schema:geoDisjoint"]: None, # FIXME: troublesome GeospatialGeometry or Place + iri["schema:geoEquals"]: None, # FIXME: troublesome GeospatialGeometry or Place + iri["schema:geoIntersects"]: None, # FIXME: troublesome GeospatialGeometry or Place + iri["schema:geoOverlaps"]: None, # FIXME: troublesome GeospatialGeometry or Place + iri["schema:geoTouches"]: None, # FIXME: troublesome GeospatialGeometry or Place + iri["schema:geoWithin"]: None, # FIXME: troublesome GeospatialGeometry or Place + iri["schema:photo"]: None # FIXME: troublesome ImageObject or Photograph +} +CODEMETA_STRATEGY[iri["schema:AdministrativeArea"]] = {**CODEMETA_STRATEGY[iri["schema:Place"]]} +CODEMETA_STRATEGY[iri["schema:Country"]] = {**CODEMETA_STRATEGY[iri["schema:AdministrativeArea"]]} +CODEMETA_STRATEGY[iri["schema:CivicStructure"]] = {**CODEMETA_STRATEGY[iri["schema:Place"]]} + +CODEMETA_STRATEGY[iri["schema:CreativeWorkSeries"]] = { + **CODEMETA_STRATEGY[iri["schema:CreativeWork"]], + **CODEMETA_STRATEGY[iri["schema:Series"]] +} + +CODEMETA_STRATEGY[iri["schema:HowToSection"]] = { + **CODEMETA_STRATEGY[iri["schema:CreativeWork"]], + **CODEMETA_STRATEGY[iri["schema:ItemList"]], + **CODEMETA_STRATEGY[iri["schema:ListItem"]] +} +CODEMETA_STRATEGY[iri["schema:HowToStep"]] = { + **CODEMETA_STRATEGY[iri["schema:CreativeWork"]], + **CODEMETA_STRATEGY[iri["schema:ItemList"]], + **CODEMETA_STRATEGY[iri["schema:ListItem"]] +} + +CODEMETA_STRATEGY[iri["schema:Event"]] = { + **CODEMETA_STRATEGY[iri["schema:Thing"]], + iri["schema:actor"]: None, # FIXME: troublesome PerformingGroup or Person + iri["schema:attendee"]: None, # FIXME: troublesome Organization or Person + iri["schema:composer"]: None, # FIXME: troublesome Organization or Person + iri["schema:contributor"]: None, # FIXME: troublesome Organization or Person + iri["schema:dircetor"]: ACTIONS["merge_match_person"], + iri["schema:duration"]: None, # FIXME: troublesome Duration or QuantitativeValue + iri["schema:funder"]: None, # FIXME: troublesome Organization or Person + iri["schema:location"]: None, # FIXME: troublesome Place or PostalAddress or VirtualLocation + iri["schema:offers"]: None, # FIXME: troublesome Demand or Offer + iri["schema:organizer"]: None, # FIXME: troublesome Organization or Person + iri["schema:performer"]: None, # FIXME: troublesome Organization or Person + iri["schema:sponsor"]: None, # FIXME: troublesome Organization or Person + iri["schema:translator"]: None # FIXME: troublesome Organization or Person +} +CODEMETA_STRATEGY[iri["schema:PublicationEvent"]] = { + **CODEMETA_STRATEGY[iri["schema:Event"]], + iri["schema:publishedBy"]: None, # FIXME: troublesome Organization or Person +} + +CODEMETA_STRATEGY[iri["schema:BioChemEntity"]] = { + **CODEMETA_STRATEGY[iri["schema:Thing"]], + iri["schema:associatedDisease"]: None, # FIXME: troublesome MedicalCondition or PropertyValue + iri["schema:hasMolecularFunction"]: None, # FIXME: troublesome DefinedTerm or PropertyValue + iri["schema:isInvolvedInBiologicalProcess"]: None, # FIXME: troublesome DefinedTerm or PropertyValue + iri["schema:isLocatedInSubcellularLocation"]: None, # FIXME: troublesome DefinedTerm or PropertyValue + iri["schema:taxonomicRange"]: None # FIXME: troublesome DefinedTerm or Taxon +} +CODEMETA_STRATEGY[iri["schema:Gene"]] = { + **CODEMETA_STRATEGY[iri["schema:BioChemEntity"]], + iri["schema:expressedIn"]: None # FIXME: troublesome AnatomicalStructure or AnatomicalSystem or BioChemEntity or DefinedTerm +} + +CODEMETA_STRATEGY[iri["schema:MedicalEntity"]] = {**CODEMETA_STRATEGY[iri["schema:Thing"]]} +CODEMETA_STRATEGY[iri["schema:MedicalIntangible"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalEntity"]]} +CODEMETA_STRATEGY[iri["schema:DrugLegalStatus"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalIntangible"]]} +CODEMETA_STRATEGY[iri["schema:DDxElement"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalIntangible"]]} +CODEMETA_STRATEGY[iri["schema:MedicalConditionStage"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalIntangible"]]} +CODEMETA_STRATEGY[iri["schema:DrugStrength"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalIntangible"]]} +CODEMETA_STRATEGY[iri["schema:DoseSchedule"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalIntangible"]]} +CODEMETA_STRATEGY[iri["schema:MaximumDoseSchedule"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalIntangible"]]} +CODEMETA_STRATEGY[iri["schema:MedicalGuideline"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalEntity"]]} +CODEMETA_STRATEGY[iri["schema:AnatomicalStructure"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalEntity"]]} +CODEMETA_STRATEGY[iri["schema:MedicalCause"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalEntity"]]} +CODEMETA_STRATEGY[iri["schema:DrugClass"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalEntity"]]} +CODEMETA_STRATEGY[iri["schema:LifestyleModification"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalEntity"]]} +CODEMETA_STRATEGY[iri["schema:MedicalRiskFactor"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalEntity"]]} +CODEMETA_STRATEGY[iri["schema:MedicalTest"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalEntity"]]} +CODEMETA_STRATEGY[iri["schema:MedicalDevice"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalEntity"]]} +CODEMETA_STRATEGY[iri["schema:MedicalTest"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalEntity"]]} +CODEMETA_STRATEGY[iri["schema:MedicalContraindication"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalEntity"]]} +CODEMETA_STRATEGY[iri["schema:MedicalProcedure"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalEntity"]]} +CODEMETA_STRATEGY[iri["schema:TherapeuticProcedure"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalProcedure"]]} +CODEMETA_STRATEGY[iri["schema:MedicalTherapy"]] = {**CODEMETA_STRATEGY[iri["schema:TherapeuticProcedure"]]} +CODEMETA_STRATEGY[iri["schema:MedicalStudy"]] = { + **CODEMETA_STRATEGY[iri["schema:MedicalEntity"]], + iri["schema:sponsor"]: None # FIXME: troublesome Organization or Person +} +CODEMETA_STRATEGY[iri["schema:MedicalCondition"]] = { + **CODEMETA_STRATEGY[iri["schema:MedicalEntity"]], + iri["schema:associatedAnatomy"]: None, # FIXME: troublesome AnatomicalStructure or AnatomicalSystem or SuperficialAnatomy + iri["schema:possibleTreatment"]: None, # FIXME: troublesome Drug or DrugClass or LifestyleModification or MedicalTherapy + iri["schema:secondaryPrevention"]: None # FIXME: troublesome Drug or DrugClass or LifestyleModification or MedicalTherapy +} +CODEMETA_STRATEGY[iri["schema:MedicalSignOrSymptom"]] = { + **CODEMETA_STRATEGY[iri["schema:MedicalCondition"]], + iri["schema:possibleTreatment"]: None # FIXME: troublesome Drug or DrugClass or LifestyleModification or MedicalTherapy +} +CODEMETA_STRATEGY[iri["schema:MedicalSign"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalSignOrSymptom"]]} +CODEMETA_STRATEGY[iri["schema:SuperficialAnatomy"]] = { + **CODEMETA_STRATEGY[iri["schema:MedicalEntity"]], + iri["schema:relatedAnatomy"]: None # FIXME: troublesome AnatomicalStructure or AnatomicalSystem +} +CODEMETA_STRATEGY[iri["schema:AnatomicalSystem"]] = { + **CODEMETA_STRATEGY[iri["schema:MedicalEntity"]], + iri["schema:comprisedOf"]: None # FIXME: troublesome AnatomicalStructure or AnatomicalSystem +} + +CODEMETA_STRATEGY[iri["schema:MedicalCode"]] = { + **CODEMETA_STRATEGY[iri["schema:CategoryCode"]], + **CODEMETA_STRATEGY[iri["schema:MedicalIntangible"]] +} + +CODEMETA_STRATEGY[iri["schema:Product"]] = { + **CODEMETA_STRATEGY[iri["schema:Thing"]], + iri["schema:brand"]: None, # FIXME: troublesome Brand or Organization + iri["schema:category"]: None, # FIXME: troublesome CategoryCode or Thing + iri["schema:depth"]: None, # FIXME: troublesome Distance or QuantitativeValue + iri["schema:height"]: None, # FIXME: troublesome Distance or QuantitativeValue + iri["schema:isRelatedTo"]: None, # FIXME: troublesome Product or Service + iri["schema:isSimilarTo"]: None, # FIXME: troublesome Product or Service + iri["schema:isVariantOf"]: None, # FIXME: troublesome ProductGroup or ProductModel + iri["schema:negativeNotes"]: None, # FIXME: troublesome ItemList or ListItem or WebContent + iri["schema:offers"]: None, # FIXME: troublesome Demand or Offer + iri["schema:positiveNotes"]: None, # FIXME: troublesome ItemList or ListItem or WebContent + iri["schema:size"]: None, # FIXME: troublesome DefinedTerm or QuantitativeValue or SizeSpecification + iri["schema:weight"]: None, # FIXME: troublesome Mass or QuantitativeValue + iri["schema:width"]: None, # FIXME: troublesome Distance or QuantitativeValue +} +CODEMETA_STRATEGY[iri["schema:ProductGroup"]] = {**CODEMETA_STRATEGY[iri["schema:Product"]]} +CODEMETA_STRATEGY[iri["schema:Drug"]] = { + **CODEMETA_STRATEGY[iri["schema:Product"]], + **CODEMETA_STRATEGY[iri["schema:MedicalEntity"]] +} +CODEMETA_STRATEGY[iri["schema:ProductModel"]] = { + **CODEMETA_STRATEGY[iri["schema:Product"]], + iri["schema:isVariantOf"]: None, # FIXME: troublesome ProductGroup or ProductModel +} + +CODEMETA_STRATEGY[iri["schema:PaymentCard"]] = { + **CODEMETA_STRATEGY[iri["schema:FinancialProduct"]], + **CODEMETA_STRATEGY[iri["schema:PaymentMethod"]] +} +CODEMETA_STRATEGY[iri["schema:CreditCard"]] = { + **CODEMETA_STRATEGY[iri["schema:LoanOrCredit"]], + **CODEMETA_STRATEGY[iri["schema:PaymentCard"]] +} + +CODEMETA_STRATEGY[iri["schema:Organization"]] = { + **CODEMETA_STRATEGY[iri["schema:Thing"]], + iri["schema:acceptedPaymentMethod"]: None, # FIXME: troublesome LoanOrCredit or PaymentMethod + iri["schema:alumni"]: ACTIONS["merge_match_person"], + iri["schema:areaServed"]: None, # FIXME: troublesome AdministrativeArea or GeoShape or Place + iri["schema:brand"]: None, # FIXME: troublesome Brand or Organization + iri["schema:employee"]: ACTIONS["merge_match_person"], + iri["schema:founder"]: None, # FIXME: troublesome Organization or Person + iri["schema:funder"]: None, # FIXME: troublesome Organization or Person + iri["schema:legalRepresentative"]: ACTIONS["merge_match_person"], + iri["schema:location"]: None, # FIXME: troublesome Place or PostalAddress or Text or VirtualLocation + iri["schema:member"]: None, # FIXME: troublesome Organization or Person + iri["schema:memberOf"]: None, # FIXME: troublesome MemberProgramTier or Organization or ProgramMembership + iri["schema:ownershipFundingInfo"]: None, # FIXME: troublesome AboutPage or CreativeWork + iri["schema:sponsor"]: None # FIXME: troublesome Organization or Person +} +CODEMETA_STRATEGY[iri["schema:PerformingGroup"]] = {**CODEMETA_STRATEGY[iri["schema:Organization"]]} +CODEMETA_STRATEGY[iri["schema:MusicGroup"]] = { + **CODEMETA_STRATEGY[iri["schema:PerformingGroup"]], + iri["schema:musicGroupMember"]: ACTIONS["merge_match_person"], + iri["schema:track"]: None # FIXME: troublesome ItemList or MusicRecording +} +CODEMETA_STRATEGY[iri["schema:EducationalOrganization"]] = { + **CODEMETA_STRATEGY[iri["schema:Organization"]], + **CODEMETA_STRATEGY[iri["schema:CivicStructure"]] +} + +CODEMETA_STRATEGY[iri["schema:DefinedRegion"]] = { + **CODEMETA_STRATEGY[iri["schema:Place"]], + **CODEMETA_STRATEGY[iri["schema:StructuredValue"]] +} + +CODEMETA_STRATEGY[iri["schema:Person"]] = { + **CODEMETA_STRATEGY[iri["schema:Thing"]], + iri["schema:alumniOf"]: None, # FIXME: troublesome EducationalOrganization or Organization + iri["schema:brand"]: None, # FIXME: troublesome Brand or Organization + iri["schema:children"]: ACTIONS["merge_match_person"], + iri["schema:colleague"]: ACTIONS["merge_match_person"], + iri["schema:follows"]: ACTIONS["merge_match_person"], + iri["schema:funder"]: None, # FIXME: troublesome Organization or Person + iri["schema:height"]: None, # FIXME: troublesome Distance or QuantitativeValue + iri["schema:homeLocation"]: None, # FIXME: troublesome ContactPoint or Place + iri["schema:knows"]: ACTIONS["merge_match_person"], + iri["schema:memberOf"]: None, # FIXME: troublesome MemberProgramTier or Organization or ProgramMembership + iri["schema:netWorth"]: None, # FIXME: troublesome MonetaryAmount or PriceSpecification + iri["schema:parent"]: ACTIONS["merge_match_person"], + iri["schema:pronouns"]: None, # FIXME: troublesome DefinedTerm or StructuredValue + iri["schema:relatedTo"]: ACTIONS["merge_match_person"], + iri["schema:sibling"]: ACTIONS["merge_match_person"], + iri["schema:sponsor"]: None, # FIXME: troublesome Organization or Person + iri["schema:spouse"]: ACTIONS["merge_match_person"], + iri["schema:weight"]: None, # FIXME: troublesome Mass or QuantitativeValue + iri["schema:workLocation"]: None # FIXME: troublesome ContactPoint or Place +} + +CODEMETA_STRATEGY[iri["schema:Taxon"]] = {**CODEMETA_STRATEGY[iri["schema:Thing"]]} diff --git a/src/hermes/model/types/ld_dict.py b/src/hermes/model/types/ld_dict.py index 42bc3ed9..2c88a520 100644 --- a/src/hermes/model/types/ld_dict.py +++ b/src/hermes/model/types/ld_dict.py @@ -72,6 +72,9 @@ def __ne__(self, other): return NotImplemented return not x + def __bool__(self): + return bool(self.data_dict) + def get(self, key, default=_NO_DEFAULT): if key not in self and default is not ld_dict._NO_DEFAULT: return default diff --git a/test/hermes_test/model/test_api_e2e.py b/test/hermes_test/model/test_api_e2e.py index f756f101..646f815a 100644 --- a/test/hermes_test/model/test_api_e2e.py +++ b/test/hermes_test/model/test_api_e2e.py @@ -508,7 +508,45 @@ def test_invenio_deposit(tmp_path, monkeypatch, sandbox_auth, metadata, invenio_ }], "http://schema.org/license": [{"@id": "https://spdx.org/licenses/Apache-2.0"}] }) - ), + ) + ] +) +def test_process(tmp_path, monkeypatch, metadata_in, metadata_out): + monkeypatch.chdir(tmp_path) + + manager = context_manager.HermesContext(tmp_path) + manager.prepare_step("harvest") + for harvester, result in metadata_in.items(): + with manager[harvester] as cache: + cache["codemeta"] = result.compact() + cache["context"] = {"@context": result.full_context} + cache["expanded"] = result.ld_value + manager.finalize_step("harvest") + + config_file = tmp_path / "hermes.toml" + config_file.write_text(f"[harvest]\nsources = [{', '.join(f'\"{harvester}\"' for harvester in metadata_in)}]") + + orig_argv = sys.argv[:] + sys.argv = ["hermes", "process", "--path", str(tmp_path), "--config", str(config_file)] + result = {} + try: + monkeypatch.setattr(context_manager.HermesContext.__init__, "__defaults__", (tmp_path.cwd(),)) + cli.main() + except SystemExit as e: + if e.code != 0: + raise e + finally: + manager.prepare_step("process") + result = SoftwareMetadata.load_from_cache(manager, "result") + manager.finalize_step("process") + sys.argv = orig_argv + + assert result == metadata_out + +@pytest.mark.xfail +@pytest.mark.parametrize( + "metadata_in, metadata_out", + [ ( { "cff": SoftwareMetadata({ @@ -520,6 +558,10 @@ def test_invenio_deposit(tmp_path, monkeypatch, sandbox_auth, metadata, invenio_ "http://schema.org/familyName": [{"@value": "Test"}], "http://schema.org/email": [{"@value": "test.testi@testis.tests"}] }, + { + "@type": "http://schema.org/Person", + "http://schema.org/familyName": [{"@value": "Testers"}] + }, { "@type": "http://schema.org/Person", "http://schema.org/familyName": [{"@value": "Tester"}], @@ -531,25 +573,41 @@ def test_invenio_deposit(tmp_path, monkeypatch, sandbox_auth, metadata, invenio_ "codemeta": SoftwareMetadata({ "@type": ["http://schema.org/SoftwareSourceCode"], "http://schema.org/description": [{"@value": "for testing"}], - "http://schema.org/name": [{"@value": "Test"}], - "http://schema.org/author": [{ - "@type": "http://schema.org/Person", - "http://schema.org/familyName": [{"@value": "Test"}], - "http://schema.org/givenName": [{"@value": "Testi"}], - "http://schema.org/email": [{"@value": "test.testi@testis.tests"}] - }] + "http://schema.org/name": [{"@value": "Test"}, {"@value": "Testis Test"}], + "http://schema.org/author": [ + { + "@type": "http://schema.org/Person", + "http://schema.org/familyName": [{"@value": "Test"}], + "http://schema.org/givenName": [{"@value": "Testi"}], + "http://schema.org/email": [ + {"@value": "test.testi@testis.tests"}, + {"@value": "test.testi@testis.tests2"} + ] + }, + { + "@type": "http://schema.org/Person", + "http://schema.org/familyName": [{"@value": "Testers"}] + } + ] }) }, SoftwareMetadata({ "@type": ["http://schema.org/SoftwareSourceCode"], "http://schema.org/description": [{"@value": "for testing"}], - "http://schema.org/name": [{"@value": "Test"}], + "http://schema.org/name": [{"@value": "Test"}, {"@value": "Testis Test"}], "http://schema.org/author": [ { "@type": "http://schema.org/Person", "http://schema.org/familyName": [{"@value": "Test"}], "http://schema.org/givenName": [{"@value": "Testi"}], - "http://schema.org/email": [{"@value": "test.testi@testis.tests"}] + "http://schema.org/email": [ + {"@value": "test.testi@testis.tests"}, + {"@value": "test.testi@testis.tests2"} + ] + }, + { + "@type": "http://schema.org/Person", + "http://schema.org/familyName": [{"@value": "Testers"}] }, { "@type": "http://schema.org/Person", @@ -592,5 +650,4 @@ def test_process(tmp_path, monkeypatch, metadata_in, metadata_out): manager.finalize_step("process") sys.argv = orig_argv - assert result.ld_value == metadata_out.ld_value assert result == metadata_out From 08619ee89ece119ec7a9f764dc72ef51d704d8ee Mon Sep 17 00:00:00 2001 From: Michael Fritzsche Date: Thu, 5 Mar 2026 14:40:46 +0100 Subject: [PATCH 17/19] fixed formation errors --- src/hermes/model/merge/strategy.py | 40 +++++++++++++++++++------- test/hermes_test/model/test_api_e2e.py | 3 +- 2 files changed, 31 insertions(+), 12 deletions(-) diff --git a/src/hermes/model/merge/strategy.py b/src/hermes/model/merge/strategy.py index e928a4fc..fb2aeaf6 100644 --- a/src/hermes/model/merge/strategy.py +++ b/src/hermes/model/merge/strategy.py @@ -188,7 +188,9 @@ iri["schema:areaServed"]: None, # FIXME: troublesome AdministrativeArea or GeoShape or Place iri["schema:eligibleRegion"]: None, # FIXME: troublesome GeoShape or Place iri["schema:ineligibleRegion"]: None, # FIXME: troublesome GeoShape or Place - iri["schema:itemOffered"]: None, # FIXME: troublesome AggregateOffer or CreativeWork or Event or MenuItem or Product or Service or Trip + iri[ + "schema:itemOffered" + ]: None, # FIXME: troublesome AggregateOffer or CreativeWork or Event or MenuItem or Product or Service or Trip iri["schema:seller"]: None # FIXME: troublesome Organization or Person } CODEMETA_STRATEGY[iri["schema:Offer"]] = { @@ -198,7 +200,9 @@ iri["schema:category"]: None, # FIXME: troublesome CategoryCode or Thing iri["schema:eligibleRegion"]: None, # FIXME: troublesome GeoShape or Place iri["schema:ineligibleRegion"]: None, # FIXME: troublesome GeoShape or Place - iri["schema:itemOffered"]: None, # FIXME: troublesome AggregateOffer or CreativeWork or Event or MenuItem or Product or Service or Trip + iri[ + "schema:itemOffered" + ]: None, # FIXME: troublesome AggregateOffer or CreativeWork or Event or MenuItem or Product or Service or Trip iri["schema:leaseLength"]: None, # FIXME: troublesome Duration or QuantitativeValue iri["schema:offeredBy"]: None, # FIXME: troublesome Organization or Person iri["schema:seller"]: None, # FIXME: troublesome Organization or Person @@ -233,7 +237,8 @@ } CODEMETA_STRATEGY[iri["schema:PropertyValue"]] = { **CODEMETA_STRATEGY[iri["schema:StructuredValue"]], - iri["schema:valueReference"]: None # FIXME: troublesome DefinedTerm or Enumeration or PropertyValue or QualitativeValue or QuantitativeValue or StructuredValue + iri["schema:valueReference"]: None # FIXME: troublesome DefinedTerm or Enumeration or PropertyValue + # or QualitativeValue or QuantitativeValue or StructuredValue } CODEMETA_STRATEGY[iri["schema:ContactPoint"]] = { **CODEMETA_STRATEGY[iri["schema:StructuredValue"]], @@ -263,7 +268,8 @@ } CODEMETA_STRATEGY[iri["schema:QuantitativeValue"]] = { **CODEMETA_STRATEGY[iri["schema:StructuredValue"]], - iri["schema:valueReference"]: None # FIXME: troublesome DefinedTerm or Enumeration or PropertyValue or QualitativeValue or QuantitativeValue or StructuredValue + iri["schema:valueReference"]: None # FIXME: troublesome DefinedTerm or Enumeration or PropertyValue + # or QualitativeValue or QuantitativeValue or StructuredValue } CODEMETA_STRATEGY[iri["schema:ShippingService"]] = { **CODEMETA_STRATEGY[iri["schema:StructuredValue"]], @@ -312,7 +318,8 @@ } CODEMETA_STRATEGY[iri["schema:Grant"]] = { **CODEMETA_STRATEGY[iri["schema:Intangible"]], - iri["schema:fundedItem"]: None, # FIXME: troublesome BioChemEntity or CreativeWork or Event or MedicalEntity or Organization or Person or Product + iri["schema:fundedItem"]: None, # FIXME: troublesome BioChemEntity or CreativeWork or Event or MedicalEntity + # or Organization or Person or Product iri["schema:funder"]: None, # FIXME: troublesome Organization or Person iri["schema:sponsor"]: None # FIXME: troublesome Organization or Person } @@ -351,7 +358,8 @@ } CODEMETA_STRATEGY[iri["schema:QualitativeValue"]] = { **CODEMETA_STRATEGY[iri["schema:Enumeration"]], - iri["schema:valueReference"]: None # FIXME: troublesome DefinedTerm or Enumeration or PropertyValue or QualitativeValue or QuantitativeValue or StructuredValue + iri["schema:valueReference"]: None # FIXME: troublesome DefinedTerm or Enumeration or PropertyValue + # or QualitativeValue or QuantitativeValue or StructuredValue } CODEMETA_STRATEGY[iri["schema:SizeSpecification"]] = {**CODEMETA_STRATEGY[iri["schema:QualitativeValue"]]} CODEMETA_STRATEGY[iri["schema:Class"]] = { @@ -467,7 +475,9 @@ } CODEMETA_STRATEGY[iri["schema:Gene"]] = { **CODEMETA_STRATEGY[iri["schema:BioChemEntity"]], - iri["schema:expressedIn"]: None # FIXME: troublesome AnatomicalStructure or AnatomicalSystem or BioChemEntity or DefinedTerm + iri[ + "schema:expressedIn" + ]: None # FIXME: troublesome AnatomicalStructure or AnatomicalSystem or BioChemEntity or DefinedTerm } CODEMETA_STRATEGY[iri["schema:MedicalEntity"]] = {**CODEMETA_STRATEGY[iri["schema:Thing"]]} @@ -497,13 +507,21 @@ } CODEMETA_STRATEGY[iri["schema:MedicalCondition"]] = { **CODEMETA_STRATEGY[iri["schema:MedicalEntity"]], - iri["schema:associatedAnatomy"]: None, # FIXME: troublesome AnatomicalStructure or AnatomicalSystem or SuperficialAnatomy - iri["schema:possibleTreatment"]: None, # FIXME: troublesome Drug or DrugClass or LifestyleModification or MedicalTherapy - iri["schema:secondaryPrevention"]: None # FIXME: troublesome Drug or DrugClass or LifestyleModification or MedicalTherapy + iri[ + "schema:associatedAnatomy" + ]: None, # FIXME: troublesome AnatomicalStructure or AnatomicalSystem or SuperficialAnatomy + iri[ + "schema:possibleTreatment" + ]: None, # FIXME: troublesome Drug or DrugClass or LifestyleModification or MedicalTherapy + iri[ + "schema:secondaryPrevention" + ]: None # FIXME: troublesome Drug or DrugClass or LifestyleModification or MedicalTherapy } CODEMETA_STRATEGY[iri["schema:MedicalSignOrSymptom"]] = { **CODEMETA_STRATEGY[iri["schema:MedicalCondition"]], - iri["schema:possibleTreatment"]: None # FIXME: troublesome Drug or DrugClass or LifestyleModification or MedicalTherapy + iri[ + "schema:possibleTreatment" + ]: None # FIXME: troublesome Drug or DrugClass or LifestyleModification or MedicalTherapy } CODEMETA_STRATEGY[iri["schema:MedicalSign"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalSignOrSymptom"]]} CODEMETA_STRATEGY[iri["schema:SuperficialAnatomy"]] = { diff --git a/test/hermes_test/model/test_api_e2e.py b/test/hermes_test/model/test_api_e2e.py index 646f815a..30ecd11c 100644 --- a/test/hermes_test/model/test_api_e2e.py +++ b/test/hermes_test/model/test_api_e2e.py @@ -543,6 +543,7 @@ def test_process(tmp_path, monkeypatch, metadata_in, metadata_out): assert result == metadata_out + @pytest.mark.xfail @pytest.mark.parametrize( "metadata_in, metadata_out", @@ -620,7 +621,7 @@ def test_process(tmp_path, monkeypatch, metadata_in, metadata_out): ) ] ) -def test_process(tmp_path, monkeypatch, metadata_in, metadata_out): +def test_process_complex(tmp_path, monkeypatch, metadata_in, metadata_out): monkeypatch.chdir(tmp_path) manager = context_manager.HermesContext(tmp_path) From ac36a286a2bf86842fc6c6e04391221e535b7248 Mon Sep 17 00:00:00 2001 From: notactuallyfinn Date: Fri, 6 Mar 2026 17:48:50 +0100 Subject: [PATCH 18/19] updated strategies and match functions --- src/hermes/model/merge/action.py | 2 +- src/hermes/model/merge/match.py | 31 +- src/hermes/model/merge/strategy.py | 1094 ++++++++++++++---------- test/hermes_test/model/test_api_e2e.py | 1 - 4 files changed, 659 insertions(+), 469 deletions(-) diff --git a/src/hermes/model/merge/action.py b/src/hermes/model/merge/action.py index 6108b9ea..b9b516ef 100644 --- a/src/hermes/model/merge/action.py +++ b/src/hermes/model/merge/action.py @@ -259,6 +259,6 @@ def merge( """ FIXME: log error """ break else: - value.append(item) + value.append(update_item) # Return the merged values. return value diff --git a/src/hermes/model/merge/match.py b/src/hermes/model/merge/match.py index 3934b785..8a0aa9a1 100644 --- a/src/hermes/model/merge/match.py +++ b/src/hermes/model/merge/match.py @@ -10,21 +10,6 @@ from ..types import ld_dict -def match_equals(a: Any, b: Any) -> bool: - """ - Wrapper method for normal == comparison. - - :param a: First item for the comparison. - :type a: Any - :param b: Second item for the comparison. - :type b: Any - - :return: Truth value of a == b. - :rtype: bool - """ - return a == b - - def match_keys(*keys: list[str], fall_back_to_equals: bool = False) -> Callable[[Any, Any], bool]: """ Creates a function taking to parameters that returns true @@ -81,3 +66,19 @@ def match_person(left: Any, right: Any) -> bool: mails_right = right["schema:email"] return any((mail in mails_right) for mail in left["schema:email"]) return left == right + + +def match_multiple_types( + *functions_for_types: list[tuple[str, Callable[[Any, Any], bool]]], + fall_back_function: Callable[[Any, Any], bool] = match_keys("@id", fall_back_to_equals=True) +) -> Callable[[Any, Any], bool]: + def match_func(left: Any, right: Any) -> bool: + if not ((isinstance(left, ld_dict) and isinstance(right, ld_dict)) and "@type" in left and "@type" in right): + return fall_back_function(left, right) + types_left = left["@type"] + types_right = right["@type"] + for ld_type, func in functions_for_types: + if ld_type in types_left and ld_type in types_right: + return func(left, right) + return fall_back_function(left, right) + return match_func diff --git a/src/hermes/model/merge/strategy.py b/src/hermes/model/merge/strategy.py index fb2aeaf6..5aaa5d7f 100644 --- a/src/hermes/model/merge/strategy.py +++ b/src/hermes/model/merge/strategy.py @@ -7,625 +7,815 @@ from ..types.ld_context import iri_map as iri from .action import Concat, MergeSet -from .match import match_keys, match_person +from .match import match_keys, match_person, match_multiple_types +DEFAULT_MATCH = match_keys("@id", fall_back_to_equals=True) + +MATCH_FUNCTION_FOR_TYPE = {"schema:Person": match_person} + ACTIONS = { - "default": MergeSet(match_keys("@id", fall_back_to_equals=True)), - "merge_match_person": MergeSet(match_person) + "default": MergeSet(DEFAULT_MATCH), + "concat": Concat(), + "Person": MergeSet(MATCH_FUNCTION_FOR_TYPE["schema:Person"]), + **{ + "Or".join(types): MergeSet(match_multiple_types( + *(("schema:" + type, MATCH_FUNCTION_FOR_TYPE.get("schema:" + type, DEFAULT_MATCH)) for type in types) + )) + for types in [ + ("AboutPage", "CreativeWork"), + ("AdministrativeArea", "GeoShape", "Place"), + ("AggregateOffer", "CreativeWork", "Event", "MenuItem", "Product", "Service", "Trip"), + ("AnatomicalStructure", "AnatomicalSystem"), + ("AnatomicalStructure", "AnatomicalSystem", "BioChemEntity", "DefinedTerm"), + ("AnatomicalStructure", "AnatomicalSystem", "SuperficialAnatomy"), + ("AudioObject", "Clip", "MusicRecording"), + ("BioChemEntity", "CreativeWork", "Event", "MedicalEntity", "Organization", "Person", "Product"), + ("Brand", "Organization"), + ("CategoryCode", "Thing"), + ("Class", "Enumeration"), + ("Class", "Enumeration", "Property"), + ("Clip", "VideoObject"), + ("Comment", "CreativeWork"), + ("ContactPoint", "Place"), + ("CreativeWork", "HowToSection", "HowToStep"), + ("CreativeWork", "Product"), + ("CreditCard", "MonetaryAmount", "UnitPriceSpecification"), + ("DataFeedItem", "Thing"), + ("Demand", "Offer"), + ("DefinedTerm", "Enumeration", "PropertyValue", "QualitativeValue", "QuantitativeValue", "StructuredValue"), + ("DefinedTerm", "PropertyValue"), + ("DefinedTerm", "QuantitativeValue", "SizeSpecification"), + ("DefinedTerm", "StructuredValue"), + ("DefinedTerm", "Taxon"), + ("Distance", "QuantitativeValue"), + ("Drug", "DrugClass", "LifestyleModification", "MedicalTherapy"), + ("Duration", "QuantitativeValue"), + ("EducationalOrganization", "Organization"), + ("GeoCoordinates", "GeoShape"), + ("GeoShape", "Place"), + ("GeospatialGeometry", "Place"), + ("ImageObject", "Photograph"), + ("ItemList", "ListItem", "WebContent"), + ("ItemList", "MusicRecording"), + ("ListItem", "Thing"), + ("LoanOrCredit", "PaymentMethod"), + ("Mass", "QuantitativeValue"), + ("MedicalCondition", "PropertyValue"), + ("MemberProgramTier", "Organization", "ProgramMembership"), + ("MenuItem", "MenuSection"), + ("MonetaryAmount", "MonetaryAmountDistribution"), + ("MonetaryAmount", "PriceSpecification"), + ("MonetaryAmount", "ShippingRateSettings"), + ("MusicGroup", "Person"), + ("Organization", "Person"), + ("PerformingGroup", "Person"), + ("Place", "PostalAddress", "VirtualLocation"), + ("ProductGroup", "ProductModel"), + ("Property", "PropertyValue", "StatisticalVariable"), + ("Product", "Service"), + ("QuantitativeValue", "ServicePeriod"), + ("SoftwareApplication", "WebSite") + ] + } } PROV_STRATEGY = { - None: {iri["hermes-rt:graph"]: Concat(), iri["hermes-rt:replace"]: Concat(), iri["hermes-rt:reject"]: Concat()} + None: { + iri["hermes-rt:graph"]: ACTIONS["concat"], + iri["hermes-rt:replace"]: ACTIONS["concat"], + iri["hermes-rt:reject"]: ACTIONS["concat"] + } } -# All troublesome marked entries can contain objects of different types, e.g. Person and Organization. -# This is troublesome because Persons may be compared using a different method than Organizations. + # Filled with entries for every schema-type that can be found inside an JSON-LD dict of type # SoftwareSourceCode or SoftwareApplication. CODEMETA_STRATEGY = {None: {None: ACTIONS["default"]}} +CODEMETA_STRATEGY[iri["schema:Thing"]] = {iri["schema:owner"]: ACTIONS["OrganizationOrPerson"]} -CODEMETA_STRATEGY[iri["schema:Thing"]] = {iri["schema:owner"]: None} # FIXME: troublesome Organization or Person -CODEMETA_STRATEGY[iri["schema:CreativeWork"]] = { + +CODEMETA_STRATEGY[iri["schema:Action"]] = { **CODEMETA_STRATEGY[iri["schema:Thing"]], - iri["schema:accountablePerson"]: ACTIONS["merge_match_person"], - iri["schema:audio"]: None, # FIXME: troublesome AudioObject or Clip or MusicRecording - iri["schema:author"]: None, # FIXME: troublesome Organization or Person - iri["schema:character"]: ACTIONS["merge_match_person"], - iri["schema:contributor"]: None, # FIXME: troublesome Organization or Person - iri["schema:copyrightHolder"]: None, # FIXME: troublesome Organization or Person - iri["schema:creator"]: None, # FIXME: troublesome Organization or Person - iri["schema:editor"]: ACTIONS["merge_match_person"], - iri["schema:funder"]: None, # FIXME: troublesome Organization or Person - iri["schema:isBasedOn"]: None, # FIXME: troublesome CreativeWork or Product - iri["schema:maintainer"]: None, # FIXME: troublesome Organization or Person - iri["schema:offers"]: None, # FIXME: troublesome Demand or Offer - iri["schema:producer"]: None, # FIXME: troublesome Organization or Person - iri["schema:provider"]: None, # FIXME: troublesome Organization or Person - iri["schema:publisher"]: None, # FIXME: troublesome Organization or Person - iri["schema:sdPublisher"]: None, # FIXME: troublesome Organization or Person - iri["schema:size"]: None, # FIXME: troublesome DefinedTerm or QuantitativeValue or SizeSpecification - iri["schema:sponsor"]: None, # FIXME: troublesome Organization or Person - iri["schema:translator"]: None, # FIXME: troublesome Organization or Person - iri["schema:video"]: None # FIXME: troublesome Clip or VideoObject + iri["schema:agent"]: ACTIONS["OrganizationOrPerson"], + iri["schema:location"]: ACTIONS["PlaceOrPostalAddressOrVirtualLocation"], + iri["schema:participant"]: ACTIONS["OrganizationOrPerson"], + iri["schema:provider"]: ACTIONS["OrganizationOrPerson"] } -CODEMETA_STRATEGY[iri["schema:SoftwareSourceCode"]] = { - **CODEMETA_STRATEGY[iri["schema:CreativeWork"]], - iri["maintainer"]: ACTIONS["merge_match_person"] + + + +CODEMETA_STRATEGY[iri["schema:BioChemEntity"]] = { + **CODEMETA_STRATEGY[iri["schema:Thing"]], + iri["schema:associatedDisease"]: ACTIONS["MedicalConditionOrPropertyValue"], + iri["schema:hasMolecularFunction"]: ACTIONS["DefinedTermOrPropertyValue"], + iri["schema:isInvolvedInBiologicalProcess"]: ACTIONS["DefinedTermOrPropertyValue"], + iri["schema:isLocatedInSubcellularLocation"]: ACTIONS["DefinedTermOrPropertyValue"], + iri["schema:taxonomicRange"]: ACTIONS["DefinedTermOrTaxon"] } -CODEMETA_STRATEGY[iri["schema:MediaObject"]] = { - **CODEMETA_STRATEGY[iri["schema:CreativeWork"]], - iri["schema:duration"]: None, # FIXME: troublesome Duration or QuantitativeValue - iri["schema:height"]: None, # FIXME: troublesome Distance or QuantitativeValue - iri["schema:ineligibleRegion"]: None, # FIXME: troublesome GeoShape or Place - iri["schema:width"]: None # FIXME: troublesome Distance or QuantitativeValue + + +CODEMETA_STRATEGY[iri["schema:Gene"]] = { + **CODEMETA_STRATEGY[iri["schema:BioChemEntity"]], + iri["schema:expressedIn"]: ACTIONS["AnatomicalStructureOrAnatomicalSystemOrBioChemEntityOrDefinedTerm"] } -CODEMETA_STRATEGY[iri["schema:AudioObject"]] = {**CODEMETA_STRATEGY[iri["schema:MediaObject"]]} -CODEMETA_STRATEGY[iri["schema:ImageObject"]] = {**CODEMETA_STRATEGY[iri["schema:MediaObject"]]} -CODEMETA_STRATEGY[iri["schema:VideoObject"]] = { - **CODEMETA_STRATEGY[iri["schema:MediaObject"]], - iri["schema:actor"]: None, # FIXME: troublesome PerformingGroup or Person - iri["schema:dircetor"]: ACTIONS["merge_match_person"], - iri["schema:musicBy"]: None # FIXME: troublesome MusicGroup or Person + + + +CODEMETA_STRATEGY[iri["schema:CreativeWork"]] = { + **CODEMETA_STRATEGY[iri["schema:Thing"]], + iri["schema:accountablePerson"]: ACTIONS["Person"], + iri["schema:audio"]: ACTIONS["AudioObjectOrClipOrMusicRecording"], + iri["schema:author"]: ACTIONS["OrganizationOrPerson"], + iri["schema:character"]: ACTIONS["Person"], + iri["schema:contributor"]: ACTIONS["OrganizationOrPerson"], + iri["schema:copyrightHolder"]: ACTIONS["OrganizationOrPerson"], + iri["schema:creator"]: ACTIONS["OrganizationOrPerson"], + iri["schema:editor"]: ACTIONS["Person"], + iri["schema:funder"]: ACTIONS["OrganizationOrPerson"], + iri["schema:isBasedOn"]: ACTIONS["CreativeWorkOrProduct"], + iri["schema:maintainer"]: ACTIONS["OrganizationOrPerson"], + iri["schema:offers"]: ACTIONS["DemandOrOffer"], + iri["schema:producer"]: ACTIONS["OrganizationOrPerson"], + iri["schema:provider"]: ACTIONS["OrganizationOrPerson"], + iri["schema:publisher"]: ACTIONS["OrganizationOrPerson"], + iri["schema:sdPublisher"]: ACTIONS["OrganizationOrPerson"], + iri["schema:size"]: ACTIONS["DefinedTermOrQuantitativeValueOrSizeSpecification"], + iri["schema:sponsor"]: ACTIONS["OrganizationOrPerson"], + iri["schema:translator"]: ACTIONS["OrganizationOrPerson"], + iri["schema:video"]: ACTIONS["ClipOrVideoObject"] } -CODEMETA_STRATEGY[iri["schema:DataDownload"]] = {**CODEMETA_STRATEGY[iri["schema:MediaObject"]]} + + +CODEMETA_STRATEGY[iri["schema:Article"]] = {**CODEMETA_STRATEGY[iri["schema:CreativeWork"]]} +CODEMETA_STRATEGY[iri["schema:NewsArticle"]] = {**CODEMETA_STRATEGY[iri["schema:Article"]]} +CODEMETA_STRATEGY[iri["schema:ScholarlyArticle"]] = {**CODEMETA_STRATEGY[iri["schema:Article"]]} + CODEMETA_STRATEGY[iri["schema:Certification"]] = {**CODEMETA_STRATEGY[iri["schema:CreativeWork"]]} + CODEMETA_STRATEGY[iri["schema:Claim"]] = { **CODEMETA_STRATEGY[iri["schema:CreativeWork"]], - iri["schema:claimInterpreter"]: None # FIXME: troublesome Organization or Person + iri["schema:claimInterpreter"]: ACTIONS["OrganizationOrPerson"] } + CODEMETA_STRATEGY[iri["schema:Clip"]] = { **CODEMETA_STRATEGY[iri["schema:CreativeWork"]], - iri["schema:actor"]: None, # FIXME: troublesome PerformingGroup or Person - iri["schema:dircetor"]: ACTIONS["merge_match_person"], - iri["schema:musicBy"]: None # FIXME: troublesome MusicGroup or Person + iri["schema:actor"]: ACTIONS["PerformingGroupOrPerson"], + iri["schema:dircetor"]: ACTIONS["Person"], + iri["schema:musicBy"]: ACTIONS["MusicGroupOrPerson"] } + CODEMETA_STRATEGY[iri["schema:Comment"]] = { **CODEMETA_STRATEGY[iri["schema:CreativeWork"]], - iri["schema:parentItem"]: None # FIXME: troublesome Comment or CreativeWork + iri["schema:parentItem"]: ACTIONS["CommentOrCreativeWork"] } CODEMETA_STRATEGY[iri["schema:CorrectionComment"]] = {**CODEMETA_STRATEGY[iri["schema:Comment"]]} + CODEMETA_STRATEGY[iri["schema:CreativeWorkSeason"]] = { **CODEMETA_STRATEGY[iri["schema:CreativeWork"]], - iri["schema:actor"]: None # FIXME: troublesome PerformingGroup or Person + iri["schema:actor"]: ACTIONS["PerformingGroupOrPerson"] +} + +CODEMETA_STRATEGY[iri["schema:DataCatalog"]] = {**CODEMETA_STRATEGY[iri["schema:CreativeWork"]]} + +CODEMETA_STRATEGY[iri["schema:Dataset"]] = { + **CODEMETA_STRATEGY[iri["schema:CreativeWork"]], + iri["schema:variableMeasured"]: ACTIONS["PropertyOrPropertyValueOrStatisticalVariable"] +} +CODEMETA_STRATEGY[iri["schema:DataFeed"]] = { + **CODEMETA_STRATEGY[iri["schema:Dataset"]], + iri["schema:dataFeedElement"]: ACTIONS["DataFeedItemOrThing"] } + CODEMETA_STRATEGY[iri["schema:DefinedTermSet"]] = {**CODEMETA_STRATEGY[iri["schema:CreativeWork"]]} CODEMETA_STRATEGY[iri["schema:CategoryCodeSet"]] = {**CODEMETA_STRATEGY[iri["schema:DefinedTermSet"]]} + +CODEMETA_STRATEGY[iri["schema:EducationalOccupationalCredential"]] = {**CODEMETA_STRATEGY[iri["schema:CreativeWork"]]} + CODEMETA_STRATEGY[iri["schema:Episode"]] = { **CODEMETA_STRATEGY[iri["schema:CreativeWork"]], - iri["schema:actor"]: None, # FIXME: troublesome PerformingGroup or Person - iri["schema:dircetor"]: ACTIONS["merge_match_person"], - iri["schema:duration"]: None, # FIXME: troublesome Duration or QuantitativeValue - iri["schema:musicBy"]: None # FIXME: troublesome MusicGroup or Person + iri["schema:actor"]: ACTIONS["PerformingGroupOrPerson"], + iri["schema:dircetor"]: ACTIONS["Person"], + iri["schema:duration"]: ACTIONS["DurationOrQuantitativeValue"], + iri["schema:musicBy"]: ACTIONS["MusicGroupOrPerson"] } + CODEMETA_STRATEGY[iri["schema:HowTo"]] = { **CODEMETA_STRATEGY[iri["schema:CreativeWork"]], - iri["schema:step"]: None # FIXME: troublesome CreativeWork or HowToSection or HowToStep + iri["schema:step"]: ACTIONS["CreativeWorkOrHowToSectionOrHowToStep"] } + CODEMETA_STRATEGY[iri["schema:HyperTocEntry"]] = {**CODEMETA_STRATEGY[iri["schema:CreativeWork"]]} + CODEMETA_STRATEGY[iri["schema:Map"]] = {**CODEMETA_STRATEGY[iri["schema:CreativeWork"]]} -CODEMETA_STRATEGY[iri["schema:MenuSection"]] = {**CODEMETA_STRATEGY[iri["schema:CreativeWork"]]} -CODEMETA_STRATEGY[iri["schema:MusicRecording"]] = { + +CODEMETA_STRATEGY[iri["schema:MediaObject"]] = { **CODEMETA_STRATEGY[iri["schema:CreativeWork"]], - iri["schema:byArtist"]: None, # FIXME: troublesome MusicGroup or Person - iri["schema:duration"]: None # FIXME: troublesome Duration or QuantitativeValue + iri["schema:duration"]: ACTIONS["DurationOrQuantitativeValue"], + iri["schema:height"]: ACTIONS["DistanceOrQuantitativeValue"], + iri["schema:ineligibleRegion"]: ACTIONS["GeoShapeOrPlace"], + iri["schema:width"]: ACTIONS["DistanceOrQuantitativeValue"] } -CODEMETA_STRATEGY[iri["schema:WebPage"]] = { +CODEMETA_STRATEGY[iri["schema:AudioObject"]] = {**CODEMETA_STRATEGY[iri["schema:MediaObject"]]} +CODEMETA_STRATEGY[iri["schema:DataDownload"]] = {**CODEMETA_STRATEGY[iri["schema:MediaObject"]]} +CODEMETA_STRATEGY[iri["schema:ImageObject"]] = {**CODEMETA_STRATEGY[iri["schema:MediaObject"]]} +CODEMETA_STRATEGY[iri["schema:VideoObject"]] = { + **CODEMETA_STRATEGY[iri["schema:MediaObject"]], + iri["schema:actor"]: ACTIONS["PerformingGroupOrPerson"], + iri["schema:dircetor"]: ACTIONS["Person"], + iri["schema:musicBy"]: ACTIONS["MusicGroupOrPerson"] +} + +CODEMETA_STRATEGY[iri["schema:MenuSection"]] = {**CODEMETA_STRATEGY[iri["schema:CreativeWork"]]} + +CODEMETA_STRATEGY[iri["schema:MusicComposition"]] = { **CODEMETA_STRATEGY[iri["schema:CreativeWork"]], - iri["schema:reviewedBy"]: None # FIXME: troublesome Organization or Person + iri["schema:composer"]: ACTIONS["OrganizationOrPerson"], + iri["schema:lyricist"]: ACTIONS["Person"] } -CODEMETA_STRATEGY[iri["schema:AboutPage"]] = {**CODEMETA_STRATEGY[iri["schema:WebPage"]]} -CODEMETA_STRATEGY[iri["schema:Article"]] = {**CODEMETA_STRATEGY[iri["schema:CreativeWork"]]} -CODEMETA_STRATEGY[iri["schema:NewsArticle"]] = {**CODEMETA_STRATEGY[iri["schema:Article"]]} -CODEMETA_STRATEGY[iri["schema:ScholarlyArticle"]] = {**CODEMETA_STRATEGY[iri["schema:Article"]]} -CODEMETA_STRATEGY[iri["schema:WebPageElement"]] = {**CODEMETA_STRATEGY[iri["schema:CreativeWork"]]} -CODEMETA_STRATEGY[iri["schema:EducationalOccupationalCredential"]] = {**CODEMETA_STRATEGY[iri["schema:CreativeWork"]]} + CODEMETA_STRATEGY[iri["schema:MusicPlaylist"]] = { **CODEMETA_STRATEGY[iri["schema:CreativeWork"]], - iri["schema:track"]: None # FIXME: troublesome ItemList or MusicRecording + iri["schema:track"]: ACTIONS["ItemListOrMusicRecording"] } CODEMETA_STRATEGY[iri["schema:MusicAlbum"]] = { **CODEMETA_STRATEGY[iri["schema:MusicPlaylist"]], - iri["schema:byArtist"]: None, # FIXME: troublesome MusicGroup or Person + iri["schema:byArtist"]: ACTIONS["MusicGroupOrPerson"] } CODEMETA_STRATEGY[iri["schema:MusicRelease"]] = { **CODEMETA_STRATEGY[iri["schema:MusicPlaylist"]], - iri["schema:creditedTo"]: None, # FIXME: troublesome Organization or Person - iri["schema:duration"]: None # FIXME: troublesome Duration or QuantitativeValue + iri["schema:creditedTo"]: ACTIONS["OrganizationOrPerson"], + iri["schema:duration"]: ACTIONS["DurationOrQuantitativeValue"] } -CODEMETA_STRATEGY[iri["schema:MusicComposition"]] = { + +CODEMETA_STRATEGY[iri["schema:MusicRecording"]] = { **CODEMETA_STRATEGY[iri["schema:CreativeWork"]], - iri["schema:composer"]: None, # FIXME: troublesome Organization or Person - iri["schema:lyricist"]: ACTIONS["merge_match_person"], + iri["schema:byArtist"]: ACTIONS["MusicGroupOrPerson"], + iri["schema:duration"]: ACTIONS["DurationOrQuantitativeValue"] } + CODEMETA_STRATEGY[iri["schema:Photograph"]] = {**CODEMETA_STRATEGY[iri["schema:CreativeWork"]]} + CODEMETA_STRATEGY[iri["schema:Review"]] = { **CODEMETA_STRATEGY[iri["schema:CreativeWork"]], - iri["schema:negativeNotes"]: None, # FIXME: troublesome ItemList or ListItem or WebContent - iri["schema:positiveNotes"]: None # FIXME: troublesome ItemList or ListItem or WebContent + iri["schema:negativeNotes"]: ACTIONS["ItemListOrListItemOrWebContent"], + iri["schema:positiveNotes"]: ACTIONS["ItemListOrListItemOrWebContent"] } + CODEMETA_STRATEGY[iri["schema:SoftwareApplication"]] = {**CODEMETA_STRATEGY[iri["schema:CreativeWork"]]} -CODEMETA_STRATEGY[iri["schema:RuntimePlatform"]] = {**CODEMETA_STRATEGY[iri["schema:SoftwareApplication"]]} CODEMETA_STRATEGY[iri["schema:OperatingSystem"]] = {**CODEMETA_STRATEGY[iri["schema:SoftwareApplication"]]} -CODEMETA_STRATEGY[iri["schema:WebSite"]] = {**CODEMETA_STRATEGY[iri["schema:CreativeWork"]]} -CODEMETA_STRATEGY[iri["schema:WebContent"]] = {**CODEMETA_STRATEGY[iri["schema:CreativeWork"]]} -CODEMETA_STRATEGY[iri["schema:DataCatalog"]] = {**CODEMETA_STRATEGY[iri["schema:CreativeWork"]]} -CODEMETA_STRATEGY[iri["schema:Dataset"]] = { +CODEMETA_STRATEGY[iri["schema:RuntimePlatform"]] = {**CODEMETA_STRATEGY[iri["schema:SoftwareApplication"]]} + +CODEMETA_STRATEGY[iri["schema:SoftwareSourceCode"]] = { **CODEMETA_STRATEGY[iri["schema:CreativeWork"]], - iri["schema:variableMeasured"]: None # FIXME: troublesome Property or PropertyValue or StatisticalVariable + iri["maintainer"]: ACTIONS["Person"] } -CODEMETA_STRATEGY[iri["schema:DataFeed"]] = { - **CODEMETA_STRATEGY[iri["schema:Dataset"]], - iri["schema:dataFeedElement"]: None # FIXME: troublesome DataFeedItem or Thing + +CODEMETA_STRATEGY[iri["schema:WebContent"]] = {**CODEMETA_STRATEGY[iri["schema:CreativeWork"]]} + +CODEMETA_STRATEGY[iri["schema:WebPage"]] = { + **CODEMETA_STRATEGY[iri["schema:CreativeWork"]], + iri["schema:reviewedBy"]: ACTIONS["OrganizationOrPerson"] } +CODEMETA_STRATEGY[iri["schema:AboutPage"]] = {**CODEMETA_STRATEGY[iri["schema:WebPage"]]} -CODEMETA_STRATEGY[iri["schema:Action"]] = { +CODEMETA_STRATEGY[iri["schema:WebPageElement"]] = {**CODEMETA_STRATEGY[iri["schema:CreativeWork"]]} + +CODEMETA_STRATEGY[iri["schema:WebSite"]] = {**CODEMETA_STRATEGY[iri["schema:CreativeWork"]]} + + + +CODEMETA_STRATEGY[iri["schema:Event"]] = { **CODEMETA_STRATEGY[iri["schema:Thing"]], - iri["schema:agent"]: None, # FIXME: troublesome Organization or Person - iri["schema:location"]: None, # FIXME: troublesome Place or PostalAddress or VirtualLocation - iri["schema:participant"]: None, # FIXME: troublesome Organization or Person - iri["schema:provider"]: None # FIXME: troublesome Organization or Person + iri["schema:actor"]: ACTIONS["PerformingGroupOrPerson"], + iri["schema:attendee"]: ACTIONS["OrganizationOrPerson"], + iri["schema:composer"]: ACTIONS["OrganizationOrPerson"], + iri["schema:contributor"]: ACTIONS["OrganizationOrPerson"], + iri["schema:dircetor"]: ACTIONS["Person"], + iri["schema:duration"]: ACTIONS["DurationOrQuantitativeValue"], + iri["schema:funder"]: ACTIONS["OrganizationOrPerson"], + iri["schema:location"]: ACTIONS["PlaceOrPostalAddressOrVirtualLocation"], + iri["schema:offers"]: ACTIONS["DemandOrOffer"], + iri["schema:organizer"]: ACTIONS["OrganizationOrPerson"], + iri["schema:performer"]: ACTIONS["OrganizationOrPerson"], + iri["schema:sponsor"]: ACTIONS["OrganizationOrPerson"], + iri["schema:translator"]: ACTIONS["OrganizationOrPerson"] } -CODEMETA_STRATEGY[iri["schema:Intangible"]] = {**CODEMETA_STRATEGY[iri["schema:Thing"]]} -CODEMETA_STRATEGY[iri["schema:Rating"]] = { - **CODEMETA_STRATEGY[iri["schema:Intangible"]], - iri["schema:author"]: None # FIXME: troublesome Organization or Person + +CODEMETA_STRATEGY[iri["schema:PublicationEvent"]] = { + **CODEMETA_STRATEGY[iri["schema:Event"]], + iri["schema:publishedBy"]: ACTIONS["OrganizationOrPerson"] } -CODEMETA_STRATEGY[iri["schema:AggregateRating"]] = {**CODEMETA_STRATEGY[iri["schema:Rating"]]} + + + +CODEMETA_STRATEGY[iri["schema:Intangible"]] = {**CODEMETA_STRATEGY[iri["schema:Thing"]]} + + CODEMETA_STRATEGY[iri["schema:AlignmentObject"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} + CODEMETA_STRATEGY[iri["schema:Audience"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} + +CODEMETA_STRATEGY[iri["schema:Brand"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} + +CODEMETA_STRATEGY[iri["schema:BroadcastChannel"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} + +CODEMETA_STRATEGY[iri["schema:BroadcastFrequencySpecification"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} + +CODEMETA_STRATEGY[iri["schema:Class"]] = { + **CODEMETA_STRATEGY[iri["schema:Intangible"]], + iri["schema:supersededBy"]: ACTIONS["ClassOrEnumeration"] +} + CODEMETA_STRATEGY[iri["schema:ComputerLanguage"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} -CODEMETA_STRATEGY[iri["schema:Series"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} + +CODEMETA_STRATEGY[iri["schema:ConstraintNode"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} +CODEMETA_STRATEGY[iri["schema:StatisticalVariable"]] = {**CODEMETA_STRATEGY[iri["schema:ConstraintNode"]]} + CODEMETA_STRATEGY[iri["schema:DefinedTerm"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} CODEMETA_STRATEGY[iri["schema:CategoryCode"]] = {**CODEMETA_STRATEGY[iri["schema:DefinedTerm"]]} + CODEMETA_STRATEGY[iri["schema:Demand"]] = { **CODEMETA_STRATEGY[iri["schema:Intangible"]], - iri["schema:acceptedPaymentMethod"]: None, # FIXME: troublesome LoanOrCredit or PaymentMethod - iri["schema:areaServed"]: None, # FIXME: troublesome AdministrativeArea or GeoShape or Place - iri["schema:eligibleRegion"]: None, # FIXME: troublesome GeoShape or Place - iri["schema:ineligibleRegion"]: None, # FIXME: troublesome GeoShape or Place - iri[ - "schema:itemOffered" - ]: None, # FIXME: troublesome AggregateOffer or CreativeWork or Event or MenuItem or Product or Service or Trip - iri["schema:seller"]: None # FIXME: troublesome Organization or Person -} -CODEMETA_STRATEGY[iri["schema:Offer"]] = { - **CODEMETA_STRATEGY[iri["schema:Intangible"]], - iri["schema:acceptedPaymentMethod"]: None, # FIXME: troublesome LoanOrCredit or PaymentMethod - iri["schema:areaServed"]: None, # FIXME: troublesome AdministrativeArea or GeoShape or Place - iri["schema:category"]: None, # FIXME: troublesome CategoryCode or Thing - iri["schema:eligibleRegion"]: None, # FIXME: troublesome GeoShape or Place - iri["schema:ineligibleRegion"]: None, # FIXME: troublesome GeoShape or Place - iri[ - "schema:itemOffered" - ]: None, # FIXME: troublesome AggregateOffer or CreativeWork or Event or MenuItem or Product or Service or Trip - iri["schema:leaseLength"]: None, # FIXME: troublesome Duration or QuantitativeValue - iri["schema:offeredBy"]: None, # FIXME: troublesome Organization or Person - iri["schema:seller"]: None, # FIXME: troublesome Organization or Person -} -CODEMETA_STRATEGY[iri["schema:AggregateOffer"]] = { - **CODEMETA_STRATEGY[iri["schema:Offer"]], - iri["schema:offers"]: None # FIXME: troublesome Demand or Offer + iri["schema:acceptedPaymentMethod"]: ACTIONS["LoanOrCreditOrPaymentMethod"], + iri["schema:areaServed"]: ACTIONS["AdministrativeAreaOrGeoShapeOrPlace"], + iri["schema:eligibleRegion"]: ACTIONS["GeoShapeOrPlace"], + iri["schema:ineligibleRegion"]: ACTIONS["GeoShapeOrPlace"], + iri["schema:itemOffered"]: ACTIONS["AggregateOfferOrCreativeWorkOrEventOrMenuItemOrProductOrServiceOrTrip"], + iri["schema:seller"]: ACTIONS["OrganizationOrPerson"] } -CODEMETA_STRATEGY[iri["schema:Quantity"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} -CODEMETA_STRATEGY[iri["schema:Duration"]] = {**CODEMETA_STRATEGY[iri["schema:Quantity"]]} -CODEMETA_STRATEGY[iri["schema:Energy"]] = {**CODEMETA_STRATEGY[iri["schema:Quantity"]]} -CODEMETA_STRATEGY[iri["schema:Mass"]] = {**CODEMETA_STRATEGY[iri["schema:Quantity"]]} + +CODEMETA_STRATEGY[iri["schema:EnergyConsumptionDetails"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} + CODEMETA_STRATEGY[iri["schema:EntryPoint"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} -CODEMETA_STRATEGY[iri["schema:StructuredValue"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} -CODEMETA_STRATEGY[iri["schema:GeoCoordinates"]] = {**CODEMETA_STRATEGY[iri["schema:StructuredValue"]]} -CODEMETA_STRATEGY[iri["schema:GeoShape"]] = {**CODEMETA_STRATEGY[iri["schema:StructuredValue"]]} -CODEMETA_STRATEGY[iri["schema:NutritionInformation"]] = {**CODEMETA_STRATEGY[iri["schema:StructuredValue"]]} -CODEMETA_STRATEGY[iri["schema:MonetaryAmount"]] = {**CODEMETA_STRATEGY[iri["schema:StructuredValue"]]} -CODEMETA_STRATEGY[iri["schema:Distance"]] = {**CODEMETA_STRATEGY[iri["schema:StructuredValue"]]} -CODEMETA_STRATEGY[iri["schema:PostalCodeRangeSpecification"]] = {**CODEMETA_STRATEGY[iri["schema:StructuredValue"]]} -CODEMETA_STRATEGY[iri["schema:OpeningHoursSpecification"]] = {**CODEMETA_STRATEGY[iri["schema:StructuredValue"]]} -CODEMETA_STRATEGY[iri["schema:RepaymentSpecification"]] = {**CODEMETA_STRATEGY[iri["schema:StructuredValue"]]} -CODEMETA_STRATEGY[iri["schema:WarrantyPromise"]] = {**CODEMETA_STRATEGY[iri["schema:StructuredValue"]]} -CODEMETA_STRATEGY[iri["schema:ShippingRateSettings"]] = { - **CODEMETA_STRATEGY[iri["schema:StructuredValue"]], - iri["schema:shippingRate"]: None # FIXME: troublesome MonetaryAmount or ShippingRateSettings -} -CODEMETA_STRATEGY[iri["schema:InteractionCounter"]] = { - **CODEMETA_STRATEGY[iri["schema:StructuredValue"]], - iri["schema:interactionService"]: None, # FIXME: troublesome SoftwareApplication or WebSite - iri["schema:location"]: None # FIXME: troublesome Place or PostalAddress or VirtualLocation -} -CODEMETA_STRATEGY[iri["schema:PropertyValue"]] = { - **CODEMETA_STRATEGY[iri["schema:StructuredValue"]], - iri["schema:valueReference"]: None # FIXME: troublesome DefinedTerm or Enumeration or PropertyValue - # or QualitativeValue or QuantitativeValue or StructuredValue -} -CODEMETA_STRATEGY[iri["schema:ContactPoint"]] = { - **CODEMETA_STRATEGY[iri["schema:StructuredValue"]], - iri["schema:areaServed"]: None, # FIXME: troublesome AdministrativeArea or GeoShape or Place -} -CODEMETA_STRATEGY[iri["schema:PostalAddress"]] = {**CODEMETA_STRATEGY[iri["schema:ContactPoint"]]} -CODEMETA_STRATEGY[iri["schema:OfferShippingDetails"]] = { - **CODEMETA_STRATEGY[iri["schema:StructuredValue"]], - iri["schema:depth"]: None, # FIXME: troublesome Distance or QuantitativeValue - iri["schema:height"]: None, # FIXME: troublesome Distance or QuantitativeValue - iri["schema:shippingRate"]: None, # FIXME: troublesome MonetaryAmount or ShippingRateSettings - iri["schema:weight"]: None, # FIXME: troublesome Mass or QuantitativeValue - iri["schema:width"]: None # FIXME: troublesome Distance or QuantitativeValue -} -CODEMETA_STRATEGY[iri["schema:ShippingDeliveryTime"]] = { - **CODEMETA_STRATEGY[iri["schema:StructuredValue"]], - iri["schema:handlingTime"]: None, # FIXME: troublesome QuantitativeValue or ServicePeriod - iri["schema:transitTime"]: None # FIXME: troublesome QuantitativeValue or ServicePeriod -} -CODEMETA_STRATEGY[iri["schema:TypeAndQuantityNode"]] = { - **CODEMETA_STRATEGY[iri["schema:StructuredValue"]], - iri["schema:typeOfGood"]: None # FIXME: troublesome Product or Service -} -CODEMETA_STRATEGY[iri["schema:ServicePeriod"]] = { - **CODEMETA_STRATEGY[iri["schema:StructuredValue"]], - iri["schema:duration"]: None # FIXME: troublesome Duration or QuantitativeValue -} -CODEMETA_STRATEGY[iri["schema:QuantitativeValue"]] = { - **CODEMETA_STRATEGY[iri["schema:StructuredValue"]], - iri["schema:valueReference"]: None # FIXME: troublesome DefinedTerm or Enumeration or PropertyValue - # or QualitativeValue or QuantitativeValue or StructuredValue -} -CODEMETA_STRATEGY[iri["schema:ShippingService"]] = { - **CODEMETA_STRATEGY[iri["schema:StructuredValue"]], - iri["schema:handlingTime"]: None # FIXME: troublesome QuantitativeValue or ServicePeriod -} -CODEMETA_STRATEGY[iri["schema:ShippingConditions"]] = { - **CODEMETA_STRATEGY[iri["schema:StructuredValue"]], - iri["schema:depth"]: None, # FIXME: troublesome Distance or QuantitativeValue - iri["schema:height"]: None, # FIXME: troublesome Distance or QuantitativeValue - iri["schema:shippingRate"]: None, # FIXME: troublesome MonetaryAmount or ShippingRateSettings - iri["schema:transitTime"]: None, # FIXME: troublesome QuantitativeValue or ServicePeriod - iri["schema:weight"]: None, # FIXME: troublesome Mass or QuantitativeValue - iri["schema:width"]: None # FIXME: troublesome Distance or QuantitativeValue -} -CODEMETA_STRATEGY[iri["schema:QuantitativeValueDistribution"]] = { - **CODEMETA_STRATEGY[iri["schema:StructuredValue"]], - iri["schema:duration"]: None # FIXME: troublesome Duration or QuantitativeValue -} -CODEMETA_STRATEGY[iri["schema:MonetaryAmountDistribution"]] = { - **CODEMETA_STRATEGY[iri["schema:QuantitativeValueDistribution"]] -} -CODEMETA_STRATEGY[iri["schema:PriceSpecification"]] = {**CODEMETA_STRATEGY[iri["schema:StructuredValue"]]} -CODEMETA_STRATEGY[iri["schema:UnitPriceSpecification"]] = { - **CODEMETA_STRATEGY[iri["schema:PriceSpecification"]], - iri["schema:billingDuration"]: None, # FIXME: troublesome Duration or QuantitativeValue + +CODEMETA_STRATEGY[iri["schema:Enumeration"]] = { + **CODEMETA_STRATEGY[iri["schema:Intangible"]], + iri["schema:supersededBy"]: ACTIONS["ClassOrEnumeration"] } -CODEMETA_STRATEGY[iri["schema:DeliveryChargeSpecification"]] = { - **CODEMETA_STRATEGY[iri["schema:PriceSpecification"]], - iri["schema:areaServed"]: None, # FIXME: troublesome AdministrativeArea or GeoShape or Place - iri["schema:eligibleRegion"]: None, # FIXME: troublesome GeoShape or Place - iri["schema:ineligibleRegion"]: None # FIXME: troublesome GeoShape or Place +CODEMETA_STRATEGY[iri["schema:QualitativeValue"]] = { + **CODEMETA_STRATEGY[iri["schema:Enumeration"]], + iri[ + "schema:valueReference" + ]: ACTIONS["DefinedTermOrEnumerationOrPropertyValueOrQualitativeValueOrQuantitativeValueOrStructuredValue"] } -CODEMETA_STRATEGY[iri["schema:LocationFeatureSpecification"]] = {**CODEMETA_STRATEGY[iri["schema:PropertyValue"]]} +CODEMETA_STRATEGY[iri["schema:SizeSpecification"]] = {**CODEMETA_STRATEGY[iri["schema:QualitativeValue"]]} + CODEMETA_STRATEGY[iri["schema:GeospatialGeometry"]] = { **CODEMETA_STRATEGY[iri["schema:Intangible"]], - iri["schema:geoContains"]: None, # FIXME: troublesome GeospatialGeometry or Place - iri["schema:geoCoveredBy"]: None, # FIXME: troublesome GeospatialGeometry or Place - iri["schema:geoCovers"]: None, # FIXME: troublesome GeospatialGeometry or Place - iri["schema:geoCrosses"]: None, # FIXME: troublesome GeospatialGeometry or Place - iri["schema:geoDisjoint"]: None, # FIXME: troublesome GeospatialGeometry or Place - iri["schema:geoEquals"]: None, # FIXME: troublesome GeospatialGeometry or Place - iri["schema:geoIntersects"]: None, # FIXME: troublesome GeospatialGeometry or Place - iri["schema:geoOverlaps"]: None, # FIXME: troublesome GeospatialGeometry or Place - iri["schema:geoTouches"]: None, # FIXME: troublesome GeospatialGeometry or Place - iri["schema:geoWithin"]: None # FIXME: troublesome GeospatialGeometry or Place + iri["schema:geoContains"]: ACTIONS["GeospatialGeometryOrPlace"], + iri["schema:geoCoveredBy"]: ACTIONS["GeospatialGeometryOrPlace"], + iri["schema:geoCovers"]: ACTIONS["GeospatialGeometryOrPlace"], + iri["schema:geoCrosses"]: ACTIONS["GeospatialGeometryOrPlace"], + iri["schema:geoDisjoint"]: ACTIONS["GeospatialGeometryOrPlace"], + iri["schema:geoEquals"]: ACTIONS["GeospatialGeometryOrPlace"], + iri["schema:geoIntersects"]: ACTIONS["GeospatialGeometryOrPlace"], + iri["schema:geoOverlaps"]: ACTIONS["GeospatialGeometryOrPlace"], + iri["schema:geoTouches"]: ACTIONS["GeospatialGeometryOrPlace"], + iri["schema:geoWithin"]: ACTIONS["GeospatialGeometryOrPlace"] } + CODEMETA_STRATEGY[iri["schema:Grant"]] = { **CODEMETA_STRATEGY[iri["schema:Intangible"]], - iri["schema:fundedItem"]: None, # FIXME: troublesome BioChemEntity or CreativeWork or Event or MedicalEntity - # or Organization or Person or Product - iri["schema:funder"]: None, # FIXME: troublesome Organization or Person - iri["schema:sponsor"]: None # FIXME: troublesome Organization or Person + iri[ + "schema:fundedItem" + ]: ACTIONS["BioChemEntityOrCreativeWorkOrEventOrMedicalEntityOrOrganizationOrPersonOrProduct"], + iri["schema:funder"]: ACTIONS["OrganizationOrPerson"], + iri["schema:sponsor"]: ACTIONS["OrganizationOrPerson"] } + +CODEMETA_STRATEGY[iri["schema:HealthInsurancePlan"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} + +CODEMETA_STRATEGY[iri["schema:HealthPlanCostSharingSpecification"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} + +CODEMETA_STRATEGY[iri["schema:HealthPlanFormulary"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} + +CODEMETA_STRATEGY[iri["schema:HealthPlanNetwork"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} + CODEMETA_STRATEGY[iri["schema:ItemList"]] = { **CODEMETA_STRATEGY[iri["schema:Intangible"]], - iri["schema:itemListElement"]: None # FIXME: troublesome ListItem or Thing + iri["schema:itemListElement"]: ACTIONS["ListItemOrThing"] } CODEMETA_STRATEGY[iri["schema:OfferCatalog"]] = {**CODEMETA_STRATEGY[iri["schema:ItemList"]]} CODEMETA_STRATEGY[iri["schema:BreadcrumbList"]] = {**CODEMETA_STRATEGY[iri["schema:ItemList"]]} + CODEMETA_STRATEGY[iri["schema:Language"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} -CODEMETA_STRATEGY[iri["schema:Service"]] = { - **CODEMETA_STRATEGY[iri["schema:Intangible"]], - iri["schema:areaServed"]: None, # FIXME: troublesome AdministrativeArea or GeoShape or Place - iri["schema:brand"]: None, # FIXME: troublesome Brand or Organization - iri["schema:broker"]: None, # FIXME: troublesome Organization or Person - iri["schema:category"]: None, # FIXME: troublesome CategoryCode or Thing - iri["schema:isRelatedTo"]: None, # FIXME: troublesome Product or Service - iri["schema:isSimilarTo"]: None, # FIXME: troublesome Product or Service - iri["schema:offers Demand"]: None, # FIXME: troublesome or Offer - iri["schema:provider"]: None # FIXME: troublesome Organization or Person -} -CODEMETA_STRATEGY[iri["schema:FinancialProduct"]] = {**CODEMETA_STRATEGY[iri["schema:Service"]]} -CODEMETA_STRATEGY[iri["schema:BroadcastService"]] = {**CODEMETA_STRATEGY[iri["schema:Service"]]} -CODEMETA_STRATEGY[iri["schema:CableOrSatelliteService"]] = {**CODEMETA_STRATEGY[iri["schema:Service"]]} -CODEMETA_STRATEGY[iri["schema:LoanOrCredit"]] = {**CODEMETA_STRATEGY[iri["schema:FinancialProduct"]]} -CODEMETA_STRATEGY[iri["schema:MediaSubscription"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} -CODEMETA_STRATEGY[iri["schema:Brand"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} -CODEMETA_STRATEGY[iri["schema:HealthInsurancePlan"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} + CODEMETA_STRATEGY[iri["schema:ListItem"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} CODEMETA_STRATEGY[iri["schema:HowToItem"]] = {**CODEMETA_STRATEGY[iri["schema:ListItem"]]} CODEMETA_STRATEGY[iri["schema:HowToSupply"]] = {**CODEMETA_STRATEGY[iri["schema:HowToItem"]]} CODEMETA_STRATEGY[iri["schema:HowToTool"]] = {**CODEMETA_STRATEGY[iri["schema:HowToItem"]]} -CODEMETA_STRATEGY[iri["schema:Enumeration"]] = { + +CODEMETA_STRATEGY[iri["schema:MediaSubscription"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} + +CODEMETA_STRATEGY[iri["schema:MemberProgram"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} + +CODEMETA_STRATEGY[iri["schema:MemberProgramTier"]] = { **CODEMETA_STRATEGY[iri["schema:Intangible"]], - iri["schema:supersededBy"]: None # FIXME: troublesome Class or Enumeration -} -CODEMETA_STRATEGY[iri["schema:QualitativeValue"]] = { - **CODEMETA_STRATEGY[iri["schema:Enumeration"]], - iri["schema:valueReference"]: None # FIXME: troublesome DefinedTerm or Enumeration or PropertyValue - # or QualitativeValue or QuantitativeValue or StructuredValue + iri["schema:hasTierRequirement"]: ACTIONS["CreditCardOrMonetaryAmountOrUnitPriceSpecification"] } -CODEMETA_STRATEGY[iri["schema:SizeSpecification"]] = {**CODEMETA_STRATEGY[iri["schema:QualitativeValue"]]} -CODEMETA_STRATEGY[iri["schema:Class"]] = { + +CODEMETA_STRATEGY[iri["schema:MenuItem"]] = { **CODEMETA_STRATEGY[iri["schema:Intangible"]], - iri["schema:supersededBy"]: None # FIXME: troublesome Class or Enumeration + iri["schema:menuAddOn"]: ACTIONS["MenuItemOrMenuSection"], + iri["schema:offers"]: ACTIONS["DemandOrOffer"] } -CODEMETA_STRATEGY[iri["schema:HealthPlanFormulary"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} -CODEMETA_STRATEGY[iri["schema:HealthPlanCostSharingSpecification"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} -CODEMETA_STRATEGY[iri["schema:HealthPlanNetwork"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} -CODEMETA_STRATEGY[iri["schema:MemberProgramTier"]] = { + +CODEMETA_STRATEGY[iri["schema:MerchantReturnPolicy"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} + +CODEMETA_STRATEGY[iri["schema:MerchantReturnPolicySeasonalOverride"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} + +CODEMETA_STRATEGY[iri["schema:Occupation"]] = { **CODEMETA_STRATEGY[iri["schema:Intangible"]], - iri["schema:hasTierRequirement"]: None # FIXME: troublesome CreditCard or MonetaryAmount or UnitPriceSpecification + iri["schema:estimatedSalary"]: ACTIONS["MonetaryAmountOrMonetaryAmountDistribution"] } -CODEMETA_STRATEGY[iri["schema:MemberProgram"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} -CODEMETA_STRATEGY[iri["schema:MenuItem"]] = { + +CODEMETA_STRATEGY[iri["schema:OccupationalExperienceRequirements"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} + +CODEMETA_STRATEGY[iri["schema:Offer"]] = { **CODEMETA_STRATEGY[iri["schema:Intangible"]], - iri["schema:menuAddOn"]: None, # FIXME: troublesome MenuItem or MenuSection - iri["schema:offers"]: None # FIXME: troublesome Demand or Offer + iri["schema:acceptedPaymentMethod"]: ACTIONS["LoanOrCreditOrPaymentMethod"], + iri["schema:areaServed"]: ACTIONS["AdministrativeAreaOrGeoShapeOrPlace"], + iri["schema:category"]: ACTIONS["CategoryCodeOrThing"], + iri["schema:eligibleRegion"]: ACTIONS["GeoShapeOrPlace"], + iri["schema:ineligibleRegion"]: ACTIONS["GeoShapeOrPlace"], + iri["schema:itemOffered"]: ACTIONS["AggregateOfferOrCreativeWorkOrEventOrMenuItemOrProductOrServiceOrTrip"], + iri["schema:leaseLength"]: ACTIONS["DurationOrQuantitativeValue"], + iri["schema:offeredBy"]: ACTIONS["OrganizationOrPerson"], + iri["schema:seller"]: ACTIONS["OrganizationOrPerson"] } -CODEMETA_STRATEGY[iri["schema:MerchantReturnPolicy"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} -CODEMETA_STRATEGY[iri["schema:MerchantReturnPolicySeasonalOverride"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} -CODEMETA_STRATEGY[iri["schema:SpeakableSpecification"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} +CODEMETA_STRATEGY[iri["schema:AggregateOffer"]] = { + **CODEMETA_STRATEGY[iri["schema:Offer"]], + iri["schema:offers"]: ACTIONS["DemandOrOffer"] +} + CODEMETA_STRATEGY[iri["schema:PaymentMethod"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} + CODEMETA_STRATEGY[iri["schema:ProgramMembership"]] = { **CODEMETA_STRATEGY[iri["schema:Intangible"]], - iri["schema:member"]: None # FIXME: troublesome Organization or Person + iri["schema:member"]: ACTIONS["OrganizationOrPerson"] } -CODEMETA_STRATEGY[iri["schema:Schedule"]] = { + +CODEMETA_STRATEGY[iri["schema:Property"]] = { **CODEMETA_STRATEGY[iri["schema:Intangible"]], - iri["schema:duration"]: None # FIXME: troublesome Duration or QuantitativeValue + iri["schema:supersededBy"]: ACTIONS["ClassOrEnumerationOrProperty"] } -CODEMETA_STRATEGY[iri["schema:ServiceChannel"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} -CODEMETA_STRATEGY[iri["schema:VirtualLocation"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} -CODEMETA_STRATEGY[iri["schema:Occupation"]] = { + +CODEMETA_STRATEGY[iri["schema:Quantity"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} +CODEMETA_STRATEGY[iri["schema:Duration"]] = {**CODEMETA_STRATEGY[iri["schema:Quantity"]]} +CODEMETA_STRATEGY[iri["schema:Energy"]] = {**CODEMETA_STRATEGY[iri["schema:Quantity"]]} +CODEMETA_STRATEGY[iri["schema:Mass"]] = {**CODEMETA_STRATEGY[iri["schema:Quantity"]]} + +CODEMETA_STRATEGY[iri["schema:Rating"]] = { **CODEMETA_STRATEGY[iri["schema:Intangible"]], - iri["schema:estimatedSalary"]: None # FIXME: troublesome MonetaryAmount or MonetaryAmountDistribution + iri["schema:author"]: ACTIONS["OrganizationOrPerson"] } -CODEMETA_STRATEGY[iri["schema:EnergyConsumptionDetails"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} -CODEMETA_STRATEGY[iri["schema:OccupationalExperienceRequirements"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} -CODEMETA_STRATEGY[iri["schema:AlignmentObject"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} -CODEMETA_STRATEGY[iri["schema:BroadcastFrequencySpecification"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} -CODEMETA_STRATEGY[iri["schema:BroadcastChannel"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} -CODEMETA_STRATEGY[iri["schema:ConstraintNode"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} -CODEMETA_STRATEGY[iri["schema:StatisticalVariable"]] = {**CODEMETA_STRATEGY[iri["schema:ConstraintNode"]]} -CODEMETA_STRATEGY[iri["schema:Property"]] = { +CODEMETA_STRATEGY[iri["schema:AggregateRating"]] = {**CODEMETA_STRATEGY[iri["schema:Rating"]]} + +CODEMETA_STRATEGY[iri["schema:Schedule"]] = { **CODEMETA_STRATEGY[iri["schema:Intangible"]], - iri["schema:supersededBy"]: None, # FIXME: troublesome Class or Enumeration or Property + iri["schema:duration"]: ACTIONS["DurationOrQuantitativeValue"] } -CODEMETA_STRATEGY[iri["schema:Place"]] = { - **CODEMETA_STRATEGY[iri["schema:Thing"]], - iri["schema:geo"]: None, # FIXME: troublesome GeoCoordinates or GeoShape - iri["schema:geoContains"]: None, # FIXME: troublesome GeospatialGeometry or Place - iri["schema:geoCoveredBy"]: None, # FIXME: troublesome GeospatialGeometry or Place - iri["schema:geoCovers"]: None, # FIXME: troublesome GeospatialGeometry or Place - iri["schema:geoCrosses"]: None, # FIXME: troublesome GeospatialGeometry or Place - iri["schema:geoDisjoint"]: None, # FIXME: troublesome GeospatialGeometry or Place - iri["schema:geoEquals"]: None, # FIXME: troublesome GeospatialGeometry or Place - iri["schema:geoIntersects"]: None, # FIXME: troublesome GeospatialGeometry or Place - iri["schema:geoOverlaps"]: None, # FIXME: troublesome GeospatialGeometry or Place - iri["schema:geoTouches"]: None, # FIXME: troublesome GeospatialGeometry or Place - iri["schema:geoWithin"]: None, # FIXME: troublesome GeospatialGeometry or Place - iri["schema:photo"]: None # FIXME: troublesome ImageObject or Photograph -} -CODEMETA_STRATEGY[iri["schema:AdministrativeArea"]] = {**CODEMETA_STRATEGY[iri["schema:Place"]]} -CODEMETA_STRATEGY[iri["schema:Country"]] = {**CODEMETA_STRATEGY[iri["schema:AdministrativeArea"]]} -CODEMETA_STRATEGY[iri["schema:CivicStructure"]] = {**CODEMETA_STRATEGY[iri["schema:Place"]]} +CODEMETA_STRATEGY[iri["schema:Series"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} -CODEMETA_STRATEGY[iri["schema:CreativeWorkSeries"]] = { - **CODEMETA_STRATEGY[iri["schema:CreativeWork"]], - **CODEMETA_STRATEGY[iri["schema:Series"]] +CODEMETA_STRATEGY[iri["schema:Service"]] = { + **CODEMETA_STRATEGY[iri["schema:Intangible"]], + iri["schema:areaServed"]: ACTIONS["AdministrativeAreaOrGeoShapeOrPlace"], + iri["schema:brand"]: ACTIONS["BrandOrOrganization"], + iri["schema:broker"]: ACTIONS["OrganizationOrPerson"], + iri["schema:category"]: ACTIONS["CategoryCodeOrThing"], + iri["schema:isRelatedTo"]: ACTIONS["ProductOrService"], + iri["schema:isSimilarTo"]: ACTIONS["ProductOrService"], + iri["schema:offers"]: ACTIONS["DemandOrOffer"], + iri["schema:provider"]: ACTIONS["OrganizationOrPerson"] } +CODEMETA_STRATEGY[iri["schema:BroadcastService"]] = {**CODEMETA_STRATEGY[iri["schema:Service"]]} +CODEMETA_STRATEGY[iri["schema:CableOrSatelliteService"]] = {**CODEMETA_STRATEGY[iri["schema:Service"]]} +CODEMETA_STRATEGY[iri["schema:FinancialProduct"]] = {**CODEMETA_STRATEGY[iri["schema:Service"]]} +CODEMETA_STRATEGY[iri["schema:LoanOrCredit"]] = {**CODEMETA_STRATEGY[iri["schema:FinancialProduct"]]} -CODEMETA_STRATEGY[iri["schema:HowToSection"]] = { - **CODEMETA_STRATEGY[iri["schema:CreativeWork"]], - **CODEMETA_STRATEGY[iri["schema:ItemList"]], - **CODEMETA_STRATEGY[iri["schema:ListItem"]] +CODEMETA_STRATEGY[iri["schema:ServiceChannel"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} + +CODEMETA_STRATEGY[iri["schema:SpeakableSpecification"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} + +CODEMETA_STRATEGY[iri["schema:StructuredValue"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} +CODEMETA_STRATEGY[iri["schema:ContactPoint"]] = { + **CODEMETA_STRATEGY[iri["schema:StructuredValue"]], + iri["schema:areaServed"]: ACTIONS["AdministrativeAreaOrGeoShapeOrPlace"] } -CODEMETA_STRATEGY[iri["schema:HowToStep"]] = { - **CODEMETA_STRATEGY[iri["schema:CreativeWork"]], - **CODEMETA_STRATEGY[iri["schema:ItemList"]], - **CODEMETA_STRATEGY[iri["schema:ListItem"]] +CODEMETA_STRATEGY[iri["schema:PostalAddress"]] = {**CODEMETA_STRATEGY[iri["schema:ContactPoint"]]} +CODEMETA_STRATEGY[iri["schema:Distance"]] = {**CODEMETA_STRATEGY[iri["schema:StructuredValue"]]} +CODEMETA_STRATEGY[iri["schema:GeoCoordinates"]] = {**CODEMETA_STRATEGY[iri["schema:StructuredValue"]]} +CODEMETA_STRATEGY[iri["schema:GeoShape"]] = {**CODEMETA_STRATEGY[iri["schema:StructuredValue"]]} +CODEMETA_STRATEGY[iri["schema:InteractionCounter"]] = { + **CODEMETA_STRATEGY[iri["schema:StructuredValue"]], + iri["schema:interactionService"]: ACTIONS["SoftwareApplicationOrWebSite"], + iri["schema:location"]: ACTIONS["PlaceOrPostalAddressOrVirtualLocation"] } - -CODEMETA_STRATEGY[iri["schema:Event"]] = { - **CODEMETA_STRATEGY[iri["schema:Thing"]], - iri["schema:actor"]: None, # FIXME: troublesome PerformingGroup or Person - iri["schema:attendee"]: None, # FIXME: troublesome Organization or Person - iri["schema:composer"]: None, # FIXME: troublesome Organization or Person - iri["schema:contributor"]: None, # FIXME: troublesome Organization or Person - iri["schema:dircetor"]: ACTIONS["merge_match_person"], - iri["schema:duration"]: None, # FIXME: troublesome Duration or QuantitativeValue - iri["schema:funder"]: None, # FIXME: troublesome Organization or Person - iri["schema:location"]: None, # FIXME: troublesome Place or PostalAddress or VirtualLocation - iri["schema:offers"]: None, # FIXME: troublesome Demand or Offer - iri["schema:organizer"]: None, # FIXME: troublesome Organization or Person - iri["schema:performer"]: None, # FIXME: troublesome Organization or Person - iri["schema:sponsor"]: None, # FIXME: troublesome Organization or Person - iri["schema:translator"]: None # FIXME: troublesome Organization or Person +CODEMETA_STRATEGY[iri["schema:MonetaryAmount"]] = {**CODEMETA_STRATEGY[iri["schema:StructuredValue"]]} +CODEMETA_STRATEGY[iri["schema:NutritionInformation"]] = {**CODEMETA_STRATEGY[iri["schema:StructuredValue"]]} +CODEMETA_STRATEGY[iri["schema:OfferShippingDetails"]] = { + **CODEMETA_STRATEGY[iri["schema:StructuredValue"]], + iri["schema:depth"]: ACTIONS["DistanceOrQuantitativeValue"], + iri["schema:height"]: ACTIONS["DistanceOrQuantitativeValue"], + iri["schema:shippingRate"]: ACTIONS["MonetaryAmountOrShippingRateSettings"], + iri["schema:weight"]:ACTIONS["MassOrQuantitativeValue"], + iri["schema:width"]: ACTIONS["DistanceOrQuantitativeValue"] } -CODEMETA_STRATEGY[iri["schema:PublicationEvent"]] = { - **CODEMETA_STRATEGY[iri["schema:Event"]], - iri["schema:publishedBy"]: None, # FIXME: troublesome Organization or Person +CODEMETA_STRATEGY[iri["schema:OpeningHoursSpecification"]] = {**CODEMETA_STRATEGY[iri["schema:StructuredValue"]]} +CODEMETA_STRATEGY[iri["schema:PostalCodeRangeSpecification"]] = {**CODEMETA_STRATEGY[iri["schema:StructuredValue"]]} +CODEMETA_STRATEGY[iri["schema:PriceSpecification"]] = {**CODEMETA_STRATEGY[iri["schema:StructuredValue"]]} +CODEMETA_STRATEGY[iri["schema:DeliveryChargeSpecification"]] = { + **CODEMETA_STRATEGY[iri["schema:PriceSpecification"]], + iri["schema:areaServed"]: ACTIONS["AdministrativeAreaOrGeoShapeOrPlace"], + iri["schema:eligibleRegion"]: ACTIONS["GeoShapeOrPlace"], + iri["schema:ineligibleRegion"]: ACTIONS["GeoShapeOrPlace"] } - -CODEMETA_STRATEGY[iri["schema:BioChemEntity"]] = { - **CODEMETA_STRATEGY[iri["schema:Thing"]], - iri["schema:associatedDisease"]: None, # FIXME: troublesome MedicalCondition or PropertyValue - iri["schema:hasMolecularFunction"]: None, # FIXME: troublesome DefinedTerm or PropertyValue - iri["schema:isInvolvedInBiologicalProcess"]: None, # FIXME: troublesome DefinedTerm or PropertyValue - iri["schema:isLocatedInSubcellularLocation"]: None, # FIXME: troublesome DefinedTerm or PropertyValue - iri["schema:taxonomicRange"]: None # FIXME: troublesome DefinedTerm or Taxon +CODEMETA_STRATEGY[iri["schema:UnitPriceSpecification"]] = { + **CODEMETA_STRATEGY[iri["schema:PriceSpecification"]], + iri["schema:billingDuration"]: ACTIONS["DurationOrQuantitativeValue"] } -CODEMETA_STRATEGY[iri["schema:Gene"]] = { - **CODEMETA_STRATEGY[iri["schema:BioChemEntity"]], +CODEMETA_STRATEGY[iri["schema:PropertyValue"]] = { + **CODEMETA_STRATEGY[iri["schema:StructuredValue"]], iri[ - "schema:expressedIn" - ]: None # FIXME: troublesome AnatomicalStructure or AnatomicalSystem or BioChemEntity or DefinedTerm + "schema:valueReference" + ]: ACTIONS["DefinedTermOrEnumerationOrPropertyValueOrQualitativeValueOrQuantitativeValueOrStructuredValue"] +} +CODEMETA_STRATEGY[iri["schema:LocationFeatureSpecification"]] = {**CODEMETA_STRATEGY[iri["schema:PropertyValue"]]} +CODEMETA_STRATEGY[iri["schema:QuantitativeValue"]] = { + **CODEMETA_STRATEGY[iri["schema:StructuredValue"]], + iri[ + "schema:valueReference" + ]: ACTIONS["DefinedTermOrEnumerationOrPropertyValueOrQualitativeValueOrQuantitativeValueOrStructuredValue"] +} +CODEMETA_STRATEGY[iri["schema:QuantitativeValueDistribution"]] = { + **CODEMETA_STRATEGY[iri["schema:StructuredValue"]], + iri["schema:duration"]: ACTIONS["DurationOrQuantitativeValue"] +} +CODEMETA_STRATEGY[iri["schema:MonetaryAmountDistribution"]] = { + **CODEMETA_STRATEGY[iri["schema:QuantitativeValueDistribution"]] +} +CODEMETA_STRATEGY[iri["schema:RepaymentSpecification"]] = {**CODEMETA_STRATEGY[iri["schema:StructuredValue"]]} +CODEMETA_STRATEGY[iri["schema:ServicePeriod"]] = { + **CODEMETA_STRATEGY[iri["schema:StructuredValue"]], + iri["schema:duration"]: ACTIONS["DurationOrQuantitativeValue"] +} +CODEMETA_STRATEGY[iri["schema:ShippingConditions"]] = { + **CODEMETA_STRATEGY[iri["schema:StructuredValue"]], + iri["schema:depth"]: ACTIONS["DistanceOrQuantitativeValue"], + iri["schema:height"]: ACTIONS["DistanceOrQuantitativeValue"], + iri["schema:shippingRate"]: ACTIONS["MonetaryAmountOrShippingRateSettings"], + iri["schema:transitTime"]: ACTIONS["QuantitativeValueOrServicePeriod"], + iri["schema:weight"]:ACTIONS["MassOrQuantitativeValue"], + iri["schema:width"]: ACTIONS["DistanceOrQuantitativeValue"] +} +CODEMETA_STRATEGY[iri["schema:ShippingDeliveryTime"]] = { + **CODEMETA_STRATEGY[iri["schema:StructuredValue"]], + iri["schema:handlingTime"]: ACTIONS["QuantitativeValueOrServicePeriod"], + iri["schema:transitTime"]: ACTIONS["QuantitativeValueOrServicePeriod"] +} +CODEMETA_STRATEGY[iri["schema:ShippingRateSettings"]] = { + **CODEMETA_STRATEGY[iri["schema:StructuredValue"]], + iri["schema:shippingRate"]: ACTIONS["MonetaryAmountOrShippingRateSettings"] +} +CODEMETA_STRATEGY[iri["schema:ShippingService"]] = { + **CODEMETA_STRATEGY[iri["schema:StructuredValue"]], + iri["schema:handlingTime"]: ACTIONS["QuantitativeValueOrServicePeriod"] +} +CODEMETA_STRATEGY[iri["schema:TypeAndQuantityNode"]] = { + **CODEMETA_STRATEGY[iri["schema:StructuredValue"]], + iri["schema:typeOfGood"]: ACTIONS["ProductOrService"] } +CODEMETA_STRATEGY[iri["schema:WarrantyPromise"]] = {**CODEMETA_STRATEGY[iri["schema:StructuredValue"]]} + +CODEMETA_STRATEGY[iri["schema:VirtualLocation"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} + + CODEMETA_STRATEGY[iri["schema:MedicalEntity"]] = {**CODEMETA_STRATEGY[iri["schema:Thing"]]} -CODEMETA_STRATEGY[iri["schema:MedicalIntangible"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalEntity"]]} -CODEMETA_STRATEGY[iri["schema:DrugLegalStatus"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalIntangible"]]} -CODEMETA_STRATEGY[iri["schema:DDxElement"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalIntangible"]]} -CODEMETA_STRATEGY[iri["schema:MedicalConditionStage"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalIntangible"]]} -CODEMETA_STRATEGY[iri["schema:DrugStrength"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalIntangible"]]} -CODEMETA_STRATEGY[iri["schema:DoseSchedule"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalIntangible"]]} -CODEMETA_STRATEGY[iri["schema:MaximumDoseSchedule"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalIntangible"]]} -CODEMETA_STRATEGY[iri["schema:MedicalGuideline"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalEntity"]]} + + CODEMETA_STRATEGY[iri["schema:AnatomicalStructure"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalEntity"]]} -CODEMETA_STRATEGY[iri["schema:MedicalCause"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalEntity"]]} + +CODEMETA_STRATEGY[iri["schema:AnatomicalSystem"]] = { + **CODEMETA_STRATEGY[iri["schema:MedicalEntity"]], + iri["schema:comprisedOf"]: ACTIONS["AnatomicalStructureOrAnatomicalSystem"] +} + CODEMETA_STRATEGY[iri["schema:DrugClass"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalEntity"]]} + CODEMETA_STRATEGY[iri["schema:LifestyleModification"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalEntity"]]} -CODEMETA_STRATEGY[iri["schema:MedicalRiskFactor"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalEntity"]]} -CODEMETA_STRATEGY[iri["schema:MedicalTest"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalEntity"]]} -CODEMETA_STRATEGY[iri["schema:MedicalDevice"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalEntity"]]} -CODEMETA_STRATEGY[iri["schema:MedicalTest"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalEntity"]]} + +CODEMETA_STRATEGY[iri["schema:MedicalCause"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalEntity"]]} + +CODEMETA_STRATEGY[iri["schema:MedicalCondition"]] = { + **CODEMETA_STRATEGY[iri["schema:MedicalEntity"]], + iri["schema:associatedAnatomy"]: ACTIONS["AnatomicalStructureOrAnatomicalSystemOrSuperficialAnatomy"], + iri["schema:possibleTreatment"]: ACTIONS["DrugOrDrugClassOrLifestyleModificationOrMedicalTherapy"], + iri["schema:secondaryPrevention"]: ACTIONS["DrugOrDrugClassOrLifestyleModificationOrMedicalTherapy"] +} +CODEMETA_STRATEGY[iri["schema:MedicalSignOrSymptom"]] = { + **CODEMETA_STRATEGY[iri["schema:MedicalCondition"]], + iri["schema:possibleTreatment"]: ACTIONS["DrugOrDrugClassOrLifestyleModificationOrMedicalTherapy"] +} +CODEMETA_STRATEGY[iri["schema:MedicalSign"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalSignOrSymptom"]]} + CODEMETA_STRATEGY[iri["schema:MedicalContraindication"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalEntity"]]} + +CODEMETA_STRATEGY[iri["schema:MedicalDevice"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalEntity"]]} + +CODEMETA_STRATEGY[iri["schema:MedicalGuideline"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalEntity"]]} + +CODEMETA_STRATEGY[iri["schema:MedicalIntangible"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalEntity"]]} +CODEMETA_STRATEGY[iri["schema:DDxElement"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalIntangible"]]} +CODEMETA_STRATEGY[iri["schema:DrugLegalStatus"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalIntangible"]]} +CODEMETA_STRATEGY[iri["schema:DoseSchedule"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalIntangible"]]} +CODEMETA_STRATEGY[iri["schema:DrugStrength"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalIntangible"]]} +CODEMETA_STRATEGY[iri["schema:MaximumDoseSchedule"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalIntangible"]]} +CODEMETA_STRATEGY[iri["schema:MedicalConditionStage"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalIntangible"]]} + CODEMETA_STRATEGY[iri["schema:MedicalProcedure"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalEntity"]]} CODEMETA_STRATEGY[iri["schema:TherapeuticProcedure"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalProcedure"]]} CODEMETA_STRATEGY[iri["schema:MedicalTherapy"]] = {**CODEMETA_STRATEGY[iri["schema:TherapeuticProcedure"]]} + +CODEMETA_STRATEGY[iri["schema:MedicalRiskFactor"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalEntity"]]} + CODEMETA_STRATEGY[iri["schema:MedicalStudy"]] = { **CODEMETA_STRATEGY[iri["schema:MedicalEntity"]], - iri["schema:sponsor"]: None # FIXME: troublesome Organization or Person + iri["schema:sponsor"]: ACTIONS["OrganizationOrPerson"] } -CODEMETA_STRATEGY[iri["schema:MedicalCondition"]] = { + +CODEMETA_STRATEGY[iri["schema:MedicalTest"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalEntity"]]} + +CODEMETA_STRATEGY[iri["schema:SuperficialAnatomy"]] = { **CODEMETA_STRATEGY[iri["schema:MedicalEntity"]], - iri[ - "schema:associatedAnatomy" - ]: None, # FIXME: troublesome AnatomicalStructure or AnatomicalSystem or SuperficialAnatomy - iri[ - "schema:possibleTreatment" - ]: None, # FIXME: troublesome Drug or DrugClass or LifestyleModification or MedicalTherapy - iri[ - "schema:secondaryPrevention" - ]: None # FIXME: troublesome Drug or DrugClass or LifestyleModification or MedicalTherapy + iri["schema:relatedAnatomy"]: ACTIONS["AnatomicalStructureOrAnatomicalSystem"] } -CODEMETA_STRATEGY[iri["schema:MedicalSignOrSymptom"]] = { - **CODEMETA_STRATEGY[iri["schema:MedicalCondition"]], - iri[ - "schema:possibleTreatment" - ]: None # FIXME: troublesome Drug or DrugClass or LifestyleModification or MedicalTherapy + + + +CODEMETA_STRATEGY[iri["schema:Organization"]] = { + **CODEMETA_STRATEGY[iri["schema:Thing"]], + iri["schema:acceptedPaymentMethod"]: ACTIONS["LoanOrCreditOrPaymentMethod"], + iri["schema:alumni"]: ACTIONS["Person"], + iri["schema:areaServed"]: ACTIONS["AdministrativeAreaOrGeoShapeOrPlace"], + iri["schema:brand"]: ACTIONS["BrandOrOrganization"], + iri["schema:employee"]: ACTIONS["Person"], + iri["schema:founder"]: ACTIONS["OrganizationOrPerson"], + iri["schema:funder"]: ACTIONS["OrganizationOrPerson"], + iri["schema:legalRepresentative"]: ACTIONS["Person"], + iri["schema:location"]: ACTIONS["PlaceOrPostalAddressOrVirtualLocation"], + iri["schema:member"]: ACTIONS["OrganizationOrPerson"], + iri["schema:memberOf"]: ACTIONS["MemberProgramTierOrOrganizationOrProgramMembership"], + iri["schema:ownershipFundingInfo"]: ACTIONS["AboutPageOrCreativeWork"], + iri["schema:sponsor"]: ACTIONS["OrganizationOrPerson"] } -CODEMETA_STRATEGY[iri["schema:MedicalSign"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalSignOrSymptom"]]} -CODEMETA_STRATEGY[iri["schema:SuperficialAnatomy"]] = { - **CODEMETA_STRATEGY[iri["schema:MedicalEntity"]], - iri["schema:relatedAnatomy"]: None # FIXME: troublesome AnatomicalStructure or AnatomicalSystem + + +CODEMETA_STRATEGY[iri["schema:PerformingGroup"]] = {**CODEMETA_STRATEGY[iri["schema:Organization"]]} +CODEMETA_STRATEGY[iri["schema:MusicGroup"]] = { + **CODEMETA_STRATEGY[iri["schema:PerformingGroup"]], + iri["schema:musicGroupMember"]: ACTIONS["Person"], + iri["schema:track"]: ACTIONS["ItemListOrMusicRecording"] } -CODEMETA_STRATEGY[iri["schema:AnatomicalSystem"]] = { - **CODEMETA_STRATEGY[iri["schema:MedicalEntity"]], - iri["schema:comprisedOf"]: None # FIXME: troublesome AnatomicalStructure or AnatomicalSystem + + + +CODEMETA_STRATEGY[iri["schema:Person"]] = { + **CODEMETA_STRATEGY[iri["schema:Thing"]], + iri["schema:alumniOf"]: ACTIONS["EducationalOrganizationOrOrganization"], + iri["schema:brand"]: ACTIONS["BrandOrOrganization"], + iri["schema:children"]: ACTIONS["Person"], + iri["schema:colleague"]: ACTIONS["Person"], + iri["schema:follows"]: ACTIONS["Person"], + iri["schema:funder"]: ACTIONS["OrganizationOrPerson"], + iri["schema:height"]: ACTIONS["DistanceOrQuantitativeValue"], + iri["schema:homeLocation"]: ACTIONS["ContactPointOrPlace"], + iri["schema:knows"]: ACTIONS["Person"], + iri["schema:memberOf"]: ACTIONS["MemberProgramTierOrOrganizationOrProgramMembership"], + iri["schema:netWorth"]: ACTIONS["MonetaryAmountOrPriceSpecification"], + iri["schema:parent"]: ACTIONS["Person"], + iri["schema:pronouns"]: ACTIONS["DefinedTermOrStructuredValue"], + iri["schema:relatedTo"]: ACTIONS["Person"], + iri["schema:sibling"]: ACTIONS["Person"], + iri["schema:sponsor"]: ACTIONS["OrganizationOrPerson"], + iri["schema:spouse"]: ACTIONS["Person"], + iri["schema:weight"]:ACTIONS["MassOrQuantitativeValue"], + iri["schema:workLocation"]: ACTIONS["ContactPointOrPlace"] } -CODEMETA_STRATEGY[iri["schema:MedicalCode"]] = { - **CODEMETA_STRATEGY[iri["schema:CategoryCode"]], - **CODEMETA_STRATEGY[iri["schema:MedicalIntangible"]] + + +CODEMETA_STRATEGY[iri["schema:Place"]] = { + **CODEMETA_STRATEGY[iri["schema:Thing"]], + iri["schema:geo"]: ACTIONS["GeoCoordinatesOrGeoShape"], + iri["schema:geoContains"]: ACTIONS["GeospatialGeometryOrPlace"], + iri["schema:geoCoveredBy"]: ACTIONS["GeospatialGeometryOrPlace"], + iri["schema:geoCovers"]: ACTIONS["GeospatialGeometryOrPlace"], + iri["schema:geoCrosses"]: ACTIONS["GeospatialGeometryOrPlace"], + iri["schema:geoDisjoint"]: ACTIONS["GeospatialGeometryOrPlace"], + iri["schema:geoEquals"]: ACTIONS["GeospatialGeometryOrPlace"], + iri["schema:geoIntersects"]: ACTIONS["GeospatialGeometryOrPlace"], + iri["schema:geoOverlaps"]: ACTIONS["GeospatialGeometryOrPlace"], + iri["schema:geoTouches"]: ACTIONS["GeospatialGeometryOrPlace"], + iri["schema:geoWithin"]: ACTIONS["GeospatialGeometryOrPlace"], + iri["schema:photo"]: ACTIONS["ImageObjectOrPhotograph"] } + +CODEMETA_STRATEGY[iri["schema:AdministrativeArea"]] = {**CODEMETA_STRATEGY[iri["schema:Place"]]} +CODEMETA_STRATEGY[iri["schema:Country"]] = {**CODEMETA_STRATEGY[iri["schema:AdministrativeArea"]]} + +CODEMETA_STRATEGY[iri["schema:CivicStructure"]] = {**CODEMETA_STRATEGY[iri["schema:Place"]]} + + + CODEMETA_STRATEGY[iri["schema:Product"]] = { **CODEMETA_STRATEGY[iri["schema:Thing"]], - iri["schema:brand"]: None, # FIXME: troublesome Brand or Organization - iri["schema:category"]: None, # FIXME: troublesome CategoryCode or Thing - iri["schema:depth"]: None, # FIXME: troublesome Distance or QuantitativeValue - iri["schema:height"]: None, # FIXME: troublesome Distance or QuantitativeValue - iri["schema:isRelatedTo"]: None, # FIXME: troublesome Product or Service - iri["schema:isSimilarTo"]: None, # FIXME: troublesome Product or Service - iri["schema:isVariantOf"]: None, # FIXME: troublesome ProductGroup or ProductModel - iri["schema:negativeNotes"]: None, # FIXME: troublesome ItemList or ListItem or WebContent - iri["schema:offers"]: None, # FIXME: troublesome Demand or Offer - iri["schema:positiveNotes"]: None, # FIXME: troublesome ItemList or ListItem or WebContent - iri["schema:size"]: None, # FIXME: troublesome DefinedTerm or QuantitativeValue or SizeSpecification - iri["schema:weight"]: None, # FIXME: troublesome Mass or QuantitativeValue - iri["schema:width"]: None, # FIXME: troublesome Distance or QuantitativeValue + iri["schema:brand"]: ACTIONS["BrandOrOrganization"], + iri["schema:category"]: ACTIONS["CategoryCodeOrThing"], + iri["schema:depth"]: ACTIONS["DistanceOrQuantitativeValue"], + iri["schema:height"]: ACTIONS["DistanceOrQuantitativeValue"], + iri["schema:isRelatedTo"]: ACTIONS["ProductOrService"], + iri["schema:isSimilarTo"]: ACTIONS["ProductOrService"], + iri["schema:isVariantOf"]: ACTIONS["ProductGroupOrProductModel"], + iri["schema:negativeNotes"]: ACTIONS["ItemListOrListItemOrWebContent"], + iri["schema:offers"]: ACTIONS["DemandOrOffer"], + iri["schema:positiveNotes"]: ACTIONS["ItemListOrListItemOrWebContent"], + iri["schema:size"]: ACTIONS["DefinedTermOrQuantitativeValueOrSizeSpecification"], + iri["schema:weight"]:ACTIONS["MassOrQuantitativeValue"], + iri["schema:width"]: ACTIONS["DistanceOrQuantitativeValue"] } + + CODEMETA_STRATEGY[iri["schema:ProductGroup"]] = {**CODEMETA_STRATEGY[iri["schema:Product"]]} -CODEMETA_STRATEGY[iri["schema:Drug"]] = { - **CODEMETA_STRATEGY[iri["schema:Product"]], - **CODEMETA_STRATEGY[iri["schema:MedicalEntity"]] -} + CODEMETA_STRATEGY[iri["schema:ProductModel"]] = { **CODEMETA_STRATEGY[iri["schema:Product"]], - iri["schema:isVariantOf"]: None, # FIXME: troublesome ProductGroup or ProductModel + iri["schema:isVariantOf"]: ACTIONS["ProductGroupOrProductModel"] } -CODEMETA_STRATEGY[iri["schema:PaymentCard"]] = { - **CODEMETA_STRATEGY[iri["schema:FinancialProduct"]], - **CODEMETA_STRATEGY[iri["schema:PaymentMethod"]] -} -CODEMETA_STRATEGY[iri["schema:CreditCard"]] = { - **CODEMETA_STRATEGY[iri["schema:LoanOrCredit"]], - **CODEMETA_STRATEGY[iri["schema:PaymentCard"]] + + +CODEMETA_STRATEGY[iri["schema:Taxon"]] = {**CODEMETA_STRATEGY[iri["schema:Thing"]]} + + + +CODEMETA_STRATEGY[iri["schema:CreativeWorkSeries"]] = { + **CODEMETA_STRATEGY[iri["schema:CreativeWork"]], + **CODEMETA_STRATEGY[iri["schema:Series"]] } -CODEMETA_STRATEGY[iri["schema:Organization"]] = { - **CODEMETA_STRATEGY[iri["schema:Thing"]], - iri["schema:acceptedPaymentMethod"]: None, # FIXME: troublesome LoanOrCredit or PaymentMethod - iri["schema:alumni"]: ACTIONS["merge_match_person"], - iri["schema:areaServed"]: None, # FIXME: troublesome AdministrativeArea or GeoShape or Place - iri["schema:brand"]: None, # FIXME: troublesome Brand or Organization - iri["schema:employee"]: ACTIONS["merge_match_person"], - iri["schema:founder"]: None, # FIXME: troublesome Organization or Person - iri["schema:funder"]: None, # FIXME: troublesome Organization or Person - iri["schema:legalRepresentative"]: ACTIONS["merge_match_person"], - iri["schema:location"]: None, # FIXME: troublesome Place or PostalAddress or Text or VirtualLocation - iri["schema:member"]: None, # FIXME: troublesome Organization or Person - iri["schema:memberOf"]: None, # FIXME: troublesome MemberProgramTier or Organization or ProgramMembership - iri["schema:ownershipFundingInfo"]: None, # FIXME: troublesome AboutPage or CreativeWork - iri["schema:sponsor"]: None # FIXME: troublesome Organization or Person + +CODEMETA_STRATEGY[iri["schema:DefinedRegion"]] = { + **CODEMETA_STRATEGY[iri["schema:Place"]], + **CODEMETA_STRATEGY[iri["schema:StructuredValue"]] } -CODEMETA_STRATEGY[iri["schema:PerformingGroup"]] = {**CODEMETA_STRATEGY[iri["schema:Organization"]]} -CODEMETA_STRATEGY[iri["schema:MusicGroup"]] = { - **CODEMETA_STRATEGY[iri["schema:PerformingGroup"]], - iri["schema:musicGroupMember"]: ACTIONS["merge_match_person"], - iri["schema:track"]: None # FIXME: troublesome ItemList or MusicRecording + + +CODEMETA_STRATEGY[iri["schema:Drug"]] = { + **CODEMETA_STRATEGY[iri["schema:Product"]], + **CODEMETA_STRATEGY[iri["schema:MedicalEntity"]] } + + CODEMETA_STRATEGY[iri["schema:EducationalOrganization"]] = { **CODEMETA_STRATEGY[iri["schema:Organization"]], **CODEMETA_STRATEGY[iri["schema:CivicStructure"]] } -CODEMETA_STRATEGY[iri["schema:DefinedRegion"]] = { - **CODEMETA_STRATEGY[iri["schema:Place"]], - **CODEMETA_STRATEGY[iri["schema:StructuredValue"]] + +CODEMETA_STRATEGY[iri["schema:HowToSection"]] = { + **CODEMETA_STRATEGY[iri["schema:CreativeWork"]], + **CODEMETA_STRATEGY[iri["schema:ItemList"]], + **CODEMETA_STRATEGY[iri["schema:ListItem"]] } -CODEMETA_STRATEGY[iri["schema:Person"]] = { - **CODEMETA_STRATEGY[iri["schema:Thing"]], - iri["schema:alumniOf"]: None, # FIXME: troublesome EducationalOrganization or Organization - iri["schema:brand"]: None, # FIXME: troublesome Brand or Organization - iri["schema:children"]: ACTIONS["merge_match_person"], - iri["schema:colleague"]: ACTIONS["merge_match_person"], - iri["schema:follows"]: ACTIONS["merge_match_person"], - iri["schema:funder"]: None, # FIXME: troublesome Organization or Person - iri["schema:height"]: None, # FIXME: troublesome Distance or QuantitativeValue - iri["schema:homeLocation"]: None, # FIXME: troublesome ContactPoint or Place - iri["schema:knows"]: ACTIONS["merge_match_person"], - iri["schema:memberOf"]: None, # FIXME: troublesome MemberProgramTier or Organization or ProgramMembership - iri["schema:netWorth"]: None, # FIXME: troublesome MonetaryAmount or PriceSpecification - iri["schema:parent"]: ACTIONS["merge_match_person"], - iri["schema:pronouns"]: None, # FIXME: troublesome DefinedTerm or StructuredValue - iri["schema:relatedTo"]: ACTIONS["merge_match_person"], - iri["schema:sibling"]: ACTIONS["merge_match_person"], - iri["schema:sponsor"]: None, # FIXME: troublesome Organization or Person - iri["schema:spouse"]: ACTIONS["merge_match_person"], - iri["schema:weight"]: None, # FIXME: troublesome Mass or QuantitativeValue - iri["schema:workLocation"]: None # FIXME: troublesome ContactPoint or Place + +CODEMETA_STRATEGY[iri["schema:HowToStep"]] = { + **CODEMETA_STRATEGY[iri["schema:CreativeWork"]], + **CODEMETA_STRATEGY[iri["schema:ItemList"]], + **CODEMETA_STRATEGY[iri["schema:ListItem"]] } -CODEMETA_STRATEGY[iri["schema:Taxon"]] = {**CODEMETA_STRATEGY[iri["schema:Thing"]]} + +CODEMETA_STRATEGY[iri["schema:MedicalCode"]] = { + **CODEMETA_STRATEGY[iri["schema:CategoryCode"]], + **CODEMETA_STRATEGY[iri["schema:MedicalIntangible"]] +} + + +CODEMETA_STRATEGY[iri["schema:PaymentCard"]] = { + **CODEMETA_STRATEGY[iri["schema:FinancialProduct"]], + **CODEMETA_STRATEGY[iri["schema:PaymentMethod"]] +} + +CODEMETA_STRATEGY[iri["schema:CreditCard"]] = { + **CODEMETA_STRATEGY[iri["schema:LoanOrCredit"]], + **CODEMETA_STRATEGY[iri["schema:PaymentCard"]] +} diff --git a/test/hermes_test/model/test_api_e2e.py b/test/hermes_test/model/test_api_e2e.py index 30ecd11c..6d0ce325 100644 --- a/test/hermes_test/model/test_api_e2e.py +++ b/test/hermes_test/model/test_api_e2e.py @@ -544,7 +544,6 @@ def test_process(tmp_path, monkeypatch, metadata_in, metadata_out): assert result == metadata_out -@pytest.mark.xfail @pytest.mark.parametrize( "metadata_in, metadata_out", [ From 3291c4d6a713a04899e24d223bf46462a4784694 Mon Sep 17 00:00:00 2001 From: Michael Fritzsche Date: Mon, 9 Mar 2026 12:52:56 +0100 Subject: [PATCH 19/19] formatting and doc strings --- src/hermes/model/merge/match.py | 56 ++++++++++++- src/hermes/model/merge/strategy.py | 123 ++--------------------------- 2 files changed, 58 insertions(+), 121 deletions(-) diff --git a/src/hermes/model/merge/match.py b/src/hermes/model/merge/match.py index 8a0aa9a1..cbcad94d 100644 --- a/src/hermes/model/merge/match.py +++ b/src/hermes/model/merge/match.py @@ -58,13 +58,30 @@ def match_func(left: Any, right: Any) -> bool: def match_person(left: Any, right: Any) -> bool: + """ + Compares two objects assuming they are representing schema:Person's + if they are not ld_dicts, == is used as a fallback.
+ If both objects have an @id value, the truth value returned by this function is the comparison of both ids. + If either other has no @id value and both objects have at least one email value, + they are considered equal if they have one common email. + If the equality of the objects is not yet decided, == comparison of the objects is returned. + + :param left: The first object for the comparison. + :type left: ld_merge_dict + :param right: The second object for the comparison. + :type right: ld_dict + + :return: The result of the comparison. + :rtype: bool + """ if not (isinstance(left, ld_dict) and isinstance(right, ld_dict)): return left == right if "@id" in left and "@id" in right: return left["@id"] == right["@id"] if "schema:email" in left and "schema:email" in right: - mails_right = right["schema:email"] - return any((mail in mails_right) for mail in left["schema:email"]) + if len(left["schema:email"]) > 0 and len(right["schema:email"]) > 0: + mails_right = right["schema:email"] + return any((mail in mails_right) for mail in left["schema:email"]) return left == right @@ -72,13 +89,46 @@ def match_multiple_types( *functions_for_types: list[tuple[str, Callable[[Any, Any], bool]]], fall_back_function: Callable[[Any, Any], bool] = match_keys("@id", fall_back_to_equals=True) ) -> Callable[[Any, Any], bool]: + """ + Returns a function that compares two objects using the given functions. + + :param functions_for_types: Tuples of type and match_function. + The returned function will compare two objects of a the same, given type with the specified function. + :type functions_for_types: list[tuple[str, Callable[[Any, Any], bool]]] + :param fall_back_function: The fallback for comparison if the objects that are being compared don't have a common + type with specified compare function or at least one object is not a JSON-LD dictionary. + :type fall_back_function: Callable[[Any, Any], bool] + + :return: The function that compares the two given objects using the given functions. + :rtype: Callable[[Any, Any], bool] + """ + + # create and return the match function using the given keys def match_func(left: Any, right: Any) -> bool: - if not ((isinstance(left, ld_dict) and isinstance(right, ld_dict)) and "@type" in left and "@type" in right): + """ + Compares two objects using a predetermined function if either objects is not an ld_dict + or they don't have a common type in a predetermined list of types.
+ If the objects are ld_dicts and have the same type with a known comparison function this is used instead. + + :param left: The first object for the comparison. + :type left: ld_merge_dict + :param right: The second object for the comparison. + :type right: ld_dict + + :return: The result of the comparison. + :rtype: bool + """ + # If at least one of the objects is not an ld_dict or contains no value for the key "@type", use the fallback. + if not (isinstance(left, ld_dict) and isinstance(right, ld_dict) and "@type" in left and "@type" in right): return fall_back_function(left, right) + # Extract the list of types types_left = left["@type"] types_right = right["@type"] + # Iterate over all known type, match_function pairs. + # If one type is in both objects return the result of the comparison with the match_function. for ld_type, func in functions_for_types: if ld_type in types_left and ld_type in types_right: return func(left, right) + # No common type with known match_function: Fallback return fall_back_function(left, right) return match_func diff --git a/src/hermes/model/merge/strategy.py b/src/hermes/model/merge/strategy.py index 5aaa5d7f..ac78545c 100644 --- a/src/hermes/model/merge/strategy.py +++ b/src/hermes/model/merge/strategy.py @@ -90,14 +90,12 @@ } - # Filled with entries for every schema-type that can be found inside an JSON-LD dict of type -# SoftwareSourceCode or SoftwareApplication. +# SoftwareSourceCode or SoftwareApplication using schema and CodeMeta as Context. CODEMETA_STRATEGY = {None: {None: ACTIONS["default"]}} CODEMETA_STRATEGY[iri["schema:Thing"]] = {iri["schema:owner"]: ACTIONS["OrganizationOrPerson"]} - CODEMETA_STRATEGY[iri["schema:Action"]] = { **CODEMETA_STRATEGY[iri["schema:Thing"]], iri["schema:agent"]: ACTIONS["OrganizationOrPerson"], @@ -107,7 +105,6 @@ } - CODEMETA_STRATEGY[iri["schema:BioChemEntity"]] = { **CODEMETA_STRATEGY[iri["schema:Thing"]], iri["schema:associatedDisease"]: ACTIONS["MedicalConditionOrPropertyValue"], @@ -117,14 +114,12 @@ iri["schema:taxonomicRange"]: ACTIONS["DefinedTermOrTaxon"] } - CODEMETA_STRATEGY[iri["schema:Gene"]] = { **CODEMETA_STRATEGY[iri["schema:BioChemEntity"]], iri["schema:expressedIn"]: ACTIONS["AnatomicalStructureOrAnatomicalSystemOrBioChemEntityOrDefinedTerm"] } - CODEMETA_STRATEGY[iri["schema:CreativeWork"]] = { **CODEMETA_STRATEGY[iri["schema:Thing"]], iri["schema:accountablePerson"]: ACTIONS["Person"], @@ -149,38 +144,30 @@ iri["schema:video"]: ACTIONS["ClipOrVideoObject"] } - CODEMETA_STRATEGY[iri["schema:Article"]] = {**CODEMETA_STRATEGY[iri["schema:CreativeWork"]]} CODEMETA_STRATEGY[iri["schema:NewsArticle"]] = {**CODEMETA_STRATEGY[iri["schema:Article"]]} CODEMETA_STRATEGY[iri["schema:ScholarlyArticle"]] = {**CODEMETA_STRATEGY[iri["schema:Article"]]} - CODEMETA_STRATEGY[iri["schema:Certification"]] = {**CODEMETA_STRATEGY[iri["schema:CreativeWork"]]} - CODEMETA_STRATEGY[iri["schema:Claim"]] = { **CODEMETA_STRATEGY[iri["schema:CreativeWork"]], iri["schema:claimInterpreter"]: ACTIONS["OrganizationOrPerson"] } - CODEMETA_STRATEGY[iri["schema:Clip"]] = { **CODEMETA_STRATEGY[iri["schema:CreativeWork"]], iri["schema:actor"]: ACTIONS["PerformingGroupOrPerson"], iri["schema:dircetor"]: ACTIONS["Person"], iri["schema:musicBy"]: ACTIONS["MusicGroupOrPerson"] } - CODEMETA_STRATEGY[iri["schema:Comment"]] = { **CODEMETA_STRATEGY[iri["schema:CreativeWork"]], iri["schema:parentItem"]: ACTIONS["CommentOrCreativeWork"] } CODEMETA_STRATEGY[iri["schema:CorrectionComment"]] = {**CODEMETA_STRATEGY[iri["schema:Comment"]]} - CODEMETA_STRATEGY[iri["schema:CreativeWorkSeason"]] = { **CODEMETA_STRATEGY[iri["schema:CreativeWork"]], iri["schema:actor"]: ACTIONS["PerformingGroupOrPerson"] } - CODEMETA_STRATEGY[iri["schema:DataCatalog"]] = {**CODEMETA_STRATEGY[iri["schema:CreativeWork"]]} - CODEMETA_STRATEGY[iri["schema:Dataset"]] = { **CODEMETA_STRATEGY[iri["schema:CreativeWork"]], iri["schema:variableMeasured"]: ACTIONS["PropertyOrPropertyValueOrStatisticalVariable"] @@ -189,12 +176,9 @@ **CODEMETA_STRATEGY[iri["schema:Dataset"]], iri["schema:dataFeedElement"]: ACTIONS["DataFeedItemOrThing"] } - CODEMETA_STRATEGY[iri["schema:DefinedTermSet"]] = {**CODEMETA_STRATEGY[iri["schema:CreativeWork"]]} CODEMETA_STRATEGY[iri["schema:CategoryCodeSet"]] = {**CODEMETA_STRATEGY[iri["schema:DefinedTermSet"]]} - CODEMETA_STRATEGY[iri["schema:EducationalOccupationalCredential"]] = {**CODEMETA_STRATEGY[iri["schema:CreativeWork"]]} - CODEMETA_STRATEGY[iri["schema:Episode"]] = { **CODEMETA_STRATEGY[iri["schema:CreativeWork"]], iri["schema:actor"]: ACTIONS["PerformingGroupOrPerson"], @@ -202,16 +186,12 @@ iri["schema:duration"]: ACTIONS["DurationOrQuantitativeValue"], iri["schema:musicBy"]: ACTIONS["MusicGroupOrPerson"] } - CODEMETA_STRATEGY[iri["schema:HowTo"]] = { **CODEMETA_STRATEGY[iri["schema:CreativeWork"]], iri["schema:step"]: ACTIONS["CreativeWorkOrHowToSectionOrHowToStep"] } - CODEMETA_STRATEGY[iri["schema:HyperTocEntry"]] = {**CODEMETA_STRATEGY[iri["schema:CreativeWork"]]} - CODEMETA_STRATEGY[iri["schema:Map"]] = {**CODEMETA_STRATEGY[iri["schema:CreativeWork"]]} - CODEMETA_STRATEGY[iri["schema:MediaObject"]] = { **CODEMETA_STRATEGY[iri["schema:CreativeWork"]], iri["schema:duration"]: ACTIONS["DurationOrQuantitativeValue"], @@ -228,15 +208,12 @@ iri["schema:dircetor"]: ACTIONS["Person"], iri["schema:musicBy"]: ACTIONS["MusicGroupOrPerson"] } - CODEMETA_STRATEGY[iri["schema:MenuSection"]] = {**CODEMETA_STRATEGY[iri["schema:CreativeWork"]]} - CODEMETA_STRATEGY[iri["schema:MusicComposition"]] = { **CODEMETA_STRATEGY[iri["schema:CreativeWork"]], iri["schema:composer"]: ACTIONS["OrganizationOrPerson"], iri["schema:lyricist"]: ACTIONS["Person"] } - CODEMETA_STRATEGY[iri["schema:MusicPlaylist"]] = { **CODEMETA_STRATEGY[iri["schema:CreativeWork"]], iri["schema:track"]: ACTIONS["ItemListOrMusicRecording"] @@ -250,44 +227,34 @@ iri["schema:creditedTo"]: ACTIONS["OrganizationOrPerson"], iri["schema:duration"]: ACTIONS["DurationOrQuantitativeValue"] } - CODEMETA_STRATEGY[iri["schema:MusicRecording"]] = { **CODEMETA_STRATEGY[iri["schema:CreativeWork"]], iri["schema:byArtist"]: ACTIONS["MusicGroupOrPerson"], iri["schema:duration"]: ACTIONS["DurationOrQuantitativeValue"] } - CODEMETA_STRATEGY[iri["schema:Photograph"]] = {**CODEMETA_STRATEGY[iri["schema:CreativeWork"]]} - CODEMETA_STRATEGY[iri["schema:Review"]] = { **CODEMETA_STRATEGY[iri["schema:CreativeWork"]], iri["schema:negativeNotes"]: ACTIONS["ItemListOrListItemOrWebContent"], iri["schema:positiveNotes"]: ACTIONS["ItemListOrListItemOrWebContent"] } - CODEMETA_STRATEGY[iri["schema:SoftwareApplication"]] = {**CODEMETA_STRATEGY[iri["schema:CreativeWork"]]} CODEMETA_STRATEGY[iri["schema:OperatingSystem"]] = {**CODEMETA_STRATEGY[iri["schema:SoftwareApplication"]]} CODEMETA_STRATEGY[iri["schema:RuntimePlatform"]] = {**CODEMETA_STRATEGY[iri["schema:SoftwareApplication"]]} - CODEMETA_STRATEGY[iri["schema:SoftwareSourceCode"]] = { **CODEMETA_STRATEGY[iri["schema:CreativeWork"]], iri["maintainer"]: ACTIONS["Person"] } - CODEMETA_STRATEGY[iri["schema:WebContent"]] = {**CODEMETA_STRATEGY[iri["schema:CreativeWork"]]} - CODEMETA_STRATEGY[iri["schema:WebPage"]] = { **CODEMETA_STRATEGY[iri["schema:CreativeWork"]], iri["schema:reviewedBy"]: ACTIONS["OrganizationOrPerson"] } CODEMETA_STRATEGY[iri["schema:AboutPage"]] = {**CODEMETA_STRATEGY[iri["schema:WebPage"]]} - CODEMETA_STRATEGY[iri["schema:WebPageElement"]] = {**CODEMETA_STRATEGY[iri["schema:CreativeWork"]]} - CODEMETA_STRATEGY[iri["schema:WebSite"]] = {**CODEMETA_STRATEGY[iri["schema:CreativeWork"]]} - CODEMETA_STRATEGY[iri["schema:Event"]] = { **CODEMETA_STRATEGY[iri["schema:Thing"]], iri["schema:actor"]: ACTIONS["PerformingGroupOrPerson"], @@ -305,40 +272,28 @@ iri["schema:translator"]: ACTIONS["OrganizationOrPerson"] } - CODEMETA_STRATEGY[iri["schema:PublicationEvent"]] = { **CODEMETA_STRATEGY[iri["schema:Event"]], iri["schema:publishedBy"]: ACTIONS["OrganizationOrPerson"] } - CODEMETA_STRATEGY[iri["schema:Intangible"]] = {**CODEMETA_STRATEGY[iri["schema:Thing"]]} - CODEMETA_STRATEGY[iri["schema:AlignmentObject"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} - CODEMETA_STRATEGY[iri["schema:Audience"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} - CODEMETA_STRATEGY[iri["schema:Brand"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} - CODEMETA_STRATEGY[iri["schema:BroadcastChannel"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} - CODEMETA_STRATEGY[iri["schema:BroadcastFrequencySpecification"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} - CODEMETA_STRATEGY[iri["schema:Class"]] = { **CODEMETA_STRATEGY[iri["schema:Intangible"]], iri["schema:supersededBy"]: ACTIONS["ClassOrEnumeration"] } - CODEMETA_STRATEGY[iri["schema:ComputerLanguage"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} - CODEMETA_STRATEGY[iri["schema:ConstraintNode"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} CODEMETA_STRATEGY[iri["schema:StatisticalVariable"]] = {**CODEMETA_STRATEGY[iri["schema:ConstraintNode"]]} - CODEMETA_STRATEGY[iri["schema:DefinedTerm"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} CODEMETA_STRATEGY[iri["schema:CategoryCode"]] = {**CODEMETA_STRATEGY[iri["schema:DefinedTerm"]]} - CODEMETA_STRATEGY[iri["schema:Demand"]] = { **CODEMETA_STRATEGY[iri["schema:Intangible"]], iri["schema:acceptedPaymentMethod"]: ACTIONS["LoanOrCreditOrPaymentMethod"], @@ -348,11 +303,8 @@ iri["schema:itemOffered"]: ACTIONS["AggregateOfferOrCreativeWorkOrEventOrMenuItemOrProductOrServiceOrTrip"], iri["schema:seller"]: ACTIONS["OrganizationOrPerson"] } - CODEMETA_STRATEGY[iri["schema:EnergyConsumptionDetails"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} - CODEMETA_STRATEGY[iri["schema:EntryPoint"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} - CODEMETA_STRATEGY[iri["schema:Enumeration"]] = { **CODEMETA_STRATEGY[iri["schema:Intangible"]], iri["schema:supersededBy"]: ACTIONS["ClassOrEnumeration"] @@ -364,7 +316,6 @@ ]: ACTIONS["DefinedTermOrEnumerationOrPropertyValueOrQualitativeValueOrQuantitativeValueOrStructuredValue"] } CODEMETA_STRATEGY[iri["schema:SizeSpecification"]] = {**CODEMETA_STRATEGY[iri["schema:QualitativeValue"]]} - CODEMETA_STRATEGY[iri["schema:GeospatialGeometry"]] = { **CODEMETA_STRATEGY[iri["schema:Intangible"]], iri["schema:geoContains"]: ACTIONS["GeospatialGeometryOrPlace"], @@ -378,7 +329,6 @@ iri["schema:geoTouches"]: ACTIONS["GeospatialGeometryOrPlace"], iri["schema:geoWithin"]: ACTIONS["GeospatialGeometryOrPlace"] } - CODEMETA_STRATEGY[iri["schema:Grant"]] = { **CODEMETA_STRATEGY[iri["schema:Intangible"]], iri[ @@ -387,55 +337,39 @@ iri["schema:funder"]: ACTIONS["OrganizationOrPerson"], iri["schema:sponsor"]: ACTIONS["OrganizationOrPerson"] } - CODEMETA_STRATEGY[iri["schema:HealthInsurancePlan"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} - CODEMETA_STRATEGY[iri["schema:HealthPlanCostSharingSpecification"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} - CODEMETA_STRATEGY[iri["schema:HealthPlanFormulary"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} - CODEMETA_STRATEGY[iri["schema:HealthPlanNetwork"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} - CODEMETA_STRATEGY[iri["schema:ItemList"]] = { **CODEMETA_STRATEGY[iri["schema:Intangible"]], iri["schema:itemListElement"]: ACTIONS["ListItemOrThing"] } CODEMETA_STRATEGY[iri["schema:OfferCatalog"]] = {**CODEMETA_STRATEGY[iri["schema:ItemList"]]} CODEMETA_STRATEGY[iri["schema:BreadcrumbList"]] = {**CODEMETA_STRATEGY[iri["schema:ItemList"]]} - CODEMETA_STRATEGY[iri["schema:Language"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} - CODEMETA_STRATEGY[iri["schema:ListItem"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} CODEMETA_STRATEGY[iri["schema:HowToItem"]] = {**CODEMETA_STRATEGY[iri["schema:ListItem"]]} CODEMETA_STRATEGY[iri["schema:HowToSupply"]] = {**CODEMETA_STRATEGY[iri["schema:HowToItem"]]} CODEMETA_STRATEGY[iri["schema:HowToTool"]] = {**CODEMETA_STRATEGY[iri["schema:HowToItem"]]} - CODEMETA_STRATEGY[iri["schema:MediaSubscription"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} - CODEMETA_STRATEGY[iri["schema:MemberProgram"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} - CODEMETA_STRATEGY[iri["schema:MemberProgramTier"]] = { **CODEMETA_STRATEGY[iri["schema:Intangible"]], iri["schema:hasTierRequirement"]: ACTIONS["CreditCardOrMonetaryAmountOrUnitPriceSpecification"] } - CODEMETA_STRATEGY[iri["schema:MenuItem"]] = { **CODEMETA_STRATEGY[iri["schema:Intangible"]], iri["schema:menuAddOn"]: ACTIONS["MenuItemOrMenuSection"], iri["schema:offers"]: ACTIONS["DemandOrOffer"] } - CODEMETA_STRATEGY[iri["schema:MerchantReturnPolicy"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} - CODEMETA_STRATEGY[iri["schema:MerchantReturnPolicySeasonalOverride"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} - CODEMETA_STRATEGY[iri["schema:Occupation"]] = { **CODEMETA_STRATEGY[iri["schema:Intangible"]], iri["schema:estimatedSalary"]: ACTIONS["MonetaryAmountOrMonetaryAmountDistribution"] } - CODEMETA_STRATEGY[iri["schema:OccupationalExperienceRequirements"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} - CODEMETA_STRATEGY[iri["schema:Offer"]] = { **CODEMETA_STRATEGY[iri["schema:Intangible"]], iri["schema:acceptedPaymentMethod"]: ACTIONS["LoanOrCreditOrPaymentMethod"], @@ -452,37 +386,29 @@ **CODEMETA_STRATEGY[iri["schema:Offer"]], iri["schema:offers"]: ACTIONS["DemandOrOffer"] } - CODEMETA_STRATEGY[iri["schema:PaymentMethod"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} - CODEMETA_STRATEGY[iri["schema:ProgramMembership"]] = { **CODEMETA_STRATEGY[iri["schema:Intangible"]], iri["schema:member"]: ACTIONS["OrganizationOrPerson"] } - CODEMETA_STRATEGY[iri["schema:Property"]] = { **CODEMETA_STRATEGY[iri["schema:Intangible"]], iri["schema:supersededBy"]: ACTIONS["ClassOrEnumerationOrProperty"] } - CODEMETA_STRATEGY[iri["schema:Quantity"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} CODEMETA_STRATEGY[iri["schema:Duration"]] = {**CODEMETA_STRATEGY[iri["schema:Quantity"]]} CODEMETA_STRATEGY[iri["schema:Energy"]] = {**CODEMETA_STRATEGY[iri["schema:Quantity"]]} CODEMETA_STRATEGY[iri["schema:Mass"]] = {**CODEMETA_STRATEGY[iri["schema:Quantity"]]} - CODEMETA_STRATEGY[iri["schema:Rating"]] = { **CODEMETA_STRATEGY[iri["schema:Intangible"]], iri["schema:author"]: ACTIONS["OrganizationOrPerson"] } CODEMETA_STRATEGY[iri["schema:AggregateRating"]] = {**CODEMETA_STRATEGY[iri["schema:Rating"]]} - CODEMETA_STRATEGY[iri["schema:Schedule"]] = { **CODEMETA_STRATEGY[iri["schema:Intangible"]], iri["schema:duration"]: ACTIONS["DurationOrQuantitativeValue"] } - CODEMETA_STRATEGY[iri["schema:Series"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} - CODEMETA_STRATEGY[iri["schema:Service"]] = { **CODEMETA_STRATEGY[iri["schema:Intangible"]], iri["schema:areaServed"]: ACTIONS["AdministrativeAreaOrGeoShapeOrPlace"], @@ -498,11 +424,8 @@ CODEMETA_STRATEGY[iri["schema:CableOrSatelliteService"]] = {**CODEMETA_STRATEGY[iri["schema:Service"]]} CODEMETA_STRATEGY[iri["schema:FinancialProduct"]] = {**CODEMETA_STRATEGY[iri["schema:Service"]]} CODEMETA_STRATEGY[iri["schema:LoanOrCredit"]] = {**CODEMETA_STRATEGY[iri["schema:FinancialProduct"]]} - CODEMETA_STRATEGY[iri["schema:ServiceChannel"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} - CODEMETA_STRATEGY[iri["schema:SpeakableSpecification"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} - CODEMETA_STRATEGY[iri["schema:StructuredValue"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} CODEMETA_STRATEGY[iri["schema:ContactPoint"]] = { **CODEMETA_STRATEGY[iri["schema:StructuredValue"]], @@ -524,7 +447,7 @@ iri["schema:depth"]: ACTIONS["DistanceOrQuantitativeValue"], iri["schema:height"]: ACTIONS["DistanceOrQuantitativeValue"], iri["schema:shippingRate"]: ACTIONS["MonetaryAmountOrShippingRateSettings"], - iri["schema:weight"]:ACTIONS["MassOrQuantitativeValue"], + iri["schema:weight"]: ACTIONS["MassOrQuantitativeValue"], iri["schema:width"]: ACTIONS["DistanceOrQuantitativeValue"] } CODEMETA_STRATEGY[iri["schema:OpeningHoursSpecification"]] = {**CODEMETA_STRATEGY[iri["schema:StructuredValue"]]} @@ -571,7 +494,7 @@ iri["schema:height"]: ACTIONS["DistanceOrQuantitativeValue"], iri["schema:shippingRate"]: ACTIONS["MonetaryAmountOrShippingRateSettings"], iri["schema:transitTime"]: ACTIONS["QuantitativeValueOrServicePeriod"], - iri["schema:weight"]:ACTIONS["MassOrQuantitativeValue"], + iri["schema:weight"]: ACTIONS["MassOrQuantitativeValue"], iri["schema:width"]: ACTIONS["DistanceOrQuantitativeValue"] } CODEMETA_STRATEGY[iri["schema:ShippingDeliveryTime"]] = { @@ -592,27 +515,19 @@ iri["schema:typeOfGood"]: ACTIONS["ProductOrService"] } CODEMETA_STRATEGY[iri["schema:WarrantyPromise"]] = {**CODEMETA_STRATEGY[iri["schema:StructuredValue"]]} - CODEMETA_STRATEGY[iri["schema:VirtualLocation"]] = {**CODEMETA_STRATEGY[iri["schema:Intangible"]]} - CODEMETA_STRATEGY[iri["schema:MedicalEntity"]] = {**CODEMETA_STRATEGY[iri["schema:Thing"]]} - CODEMETA_STRATEGY[iri["schema:AnatomicalStructure"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalEntity"]]} - CODEMETA_STRATEGY[iri["schema:AnatomicalSystem"]] = { **CODEMETA_STRATEGY[iri["schema:MedicalEntity"]], iri["schema:comprisedOf"]: ACTIONS["AnatomicalStructureOrAnatomicalSystem"] } - CODEMETA_STRATEGY[iri["schema:DrugClass"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalEntity"]]} - CODEMETA_STRATEGY[iri["schema:LifestyleModification"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalEntity"]]} - CODEMETA_STRATEGY[iri["schema:MedicalCause"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalEntity"]]} - CODEMETA_STRATEGY[iri["schema:MedicalCondition"]] = { **CODEMETA_STRATEGY[iri["schema:MedicalEntity"]], iri["schema:associatedAnatomy"]: ACTIONS["AnatomicalStructureOrAnatomicalSystemOrSuperficialAnatomy"], @@ -624,13 +539,9 @@ iri["schema:possibleTreatment"]: ACTIONS["DrugOrDrugClassOrLifestyleModificationOrMedicalTherapy"] } CODEMETA_STRATEGY[iri["schema:MedicalSign"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalSignOrSymptom"]]} - CODEMETA_STRATEGY[iri["schema:MedicalContraindication"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalEntity"]]} - CODEMETA_STRATEGY[iri["schema:MedicalDevice"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalEntity"]]} - CODEMETA_STRATEGY[iri["schema:MedicalGuideline"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalEntity"]]} - CODEMETA_STRATEGY[iri["schema:MedicalIntangible"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalEntity"]]} CODEMETA_STRATEGY[iri["schema:DDxElement"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalIntangible"]]} CODEMETA_STRATEGY[iri["schema:DrugLegalStatus"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalIntangible"]]} @@ -638,27 +549,21 @@ CODEMETA_STRATEGY[iri["schema:DrugStrength"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalIntangible"]]} CODEMETA_STRATEGY[iri["schema:MaximumDoseSchedule"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalIntangible"]]} CODEMETA_STRATEGY[iri["schema:MedicalConditionStage"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalIntangible"]]} - CODEMETA_STRATEGY[iri["schema:MedicalProcedure"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalEntity"]]} CODEMETA_STRATEGY[iri["schema:TherapeuticProcedure"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalProcedure"]]} CODEMETA_STRATEGY[iri["schema:MedicalTherapy"]] = {**CODEMETA_STRATEGY[iri["schema:TherapeuticProcedure"]]} - CODEMETA_STRATEGY[iri["schema:MedicalRiskFactor"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalEntity"]]} - CODEMETA_STRATEGY[iri["schema:MedicalStudy"]] = { **CODEMETA_STRATEGY[iri["schema:MedicalEntity"]], iri["schema:sponsor"]: ACTIONS["OrganizationOrPerson"] } - CODEMETA_STRATEGY[iri["schema:MedicalTest"]] = {**CODEMETA_STRATEGY[iri["schema:MedicalEntity"]]} - CODEMETA_STRATEGY[iri["schema:SuperficialAnatomy"]] = { **CODEMETA_STRATEGY[iri["schema:MedicalEntity"]], iri["schema:relatedAnatomy"]: ACTIONS["AnatomicalStructureOrAnatomicalSystem"] } - CODEMETA_STRATEGY[iri["schema:Organization"]] = { **CODEMETA_STRATEGY[iri["schema:Thing"]], iri["schema:acceptedPaymentMethod"]: ACTIONS["LoanOrCreditOrPaymentMethod"], @@ -676,7 +581,6 @@ iri["schema:sponsor"]: ACTIONS["OrganizationOrPerson"] } - CODEMETA_STRATEGY[iri["schema:PerformingGroup"]] = {**CODEMETA_STRATEGY[iri["schema:Organization"]]} CODEMETA_STRATEGY[iri["schema:MusicGroup"]] = { **CODEMETA_STRATEGY[iri["schema:PerformingGroup"]], @@ -685,7 +589,6 @@ } - CODEMETA_STRATEGY[iri["schema:Person"]] = { **CODEMETA_STRATEGY[iri["schema:Thing"]], iri["schema:alumniOf"]: ACTIONS["EducationalOrganizationOrOrganization"], @@ -705,12 +608,11 @@ iri["schema:sibling"]: ACTIONS["Person"], iri["schema:sponsor"]: ACTIONS["OrganizationOrPerson"], iri["schema:spouse"]: ACTIONS["Person"], - iri["schema:weight"]:ACTIONS["MassOrQuantitativeValue"], + iri["schema:weight"]: ACTIONS["MassOrQuantitativeValue"], iri["schema:workLocation"]: ACTIONS["ContactPointOrPlace"] } - CODEMETA_STRATEGY[iri["schema:Place"]] = { **CODEMETA_STRATEGY[iri["schema:Thing"]], iri["schema:geo"]: ACTIONS["GeoCoordinatesOrGeoShape"], @@ -727,14 +629,11 @@ iri["schema:photo"]: ACTIONS["ImageObjectOrPhotograph"] } - CODEMETA_STRATEGY[iri["schema:AdministrativeArea"]] = {**CODEMETA_STRATEGY[iri["schema:Place"]]} CODEMETA_STRATEGY[iri["schema:Country"]] = {**CODEMETA_STRATEGY[iri["schema:AdministrativeArea"]]} - CODEMETA_STRATEGY[iri["schema:CivicStructure"]] = {**CODEMETA_STRATEGY[iri["schema:Place"]]} - CODEMETA_STRATEGY[iri["schema:Product"]] = { **CODEMETA_STRATEGY[iri["schema:Thing"]], iri["schema:brand"]: ACTIONS["BrandOrOrganization"], @@ -748,73 +647,61 @@ iri["schema:offers"]: ACTIONS["DemandOrOffer"], iri["schema:positiveNotes"]: ACTIONS["ItemListOrListItemOrWebContent"], iri["schema:size"]: ACTIONS["DefinedTermOrQuantitativeValueOrSizeSpecification"], - iri["schema:weight"]:ACTIONS["MassOrQuantitativeValue"], + iri["schema:weight"]: ACTIONS["MassOrQuantitativeValue"], iri["schema:width"]: ACTIONS["DistanceOrQuantitativeValue"] } - CODEMETA_STRATEGY[iri["schema:ProductGroup"]] = {**CODEMETA_STRATEGY[iri["schema:Product"]]} - CODEMETA_STRATEGY[iri["schema:ProductModel"]] = { **CODEMETA_STRATEGY[iri["schema:Product"]], iri["schema:isVariantOf"]: ACTIONS["ProductGroupOrProductModel"] } - CODEMETA_STRATEGY[iri["schema:Taxon"]] = {**CODEMETA_STRATEGY[iri["schema:Thing"]]} - CODEMETA_STRATEGY[iri["schema:CreativeWorkSeries"]] = { **CODEMETA_STRATEGY[iri["schema:CreativeWork"]], **CODEMETA_STRATEGY[iri["schema:Series"]] } - CODEMETA_STRATEGY[iri["schema:DefinedRegion"]] = { **CODEMETA_STRATEGY[iri["schema:Place"]], **CODEMETA_STRATEGY[iri["schema:StructuredValue"]] } - CODEMETA_STRATEGY[iri["schema:Drug"]] = { **CODEMETA_STRATEGY[iri["schema:Product"]], **CODEMETA_STRATEGY[iri["schema:MedicalEntity"]] } - CODEMETA_STRATEGY[iri["schema:EducationalOrganization"]] = { **CODEMETA_STRATEGY[iri["schema:Organization"]], **CODEMETA_STRATEGY[iri["schema:CivicStructure"]] } - CODEMETA_STRATEGY[iri["schema:HowToSection"]] = { **CODEMETA_STRATEGY[iri["schema:CreativeWork"]], **CODEMETA_STRATEGY[iri["schema:ItemList"]], **CODEMETA_STRATEGY[iri["schema:ListItem"]] } - CODEMETA_STRATEGY[iri["schema:HowToStep"]] = { **CODEMETA_STRATEGY[iri["schema:CreativeWork"]], **CODEMETA_STRATEGY[iri["schema:ItemList"]], **CODEMETA_STRATEGY[iri["schema:ListItem"]] } - CODEMETA_STRATEGY[iri["schema:MedicalCode"]] = { **CODEMETA_STRATEGY[iri["schema:CategoryCode"]], **CODEMETA_STRATEGY[iri["schema:MedicalIntangible"]] } - CODEMETA_STRATEGY[iri["schema:PaymentCard"]] = { **CODEMETA_STRATEGY[iri["schema:FinancialProduct"]], **CODEMETA_STRATEGY[iri["schema:PaymentMethod"]] } - CODEMETA_STRATEGY[iri["schema:CreditCard"]] = { **CODEMETA_STRATEGY[iri["schema:LoanOrCredit"]], **CODEMETA_STRATEGY[iri["schema:PaymentCard"]]